activerecord-alt-mongo-adapter 0.1.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,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