activerecord-alt-mongo-adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,294 @@
1
+ class SQLParser
2
+ options no_result_var
3
+ rule
4
+ sql : create_statement
5
+ | read_statemant
6
+ | update_statemant
7
+ | delete_statemant
8
+
9
+ create_statement : INSERT INTO id '(' id_list ')' VALUES '(' value_list ')'
10
+ {
11
+ {:command => :insert, :table => val[2], :column_list => val[4], :value_list => val[8]}
12
+ }
13
+
14
+ read_statemant : SELECT select_list FROM id where_clause order_by_clause limit_clause offset_clause
15
+ {
16
+ {:command => :select, :table => val[3], :select_list => val[1], :condition => val[4], :order => val[5], :limit => val[6], :offset => val[7]}
17
+ }
18
+ | SELECT DISTINCT id FROM id where_clause
19
+ {
20
+ {:command => :select, :table => val[4], :select_list => val[2], :distinct => val[2], :condition => val[5]}
21
+ }
22
+ | SELECT count_clause FROM id where_clause order_by_clause limit_clause offset_clause
23
+ {
24
+ {:command => :select, :table => val[3], :count => val[1], :condition => val[4], :order => val[5], :limit => val[6], :offset => val[7]}
25
+ }
26
+
27
+ count_clause : COUNT '(' count_arg ')'
28
+ {
29
+ "count_all"
30
+ }
31
+ | COUNT '(' count_arg ')' AS id
32
+ {
33
+ val[5]
34
+ }
35
+
36
+ count_arg : '*'
37
+ | id
38
+
39
+ select_list : '*'
40
+ {
41
+ []
42
+ }
43
+ | id_list
44
+
45
+ where_clause :
46
+ {
47
+ []
48
+ }
49
+ | WHERE id_search_condition
50
+ {
51
+ val[1]
52
+ }
53
+ | WHERE search_condition
54
+ {
55
+ val[1]
56
+ }
57
+
58
+ id_search_condition : id_predicate
59
+ | '(' id_predicate ')'
60
+ {
61
+ val[1]
62
+ }
63
+
64
+ id_predicate : ID '=' value
65
+ {
66
+ val[2]
67
+ }
68
+ | ID IN '(' value_list ')'
69
+ {
70
+ val[3]
71
+ }
72
+
73
+ search_condition : boolean_primary
74
+ {
75
+ [val[0]].flatten
76
+ }
77
+ | search_condition AND boolean_primary
78
+ {
79
+ (val[0] << val[2]).flatten
80
+ }
81
+
82
+ boolean_primary : predicate
83
+ | '(' search_condition ')'
84
+ {
85
+ val[1]
86
+ }
87
+
88
+ predicate : id op value
89
+ {
90
+ {:name => val[0], :op => val[1], :expr => val[2]}
91
+ }
92
+ | NOT id op value
93
+ {
94
+ {:name => val[1], :op => val[2], :expr => val[3], :not => true}
95
+ }
96
+ | id op '(' value_list ')'
97
+ {
98
+ {:name => val[0], :op => val[1], :expr => val[3]}
99
+ }
100
+ | NOT id op '(' value_list ')'
101
+ {
102
+ {:name => val[1], :op => val[2], :expr => val[4], :not => true}
103
+ }
104
+ | between_predicate
105
+ | not_in_predicate
106
+
107
+ between_predicate : id BETWEEN value AND value
108
+ {
109
+ {:name => val[0], :op => '$bt', :expr => [val[2], val[4]]}
110
+ }
111
+
112
+ not_in_predicate : id NOT IN '(' value_list ')'
113
+ {
114
+ {:name => val[0], :op => '$nin', :expr => val[4]}
115
+ }
116
+
117
+ order_by_clause :
118
+ {
119
+ nil
120
+ }
121
+ | ORDER BY id ordering_spec
122
+ {
123
+ {:name => val[2], :type => val[3]}
124
+ }
125
+
126
+ ordering_spec :
127
+ {
128
+ :asc
129
+ }
130
+ | order_spec
131
+
132
+ limit_clause :
133
+ {
134
+ nil
135
+ }
136
+ | LIMIT NUMBER
137
+ {
138
+ val[1]
139
+ }
140
+
141
+ offset_clause :
142
+ {
143
+ nil
144
+ }
145
+ | OFFSET NUMBER
146
+ {
147
+ val[1]
148
+ }
149
+
150
+ update_statemant : UPDATE id SET set_clause_list where_clause
151
+ {
152
+ {:command => :update, :table => val[1], :set_clause_list => val[3], :condition => val[4]}
153
+ }
154
+
155
+ set_clause_list : set_clause
156
+ | set_clause_list ',' set_clause
157
+ {
158
+ val[0].merge val[2]
159
+ }
160
+
161
+ set_clause : id '=' value
162
+ {
163
+ {val[0] => val[2]}
164
+ }
165
+
166
+ delete_statemant : DELETE FROM id where_clause
167
+ {
168
+ {:command => :delete, :table => val[2], :condition => val[3]}
169
+ }
170
+
171
+ id : IDENTIFIER
172
+
173
+ id_list : id
174
+ {
175
+ [val[0]]
176
+ }
177
+ | id_list ',' id
178
+ {
179
+ val[0] << val[2]
180
+ }
181
+
182
+ value : STRING
183
+ | NUMBER
184
+ | NULL
185
+
186
+ value_list : value
187
+ {
188
+ [val[0]]
189
+ }
190
+ | value_list ',' value
191
+ {
192
+ val[0] << val[2]
193
+ }
194
+
195
+ op : IN { '$in' }
196
+ | REGEXP { '$regexp' }
197
+ | MOD { '$mod' }
198
+ | EXISTS { '$exists' }
199
+ | '<>' { '$ne' }
200
+ | '!=' { '$ne' }
201
+ | '>=' { '$gte' }
202
+ | '<=' { '$lte' }
203
+ | '>' { '$gt' }
204
+ | '<' { '$lt' }
205
+ | '=' { '$eq' }
206
+
207
+ order_spec : ASC { :asc }
208
+ | DESC { :desc }
209
+
210
+ end
211
+
212
+ ---- header
213
+
214
+ require 'strscan'
215
+
216
+ module ActiveMongo
217
+
218
+ ---- inner
219
+
220
+ KEYWORDS = %w(
221
+ AND
222
+ AS
223
+ ASC
224
+ BETWEEN
225
+ BY
226
+ COUNT
227
+ DELETE
228
+ DESC
229
+ DISTINCT
230
+ EXISTS
231
+ FROM
232
+ IN
233
+ INSERT
234
+ INTO
235
+ LIMIT
236
+ MOD
237
+ NOT
238
+ OFFSET
239
+ ORDER
240
+ REGEXP
241
+ SELECT
242
+ SET
243
+ UPDATE
244
+ VALUES
245
+ WHERE
246
+ )
247
+
248
+ KEYWORD_REGEXP = Regexp.compile("(?:#{KEYWORDS.join '|'})\\b", Regexp::IGNORECASE)
249
+
250
+ def initialize(obj)
251
+ src = obj.is_a?(IO) ? obj.read : obj.to_s
252
+ @ss = StringScanner.new(src)
253
+ end
254
+
255
+ def scan
256
+ piece = nil
257
+
258
+ until @ss.eos?
259
+ if (tok = @ss.scan /\s+/)
260
+ # nothing to do
261
+ elsif (tok = @ss.scan /(?:<>|!=|>=|<=|>|<|=)/)
262
+ yield tok, tok
263
+ elsif (tok = @ss.scan KEYWORD_REGEXP)
264
+ yield tok.upcase.to_sym, tok
265
+ elsif (tok = @ss.scan /NULL\b/i)
266
+ yield :NULL, nil
267
+ elsif (tok = @ss.scan /'(?:[^']|'')*'/) #'
268
+ yield :STRING, tok.slice(1...-1).gsub(/''/, "'")
269
+ elsif (tok = @ss.scan /-?(?:0|[1-9]\d*)(?:\.\d+)/)
270
+ yield :NUMBER, tok.to_f
271
+ elsif (tok = @ss.scan /-?(?:0|[1-9]\d*)/)
272
+ yield :NUMBER, tok.to_i
273
+ elsif (tok = @ss.scan /[,\(\)\*]/)
274
+ yield tok, tok
275
+ elsif (tok = @ss.scan /(?:[a-z_]\w+\.|[a-z]\.)*ID\b/i)
276
+ yield :ID, tok
277
+ elsif (tok = @ss.scan /(?:[a-z_]\w+\.|[a-z]\.)*(?:[a-z_]\w+|[a-z])/i)
278
+ yield :IDENTIFIER, tok
279
+ else
280
+ raise Racc::ParseError, ('parse error on value "%s"' % @ss.rest.inspect)
281
+ end
282
+ end
283
+
284
+ yield false, '$'
285
+ end
286
+ private :scan
287
+
288
+ def parse
289
+ yyparse self, :scan
290
+ end
291
+
292
+ ---- footer
293
+
294
+ end # module ActiveMongo
@@ -0,0 +1,184 @@
1
+ require 'active_record/base'
2
+ require 'active_record/connection_adapters/abstract_adapter'
3
+ require 'mongo'
4
+ require 'active_mongo/collection'
5
+ require 'active_mongo/sqlparser.tab'
6
+
7
+ module ActiveRecord
8
+ class Base
9
+ def self.mongo_connection(config)
10
+ config = config.symbolize_keys
11
+ pair_or_host = config[:host] || 'localhost'
12
+ port = config[:port] || 27017
13
+ database = config[:database]
14
+
15
+ options = config.dup
16
+ [:adapter, :host, :port, :database].each {|i| options.delete(i) }
17
+
18
+ connection = Mongo::Connection.new(pair_or_host, port, options).db(database)
19
+ ConnectionAdapters::MongoAdapter.new(connection, logger, config)
20
+ end
21
+ end
22
+
23
+ module ConnectionAdapters
24
+ class MongoAdapter < AbstractAdapter
25
+ def initialize(connection, logger, config)
26
+ super(connection, logger)
27
+ @config = config
28
+ end
29
+
30
+ def supports_count_distinct?
31
+ false
32
+ end
33
+
34
+ def select(sql, name = nil)
35
+ log(sql, name) do
36
+ parsed_sql = ActiveMongo::SQLParser.new(sql).parse
37
+ table = parsed_sql[:table]
38
+ coll = @connection.collection(table)
39
+
40
+ count = parsed_sql[:count]
41
+ distinct = parsed_sql[:distinct]
42
+ selector = query2selector(parsed_sql)
43
+ opts = query2opts(parsed_sql)
44
+
45
+ if count and selector.empty? and opts.empty?
46
+ [{count => coll.count}]
47
+ elsif distinct
48
+ coll.distinct(distinct, selector).map do |row|
49
+ {distinct => row}
50
+ end
51
+ else
52
+ rows = []
53
+
54
+ coll.find(selector, opts).each do |row|
55
+ row['id'] = row['_id'].to_s if row
56
+ rows << row
57
+ end
58
+
59
+ count ? [{count => rows.length}] : rows
60
+ end
61
+ end
62
+ end
63
+
64
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
65
+ log(sql, name) do
66
+ parsed_sql = ActiveMongo::SQLParser.new(sql).parse
67
+ table, column_list, value_list = parsed_sql.values_at(:table, :column_list, :value_list)
68
+
69
+ doc = {}
70
+ column_list.zip(value_list).each {|k, v| doc[k] = v }
71
+
72
+ coll = @connection.collection(table)
73
+ coll.insert(doc).to_s
74
+ end
75
+ end
76
+
77
+ def update_sql(sql, name = nil)
78
+ log(sql, name) do
79
+ parsed_sql = ActiveMongo::SQLParser.new(sql).parse
80
+ table = parsed_sql[:table]
81
+ coll = @connection.collection(table)
82
+ selector = query2selector(parsed_sql)
83
+ set_clause_list = parsed_sql[:set_clause_list]
84
+ doc = {}
85
+
86
+ set_clause_list.each do |k, v|
87
+ k = k.split('.').last
88
+ doc[k] = v
89
+ end
90
+
91
+ coll.update(selector, {'$set' => doc}, :multi => true)
92
+ end
93
+
94
+ # XXX:
95
+ 1
96
+ end
97
+
98
+ def delete_sql(sql, name = nil)
99
+ log(sql, name) do
100
+ parsed_sql = ActiveMongo::SQLParser.new(sql).parse
101
+ table = parsed_sql[:table]
102
+ coll = @connection.collection(table)
103
+ selector = query2selector(parsed_sql)
104
+ coll.remove(selector)
105
+ end
106
+
107
+ # XXX:
108
+ 1
109
+ end
110
+
111
+ private
112
+ def query2selector(parsed_sql)
113
+ condition = parsed_sql[:condition]
114
+ condition ||= []
115
+ selector = {}
116
+
117
+ if is_cond?(condition)
118
+ condition.each do |c|
119
+ name, op, expr, has_not = c.values_at(:name, :op, :expr, :not)
120
+ name = name.split('.').last
121
+ op_expr = nil
122
+
123
+ case op
124
+ when '$eq'
125
+ op_expr = expr
126
+ when '$regexp'
127
+ op_expr = Regexp.compile(expr)
128
+ when '$bt'
129
+ op_expr = {'$gte' => expr[0], '$lte' => expr[1]}
130
+ when '$exists'
131
+ op_expr = {'$exists' => !(expr =~ /f|false/i)}
132
+ else
133
+ op_expr = {op => expr}
134
+ end
135
+
136
+ if has_not
137
+ if op == '$eq'
138
+ op_expr = {'$ne' => expr}
139
+ elsif op == '$ne'
140
+ op_expr = expr
141
+ else
142
+ op_expr = {'$not' => op_expr}
143
+ end
144
+ end
145
+
146
+ if selector[name].kind_of?(Hash) and op_expr.kind_of?(Hash)
147
+ selector[name] = selector[name].merge(op_expr)
148
+ else
149
+ selector[name] = op_expr
150
+ end
151
+ end
152
+ else
153
+ selector['_id'] = {'$in' => [condition].flatten.map {|i| Mongo::ObjectID.from_string(i) }}
154
+ end
155
+
156
+ selector
157
+ end
158
+
159
+ def query2opts(parsed_sql)
160
+ order, limit, offset = parsed_sql.values_at(:order, :limit, :offset)
161
+ opts = {}
162
+
163
+ if order
164
+ name, type = order.values_at(:name, :type)
165
+ opts[:sort] = [name, type]
166
+ end
167
+
168
+ if limit
169
+ opts[:limit] = limit.to_i
170
+ end
171
+
172
+ if offset
173
+ opts[:skip] = offset.to_i
174
+ end
175
+
176
+ opts
177
+ end
178
+
179
+ def is_cond?(condition)
180
+ condition.kind_of?(Array) and condition.all? {|i| i.kind_of?(Hash) }
181
+ end
182
+ end
183
+ end
184
+ end