mosql 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +1 -0
- data/Gemfile.lock +11 -11
- data/README.md +11 -3
- data/lib/mosql/schema.rb +45 -8
- data/lib/mosql/sql.rb +18 -6
- data/lib/mosql/streamer.rb +45 -18
- data/lib/mosql/tailer.rb +61 -12
- data/lib/mosql/version.rb +1 -1
- data/mosql.gemspec +2 -1
- data/test/_lib.rb +3 -0
- data/test/functional/_lib.rb +1 -0
- data/test/functional/schema.rb +3 -1
- data/test/functional/sql.rb +4 -4
- data/test/functional/streamer.rb +118 -0
- data/test/functional/transform.rb +122 -0
- data/test/unit/lib/mosql/schema.rb +34 -2
- metadata +33 -55
- data/test/functional/functional.rb +0 -7
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 43cb64bb52ef41955332728b063f24470d73c134
|
4
|
+
data.tar.gz: 9ddb444a959cdce77da58255fa64f4704cc4f201
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7260354422d7fad24eb8a018f7bf26108e7d3a86e502c0910df72cea011ecb58ee0c26065f8562940b4cca9bff2db06d07016aa4a83bd1dd4c1c0ee04c132f7d
|
7
|
+
data.tar.gz: 7bc1b499a41c0285770f26559c117e864e072490466f9aaabb7bb33093d5202767bc42d98525b2cf5a4405f79d88c5e4704df5aad75df918ad1b97ad1d5f15b8
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mosql (0.
|
4
|
+
mosql (0.4.0)
|
5
5
|
bson_ext
|
6
6
|
json
|
7
7
|
log4r
|
8
8
|
mongo
|
9
|
-
mongoriver
|
9
|
+
mongoriver (= 0.4)
|
10
10
|
pg
|
11
11
|
rake
|
12
12
|
sequel
|
@@ -14,24 +14,24 @@ PATH
|
|
14
14
|
GEM
|
15
15
|
remote: https://rubygems.org/
|
16
16
|
specs:
|
17
|
-
bson (1.
|
18
|
-
bson_ext (1.
|
19
|
-
bson (~> 1.
|
17
|
+
bson (1.10.2)
|
18
|
+
bson_ext (1.10.2)
|
19
|
+
bson (~> 1.10.2)
|
20
20
|
json (1.8.1)
|
21
21
|
log4r (1.1.10)
|
22
22
|
metaclass (0.0.4)
|
23
23
|
minitest (3.0.0)
|
24
24
|
mocha (1.0.0)
|
25
25
|
metaclass (~> 0.0.1)
|
26
|
-
mongo (1.
|
27
|
-
bson (
|
28
|
-
mongoriver (0.
|
26
|
+
mongo (1.10.2)
|
27
|
+
bson (= 1.10.2)
|
28
|
+
mongoriver (0.4.0)
|
29
29
|
bson_ext
|
30
30
|
log4r
|
31
31
|
mongo (>= 1.7)
|
32
|
-
pg (0.
|
33
|
-
rake (10.3.
|
34
|
-
sequel (
|
32
|
+
pg (0.17.1)
|
33
|
+
rake (10.3.2)
|
34
|
+
sequel (4.14.0)
|
35
35
|
|
36
36
|
PLATFORMS
|
37
37
|
ruby
|
data/README.md
CHANGED
@@ -229,8 +229,16 @@ Patches and contributions are welcome! Please fork the project and
|
|
229
229
|
open a pull request on [github][github], or just report issues.
|
230
230
|
|
231
231
|
MoSQL includes a small but hopefully-growing test suite. It assumes a
|
232
|
-
running PostgreSQL and MongoDB instance on the local host
|
233
|
-
|
234
|
-
|
232
|
+
running PostgreSQL and MongoDB instance on the local host. To run the
|
233
|
+
test suite, first install all of MoSQL's dependencies:
|
234
|
+
```shell
|
235
|
+
bundle install
|
236
|
+
```
|
237
|
+
Then, run the tests:
|
238
|
+
```shell
|
239
|
+
rake test
|
240
|
+
```
|
241
|
+
You can also point the suite at a different target via environment
|
242
|
+
variables; See `test/functional/_lib.rb` for more information.
|
235
243
|
|
236
244
|
[github]: https://github.com/stripe/mosql
|
data/lib/mosql/schema.rb
CHANGED
@@ -70,6 +70,9 @@ module MoSQL
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
73
|
+
|
74
|
+
# Lurky way to force Sequel force all timestamps to use UTC.
|
75
|
+
Sequel.default_timezone = :utc
|
73
76
|
end
|
74
77
|
|
75
78
|
def create_schema(db, clobber=false)
|
@@ -77,6 +80,8 @@ module MoSQL
|
|
77
80
|
dbspec.each do |n, collection|
|
78
81
|
next unless n.is_a?(String)
|
79
82
|
meta = collection[:meta]
|
83
|
+
composite_key = meta[:composite_key]
|
84
|
+
keys = []
|
80
85
|
log.info("Creating table '#{meta[:table]}'...")
|
81
86
|
db.send(clobber ? :create_table! : :create_table?, meta[:table]) do
|
82
87
|
collection[:columns].each do |col|
|
@@ -86,10 +91,14 @@ module MoSQL
|
|
86
91
|
end
|
87
92
|
column col[:name], col[:type], opts
|
88
93
|
|
89
|
-
if col[:
|
90
|
-
|
94
|
+
if composite_key and composite_key.include?(col[:name])
|
95
|
+
keys << col[:name].to_sym
|
96
|
+
elsif not composite_key and col[:source].to_sym == :_id
|
97
|
+
keys << col[:name].to_sym
|
91
98
|
end
|
92
99
|
end
|
100
|
+
|
101
|
+
primary_key keys
|
93
102
|
if meta[:extra_props]
|
94
103
|
type =
|
95
104
|
if meta[:extra_props] == "JSON"
|
@@ -159,6 +168,23 @@ module MoSQL
|
|
159
168
|
end
|
160
169
|
end
|
161
170
|
|
171
|
+
def transform_primitive(v, type=nil)
|
172
|
+
case v
|
173
|
+
when BSON::ObjectId, Symbol
|
174
|
+
v.to_s
|
175
|
+
when BSON::Binary
|
176
|
+
if type.downcase == 'uuid'
|
177
|
+
v.to_s.unpack("H*").first
|
178
|
+
else
|
179
|
+
Sequel::SQL::Blob.new(v.to_s)
|
180
|
+
end
|
181
|
+
when BSON::DBRef
|
182
|
+
v.object_id.to_s
|
183
|
+
else
|
184
|
+
v
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
162
188
|
def transform(ns, obj, schema=nil)
|
163
189
|
schema ||= find_ns!(ns)
|
164
190
|
|
@@ -174,18 +200,17 @@ module MoSQL
|
|
174
200
|
else
|
175
201
|
v = fetch_and_delete_dotted(obj, source)
|
176
202
|
case v
|
177
|
-
when BSON::Binary, BSON::ObjectId, Symbol
|
178
|
-
v = v.to_s
|
179
|
-
when BSON::DBRef
|
180
|
-
v = v.object_id.to_s
|
181
203
|
when Hash
|
182
|
-
v = JSON.dump(v)
|
204
|
+
v = JSON.dump(Hash[v.map { |k,v| [k, transform_primitive(v)] }])
|
183
205
|
when Array
|
206
|
+
v = v.map { |it| transform_primitive(it) }
|
184
207
|
if col[:array_type]
|
185
208
|
v = Sequel.pg_array(v, col[:array_type])
|
186
209
|
else
|
187
210
|
v = JSON.dump(v)
|
188
211
|
end
|
212
|
+
else
|
213
|
+
v = transform_primitive(v, type)
|
189
214
|
end
|
190
215
|
end
|
191
216
|
row << v
|
@@ -268,6 +293,10 @@ module MoSQL
|
|
268
293
|
'f'
|
269
294
|
when Sequel::SQL::Function
|
270
295
|
nil
|
296
|
+
when DateTime, Time
|
297
|
+
val.strftime("%FT%T.%6N %z")
|
298
|
+
when Sequel::SQL::Blob
|
299
|
+
"\\\\x" + [val].pack("h*")
|
271
300
|
else
|
272
301
|
val.to_s.gsub(/([\\\t\n\r])/, '\\\\\\1')
|
273
302
|
end
|
@@ -290,7 +319,15 @@ module MoSQL
|
|
290
319
|
end
|
291
320
|
|
292
321
|
def primary_sql_key_for_ns(ns)
|
293
|
-
find_ns!(ns)
|
322
|
+
ns = find_ns!(ns)
|
323
|
+
keys = []
|
324
|
+
if ns[:meta][:composite_key]
|
325
|
+
keys = ns[:meta][:composite_key]
|
326
|
+
else
|
327
|
+
keys << ns[:columns].find {|c| c[:source] == '_id'}[:name]
|
328
|
+
end
|
329
|
+
|
330
|
+
return keys
|
294
331
|
end
|
295
332
|
end
|
296
333
|
end
|
data/lib/mosql/sql.rb
CHANGED
@@ -39,16 +39,24 @@ module MoSQL
|
|
39
39
|
upsert!(table_for_ns(ns), @schema.primary_sql_key_for_ns(ns), h)
|
40
40
|
end
|
41
41
|
|
42
|
-
# obj must contain an _id field. All other fields will be ignored.
|
43
42
|
def delete_ns(ns, obj)
|
44
|
-
|
43
|
+
primary_sql_keys = @schema.primary_sql_key_for_ns(ns)
|
45
44
|
h = transform_one_ns(ns, obj)
|
46
|
-
|
47
|
-
|
45
|
+
query = {}
|
46
|
+
primary_sql_keys.each do |key|
|
47
|
+
raise "No #{primary_sql_keys} found in transform of #{obj.inspect}" if h[key].nil?
|
48
|
+
query[key.to_sym] = h[key]
|
49
|
+
end
|
50
|
+
|
51
|
+
table_for_ns(ns).where(query).delete
|
48
52
|
end
|
49
53
|
|
50
|
-
def upsert!(table,
|
51
|
-
|
54
|
+
def upsert!(table, table_primary_keys, item)
|
55
|
+
query = {}
|
56
|
+
table_primary_keys.each do |key|
|
57
|
+
query[key.to_sym] = item[key]
|
58
|
+
end
|
59
|
+
rows = table.where(query).update(item)
|
52
60
|
if rows == 0
|
53
61
|
begin
|
54
62
|
table.insert(item)
|
@@ -69,6 +77,10 @@ module MoSQL
|
|
69
77
|
# how to get at this error code....
|
70
78
|
e.wrapped_exception.result.error_field(PG::Result::PG_DIAG_SQLSTATE) == "23505"
|
71
79
|
end
|
80
|
+
|
81
|
+
def self.duplicate_column_error?(e)
|
82
|
+
e.wrapped_exception.result.error_field(PG::Result::PG_DIAG_SQLSTATE) == "42701"
|
83
|
+
end
|
72
84
|
end
|
73
85
|
end
|
74
86
|
|
data/lib/mosql/streamer.rb
CHANGED
@@ -24,7 +24,7 @@ module MoSQL
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def import
|
27
|
-
if options[:reimport] || tailer.
|
27
|
+
if options[:reimport] || tailer.read_position.nil?
|
28
28
|
initial_import
|
29
29
|
end
|
30
30
|
end
|
@@ -90,14 +90,17 @@ module MoSQL
|
|
90
90
|
@schema.create_schema(@sql.db, !options[:no_drop_tables])
|
91
91
|
|
92
92
|
unless options[:skip_tail]
|
93
|
-
|
93
|
+
start_state = {
|
94
|
+
'time' => nil,
|
95
|
+
'position' => @tailer.most_recent_position
|
96
|
+
}
|
94
97
|
end
|
95
98
|
|
96
99
|
dbnames = []
|
97
100
|
|
98
|
-
if
|
99
|
-
log.info "Skipping DB scan and using db: #{dbname}"
|
100
|
-
dbnames = [ dbname ]
|
101
|
+
if options[:dbname]
|
102
|
+
log.info "Skipping DB scan and using db: #{options[:dbname]}"
|
103
|
+
dbnames = [ options[:dbname] ]
|
101
104
|
else
|
102
105
|
dbnames = @mongo.database_names
|
103
106
|
end
|
@@ -121,8 +124,7 @@ module MoSQL
|
|
121
124
|
end
|
122
125
|
end
|
123
126
|
|
124
|
-
|
125
|
-
tailer.write_timestamp(start_ts) unless options[:skip_tail]
|
127
|
+
tailer.save_state(start_state) unless options[:skip_tail]
|
126
128
|
end
|
127
129
|
|
128
130
|
def did_truncate; @did_truncate ||= {}; end
|
@@ -164,9 +166,11 @@ module MoSQL
|
|
164
166
|
end
|
165
167
|
|
166
168
|
def optail
|
167
|
-
|
168
|
-
|
169
|
-
|
169
|
+
tail_from = options[:tail_from]
|
170
|
+
if tail_from.is_a? Time
|
171
|
+
tail_from = tailer.most_recent_position(tail_from)
|
172
|
+
end
|
173
|
+
tailer.tail(:from => tail_from)
|
170
174
|
until @done
|
171
175
|
tailer.stream(1000) do |op|
|
172
176
|
handle_op(op)
|
@@ -174,16 +178,21 @@ module MoSQL
|
|
174
178
|
end
|
175
179
|
end
|
176
180
|
|
177
|
-
def sync_object(ns,
|
178
|
-
|
179
|
-
sqlid = @sql.transform_one_ns(ns, { '_id' => _id })[primary_sql_key]
|
180
|
-
obj = collection_for_ns(ns).find_one({:_id => _id})
|
181
|
+
def sync_object(ns, selector)
|
182
|
+
obj = collection_for_ns(ns).find_one(selector)
|
181
183
|
if obj
|
182
184
|
unsafe_handle_exceptions(ns, obj) do
|
183
185
|
@sql.upsert_ns(ns, obj)
|
184
186
|
end
|
185
187
|
else
|
186
|
-
@
|
188
|
+
primary_sql_keys = @schema.primary_sql_key_for_ns(ns)
|
189
|
+
schema = @schema.find_ns!(ns)
|
190
|
+
query = {}
|
191
|
+
primary_sql_keys.each do |key|
|
192
|
+
source = schema[:columns].find {|c| c[:name] == key }[:source]
|
193
|
+
query[key] = selector[source]
|
194
|
+
end
|
195
|
+
@sql.table_for_ns(ns).where(query).delete()
|
187
196
|
end
|
188
197
|
end
|
189
198
|
|
@@ -194,6 +203,15 @@ module MoSQL
|
|
194
203
|
return
|
195
204
|
end
|
196
205
|
|
206
|
+
# First, check if this was an operation performed via applyOps. If so, call handle_op with
|
207
|
+
# for each op that was applied.
|
208
|
+
# The oplog format of applyOps commands can be viewed here:
|
209
|
+
# https://groups.google.com/forum/#!topic/mongodb-user/dTf5VEJJWvY
|
210
|
+
if op['op'] == 'c' && (ops = op['o']['applyOps'])
|
211
|
+
ops.each { |op| handle_op(op) }
|
212
|
+
return
|
213
|
+
end
|
214
|
+
|
197
215
|
unless @schema.find_ns(op['ns'])
|
198
216
|
log.debug("Skipping op for unknown ns #{op['ns']}...")
|
199
217
|
return
|
@@ -218,15 +236,24 @@ module MoSQL
|
|
218
236
|
update = op['o']
|
219
237
|
if update.keys.any? { |k| k.start_with? '$' }
|
220
238
|
log.debug("resync #{ns}: #{selector['_id']} (update was: #{update.inspect})")
|
221
|
-
sync_object(ns, selector
|
239
|
+
sync_object(ns, selector)
|
222
240
|
else
|
223
|
-
log.debug("upsert #{ns}: _id=#{selector['_id']}")
|
224
241
|
|
225
242
|
# The update operation replaces the existing object, but
|
226
243
|
# preserves its _id field, so grab the _id off of the
|
227
244
|
# 'query' field -- it's not guaranteed to be present on the
|
228
245
|
# update.
|
229
|
-
|
246
|
+
primary_sql_keys = @schema.primary_sql_key_for_ns(ns)
|
247
|
+
schema = @schema.find_ns!(ns)
|
248
|
+
keys = {}
|
249
|
+
primary_sql_keys.each do |key|
|
250
|
+
source = schema[:columns].find {|c| c[:name] == key }[:source]
|
251
|
+
keys[key] = selector[source]
|
252
|
+
end
|
253
|
+
|
254
|
+
log.debug("upsert #{ns}: #{keys}")
|
255
|
+
|
256
|
+
update = keys.merge(update)
|
230
257
|
unsafe_handle_exceptions(ns, update) do
|
231
258
|
@sql.upsert_ns(ns, update)
|
232
259
|
end
|
data/lib/mosql/tailer.rb
CHANGED
@@ -1,11 +1,25 @@
|
|
1
1
|
module MoSQL
|
2
2
|
class Tailer < Mongoriver::AbstractPersistentTailer
|
3
3
|
def self.create_table(db, tablename)
|
4
|
-
db.
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
if !db.table_exists?(tablename)
|
5
|
+
db.create_table(tablename) do
|
6
|
+
column :service, 'TEXT'
|
7
|
+
column :timestamp, 'INTEGER'
|
8
|
+
column :position, 'BYTEA'
|
9
|
+
primary_key [:service]
|
10
|
+
end
|
11
|
+
else
|
12
|
+
# Try to do seamless upgrades from before-tokumx times
|
13
|
+
# It will raise an exception in this in most cases,
|
14
|
+
# but there isn't a nice way I found to check if column
|
15
|
+
# exists.
|
16
|
+
begin
|
17
|
+
db.add_column(tablename, :position, 'BYTEA')
|
18
|
+
rescue Sequel::DatabaseError => e
|
19
|
+
raise unless MoSQL::SQLAdapter.duplicate_column_error?(e)
|
20
|
+
end
|
8
21
|
end
|
22
|
+
|
9
23
|
db[tablename.to_sym]
|
10
24
|
end
|
11
25
|
|
@@ -15,25 +29,60 @@ module MoSQL
|
|
15
29
|
@service = opts[:service] || "mosql"
|
16
30
|
end
|
17
31
|
|
18
|
-
def
|
19
|
-
row = @table.where(:service => @service).
|
20
|
-
|
21
|
-
|
32
|
+
def read_state
|
33
|
+
row = @table.where(:service => @service).first
|
34
|
+
return nil unless row
|
35
|
+
# Again, try to do seamless upgrades -
|
36
|
+
# If latest operation before or at timestamp if no position
|
37
|
+
# exists, use timestamp in database to guess what it could be.
|
38
|
+
result = {}
|
39
|
+
result['time'] = Time.at(row.fetch(:timestamp))
|
40
|
+
if row[:position]
|
41
|
+
result['position'] = from_blob(row[:position])
|
22
42
|
else
|
23
|
-
|
43
|
+
log.warn("Trying to seamlessly update from old version!")
|
44
|
+
result['position'] = most_recent_position(result['time'])
|
45
|
+
save_state(result)
|
24
46
|
end
|
47
|
+
result
|
25
48
|
end
|
26
49
|
|
27
|
-
def
|
50
|
+
def write_state(state)
|
51
|
+
data = {
|
52
|
+
:service => @service,
|
53
|
+
:timestamp => state['time'].to_i,
|
54
|
+
:position => to_blob(state['position'])
|
55
|
+
}
|
56
|
+
|
28
57
|
unless @did_insert
|
29
58
|
begin
|
30
|
-
@table.insert(
|
59
|
+
@table.insert(data)
|
31
60
|
rescue Sequel::DatabaseError => e
|
32
61
|
raise unless MoSQL::SQLAdapter.duplicate_key_error?(e)
|
33
62
|
end
|
34
63
|
@did_insert = true
|
35
64
|
end
|
36
|
-
|
65
|
+
|
66
|
+
@table.where(:service => @service).update(data)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def to_blob(position)
|
71
|
+
case database_type
|
72
|
+
when :mongo
|
73
|
+
return Sequel::SQL::Blob.new(position.seconds.to_s)
|
74
|
+
when :toku
|
75
|
+
return Sequel::SQL::Blob.new(position.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def from_blob(blob)
|
80
|
+
case database_type
|
81
|
+
when :mongo
|
82
|
+
return BSON::Timestamp.new(blob.to_i, 0)
|
83
|
+
when :toku
|
84
|
+
return BSON::Binary.new(blob)
|
85
|
+
end
|
37
86
|
end
|
38
87
|
end
|
39
88
|
end
|
data/lib/mosql/version.rb
CHANGED
data/mosql.gemspec
CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.version = MoSQL::VERSION
|
18
18
|
|
19
19
|
%w[sequel pg mongo bson_ext rake log4r json
|
20
|
-
|
20
|
+
].each { |dep| gem.add_runtime_dependency(dep) }
|
21
|
+
gem.add_runtime_dependency "mongoriver", "0.4"
|
21
22
|
|
22
23
|
gem.add_development_dependency "minitest"
|
23
24
|
gem.add_development_dependency "mocha"
|
data/test/_lib.rb
CHANGED
data/test/functional/_lib.rb
CHANGED
data/test/functional/schema.rb
CHANGED
@@ -141,8 +141,10 @@ EOF
|
|
141
141
|
objects.map { |o| @specialmap.transform('db.collection', o) } )
|
142
142
|
after = @sequel.select(Sequel.function(:NOW)).first[:now]
|
143
143
|
rows = @sequel[:special].select.sort_by { |r| r[:_id] }
|
144
|
+
|
144
145
|
assert_instance_of(Time, rows[0][:mosql_updated])
|
145
|
-
|
146
|
+
assert_operator(rows[0][:mosql_updated], :>, before)
|
147
|
+
assert_operator(rows[0][:mosql_updated], :<, after)
|
146
148
|
end
|
147
149
|
end
|
148
150
|
end
|
data/test/functional/sql.rb
CHANGED
@@ -17,8 +17,8 @@ class MoSQL::Test::Functional::SQLTest < MoSQL::Test::Functional
|
|
17
17
|
|
18
18
|
describe 'upsert' do
|
19
19
|
it 'inserts new items' do
|
20
|
-
@adapter.upsert!(@table, '_id', {'_id' => 0, 'color' => 'red', 'quantity' => 10, 'numbers' => Sequel.pg_array([1, 2, 3], :integer)})
|
21
|
-
@adapter.upsert!(@table, '_id', {'_id' => 1, 'color' => 'blue', 'quantity' => 5, 'numbers' => Sequel.pg_array([], :integer)})
|
20
|
+
@adapter.upsert!(@table, ['_id'], {'_id' => 0, 'color' => 'red', 'quantity' => 10, 'numbers' => Sequel.pg_array([1, 2, 3], :integer)})
|
21
|
+
@adapter.upsert!(@table, ['_id'], {'_id' => 1, 'color' => 'blue', 'quantity' => 5, 'numbers' => Sequel.pg_array([], :integer)})
|
22
22
|
assert_equal(2, @table.count)
|
23
23
|
assert_equal('red', @table[:_id => 0][:color])
|
24
24
|
assert_equal(10, @table[:_id => 0][:quantity])
|
@@ -29,11 +29,11 @@ class MoSQL::Test::Functional::SQLTest < MoSQL::Test::Functional
|
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'updates items' do
|
32
|
-
@adapter.upsert!(@table, '_id', {'_id' => 0, 'color' => 'red', 'quantity' => 10})
|
32
|
+
@adapter.upsert!(@table, ['_id'], {'_id' => 0, 'color' => 'red', 'quantity' => 10})
|
33
33
|
assert_equal(1, @table.count)
|
34
34
|
assert_equal('red', @table[:_id => 0][:color])
|
35
35
|
|
36
|
-
@adapter.upsert!(@table, '_id', {'_id' => 0, 'color' => 'blue', 'quantity' => 5})
|
36
|
+
@adapter.upsert!(@table, ['_id'], {'_id' => 0, 'color' => 'blue', 'quantity' => 5})
|
37
37
|
assert_equal(1, @table.count)
|
38
38
|
assert_equal('blue', @table[:_id => 0][:color])
|
39
39
|
end
|
data/test/functional/streamer.rb
CHANGED
@@ -53,6 +53,22 @@ filter_test:
|
|
53
53
|
:columns:
|
54
54
|
- _id: TEXT
|
55
55
|
- var: INTEGER
|
56
|
+
|
57
|
+
composite_key_test:
|
58
|
+
collection:
|
59
|
+
:meta:
|
60
|
+
:table: composite_table
|
61
|
+
:composite_key:
|
62
|
+
- store
|
63
|
+
- time
|
64
|
+
:columns:
|
65
|
+
- store:
|
66
|
+
:source: _id.s
|
67
|
+
:type: TEXT
|
68
|
+
- time:
|
69
|
+
:source: _id.t
|
70
|
+
:type: TIMESTAMP
|
71
|
+
- var: TEXT
|
56
72
|
EOF
|
57
73
|
|
58
74
|
before do
|
@@ -61,6 +77,7 @@ EOF
|
|
61
77
|
|
62
78
|
@sequel.drop_table?(:sqltable)
|
63
79
|
@sequel.drop_table?(:sqltable2)
|
80
|
+
@sequel.drop_table?(:composite_table)
|
64
81
|
@map.create_schema(@sequel)
|
65
82
|
|
66
83
|
@streamer = build_streamer
|
@@ -78,6 +95,22 @@ EOF
|
|
78
95
|
assert_equal(27, sequel[:sqltable].where(:_id => o['_id'].to_s).select.first[:var])
|
79
96
|
end
|
80
97
|
|
98
|
+
it 'applies ops performed via applyOps' do
|
99
|
+
o = { '_id' => BSON::ObjectId.new, 'var' => 17 }
|
100
|
+
@adapter.upsert_ns('mosql_test.collection', o)
|
101
|
+
|
102
|
+
op = { 'ns' => 'mosql_test.collection',
|
103
|
+
'op' => 'u',
|
104
|
+
'o2' => { '_id' => o['_id'] },
|
105
|
+
'o' => { 'var' => 27 }
|
106
|
+
}
|
107
|
+
@streamer.handle_op({ 'op' => 'c',
|
108
|
+
'ns' => 'mosql_test.$cmd',
|
109
|
+
'o' => { 'applyOps' => [op] }
|
110
|
+
})
|
111
|
+
assert_equal(27, sequel[:sqltable].where(:_id => o['_id'].to_s).select.first[:var])
|
112
|
+
end
|
113
|
+
|
81
114
|
it 'handle "d" ops with BSON::ObjectIds' do
|
82
115
|
o = { '_id' => BSON::ObjectId.new, 'var' => 17 }
|
83
116
|
@adapter.upsert_ns('mosql_test.collection', o)
|
@@ -151,6 +184,39 @@ EOF
|
|
151
184
|
assert_equal(data[1], record)
|
152
185
|
end
|
153
186
|
|
187
|
+
it 'handles "u" ops with a compsite key' do
|
188
|
+
date = Time.utc(2014, 7, 1)
|
189
|
+
o = {'_id' => {'s' => 'asdf', 't' => date}, 'var' => 'data'}
|
190
|
+
collection = mongo["composite_key_test"]["collection"]
|
191
|
+
collection.drop
|
192
|
+
collection.insert(o)
|
193
|
+
|
194
|
+
@streamer.options[:skip_tail] = true
|
195
|
+
@streamer.initial_import
|
196
|
+
|
197
|
+
collection.update({ '_id' => { 's' => 'asdf', 't' => date}}, { '$set' => { 'var' => 'new_data'}})
|
198
|
+
@streamer.handle_op({'ns' => 'composite_key_test.collection',
|
199
|
+
'op' => 'u',
|
200
|
+
'o2' => { '_id' => { 's' => 'asdf', 't' => date}},
|
201
|
+
'o' => { '$set' => { 'var' => 'new_data'}}
|
202
|
+
})
|
203
|
+
|
204
|
+
assert_equal(0, @sequel[:composite_table].where(:var => "data").count)
|
205
|
+
assert_equal(1, @sequel[:composite_table].where(:var => "new_data").count)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'handles composite keys' do
|
209
|
+
o = {'_id' => {'s' => 'asdf', 't' => Time.new}, 'var' => 'data'}
|
210
|
+
collection = mongo["composite_key_test"]["collection"]
|
211
|
+
collection.drop
|
212
|
+
collection.insert(o)
|
213
|
+
|
214
|
+
@streamer.options[:skip_tail] = true
|
215
|
+
@streamer.initial_import
|
216
|
+
|
217
|
+
assert_equal(1, @sequel[:composite_table].count)
|
218
|
+
end
|
219
|
+
|
154
220
|
describe '.bulk_upsert' do
|
155
221
|
it 'inserts multiple rows' do
|
156
222
|
objs = [
|
@@ -252,4 +318,56 @@ EOF
|
|
252
318
|
assert_equal(ids.map(&:to_s).sort, sqlobjs.map { |o| o[:_id] }.sort)
|
253
319
|
end
|
254
320
|
end
|
321
|
+
describe 'timestamps' do
|
322
|
+
TIMESTAMP_MAP = <<EOF
|
323
|
+
---
|
324
|
+
db:
|
325
|
+
has_timestamp:
|
326
|
+
:meta:
|
327
|
+
:table: has_timestamp
|
328
|
+
:columns:
|
329
|
+
- _id: TEXT
|
330
|
+
- ts: timestamp
|
331
|
+
EOF
|
332
|
+
|
333
|
+
before do
|
334
|
+
@map = MoSQL::Schema.new(YAML.load(TIMESTAMP_MAP))
|
335
|
+
@adapter = MoSQL::SQLAdapter.new(@map, sql_test_uri)
|
336
|
+
|
337
|
+
mongo['db']['has_timestamp'].drop
|
338
|
+
@sequel.drop_table?(:has_timestamp)
|
339
|
+
@map.create_schema(@sequel)
|
340
|
+
|
341
|
+
@streamer = build_streamer
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'preserves milliseconds on import' do
|
345
|
+
ts = Time.utc(2014, 8, 7, 6, 54, 32, 123000)
|
346
|
+
mongo['db']['has_timestamp'].insert({ts: ts})
|
347
|
+
@streamer.options[:skip_tail] = true
|
348
|
+
@streamer.initial_import
|
349
|
+
|
350
|
+
row = @sequel[:has_timestamp].select.to_a
|
351
|
+
assert_equal(1, row.length)
|
352
|
+
assert_equal(ts.to_i, row.first[:ts].to_i)
|
353
|
+
assert_equal(ts.tv_usec, row.first[:ts].tv_usec)
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'preserves milliseconds on tailing' do
|
357
|
+
ts = Time.utc(2006,01,02, 15,04,05,678000)
|
358
|
+
id = mongo['db']['has_timestamp'].insert({ts: ts})
|
359
|
+
@streamer.handle_op(
|
360
|
+
{
|
361
|
+
"ts" => {"t" => 1408647630, "i" => 4},
|
362
|
+
"h" => -965650193548512059,
|
363
|
+
"v" => 2,
|
364
|
+
"op" => "i",
|
365
|
+
"ns" => "db.has_timestamp",
|
366
|
+
"o" => mongo['db']['has_timestamp'].find_one({_id: id})
|
367
|
+
})
|
368
|
+
got = @sequel[:has_timestamp].where(:_id => id.to_s).select.first[:ts]
|
369
|
+
assert_equal(ts.to_i, got.to_i)
|
370
|
+
assert_equal(ts.tv_usec, got.tv_usec)
|
371
|
+
end
|
372
|
+
end
|
255
373
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '_lib.rb')
|
2
|
+
require 'mosql/cli'
|
3
|
+
|
4
|
+
class MoSQL::Test::Functional::TransformTest < MoSQL::Test::Functional
|
5
|
+
TESTCASES = [
|
6
|
+
[
|
7
|
+
BSON::ObjectId.from_string('5405fae77c584947fc000001'),
|
8
|
+
'TEXT',
|
9
|
+
'5405fae77c584947fc000001'
|
10
|
+
],
|
11
|
+
[
|
12
|
+
Time.utc(2006,01,02, 15,04,05,678000),
|
13
|
+
'TIMESTAMP',
|
14
|
+
Time.utc(2006,01,02, 15,04,05,678000)
|
15
|
+
],
|
16
|
+
[
|
17
|
+
:stringy,
|
18
|
+
'TEXT',
|
19
|
+
'stringy'
|
20
|
+
],
|
21
|
+
[
|
22
|
+
BSON::DBRef.new('db.otherns', BSON::ObjectId.from_string('5405fae77c584947fc000001')),
|
23
|
+
'TEXT',
|
24
|
+
'5405fae77c584947fc000001'
|
25
|
+
],
|
26
|
+
[
|
27
|
+
[
|
28
|
+
BSON::ObjectId.from_string('5405fae77c584947fc000001'),
|
29
|
+
BSON::ObjectId.from_string('5405fae77c584947fc000002')
|
30
|
+
],
|
31
|
+
'TEXT ARRAY',
|
32
|
+
['5405fae77c584947fc000001', '5405fae77c584947fc000002']
|
33
|
+
],
|
34
|
+
[
|
35
|
+
[
|
36
|
+
BSON::ObjectId.from_string('5405fae77c584947fc000001'),
|
37
|
+
BSON::ObjectId.from_string('5405fae77c584947fc000002')
|
38
|
+
],
|
39
|
+
'TEXT',
|
40
|
+
['5405fae77c584947fc000001', '5405fae77c584947fc000002'].to_json,
|
41
|
+
],
|
42
|
+
[
|
43
|
+
[
|
44
|
+
BSON::DBRef.new('db.otherns', BSON::ObjectId.from_string('5405fae77c584947fc000001')),
|
45
|
+
BSON::DBRef.new('db.otherns', BSON::ObjectId.from_string('5405fae77c584947fc000002'))
|
46
|
+
],
|
47
|
+
'TEXT ARRAY',
|
48
|
+
['5405fae77c584947fc000001', '5405fae77c584947fc000002']
|
49
|
+
],
|
50
|
+
[
|
51
|
+
[
|
52
|
+
BSON::DBRef.new('db.otherns', BSON::ObjectId.from_string('5405fae77c584947fc000001')),
|
53
|
+
BSON::DBRef.new('db.otherns', BSON::ObjectId.from_string('5405fae77c584947fc000002'))
|
54
|
+
],
|
55
|
+
'TEXT',
|
56
|
+
['5405fae77c584947fc000001', '5405fae77c584947fc000002'].to_json
|
57
|
+
],
|
58
|
+
[
|
59
|
+
BSON::Binary.new(["2d931510d99f494a8c6787feb05e1594"].pack("H*"),
|
60
|
+
BSON::Binary::SUBTYPE_UUID),
|
61
|
+
'UUID',
|
62
|
+
"2d931510-d99f-494a-8c67-87feb05e1594"
|
63
|
+
],
|
64
|
+
[
|
65
|
+
BSON::Binary.new(["deadbeefcafebabe"].pack("H*"),
|
66
|
+
BSON::Binary::SUBTYPE_SIMPLE),
|
67
|
+
'BYTEA',
|
68
|
+
["deadbeefcafebabe"].pack("H*")
|
69
|
+
]
|
70
|
+
]
|
71
|
+
|
72
|
+
TESTCASES.each do |mongo, typ, sql|
|
73
|
+
it "Can transform a #{mongo.class} into a #{typ}" do
|
74
|
+
map = {'test' => {'test_transform' =>
|
75
|
+
{
|
76
|
+
meta: {
|
77
|
+
table: 'test_transform'
|
78
|
+
},
|
79
|
+
columns: [
|
80
|
+
{'_id' => 'TEXT'},
|
81
|
+
{'value' => typ},
|
82
|
+
]
|
83
|
+
}}}
|
84
|
+
schema = MoSQL::Schema.new(map)
|
85
|
+
adapter = MoSQL::SQLAdapter.new(schema, sql_test_uri)
|
86
|
+
@sequel.drop_table?(:test_transform)
|
87
|
+
collection = @mongo['test']['test_transform']
|
88
|
+
collection.drop
|
89
|
+
|
90
|
+
schema.create_schema(@sequel)
|
91
|
+
streamer = MoSQL::Streamer.new(:mongo => self.mongo,
|
92
|
+
:tailer => nil,
|
93
|
+
:options => {skip_tail: true},
|
94
|
+
:sql => adapter,
|
95
|
+
:schema => schema)
|
96
|
+
|
97
|
+
# Test initial import
|
98
|
+
id = 'imported'
|
99
|
+
collection.insert({_id: id, value: mongo})
|
100
|
+
streamer.initial_import
|
101
|
+
|
102
|
+
got = @sequel[:test_transform].where(_id: id).to_a
|
103
|
+
assert_equal(sql, got.first[:value], "was able to transform a #{typ} field on initial import")
|
104
|
+
|
105
|
+
# Test streaming an insert
|
106
|
+
id = 'inserted'
|
107
|
+
collection.insert({_id: id, value: mongo})
|
108
|
+
streamer.handle_op(
|
109
|
+
{
|
110
|
+
"ts" => {"t" => 1408647630, "i" => 4},
|
111
|
+
"h" => -965650193548512059,
|
112
|
+
"v" => 2,
|
113
|
+
"op" => "i",
|
114
|
+
"ns" => "test.test_transform",
|
115
|
+
"o" => collection.find_one(_id: id)
|
116
|
+
})
|
117
|
+
|
118
|
+
got = @sequel[:test_transform].where(_id: id).to_a
|
119
|
+
assert_equal(sql, got.first[:value], "was able to transform a #{typ} field while streaming")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -39,6 +39,23 @@ db:
|
|
39
39
|
- arry: TEXT
|
40
40
|
:meta:
|
41
41
|
:table: sqltable5
|
42
|
+
with_composite_key:
|
43
|
+
:meta:
|
44
|
+
:table: sqltable6
|
45
|
+
:composite_key:
|
46
|
+
- store
|
47
|
+
- time
|
48
|
+
:columns:
|
49
|
+
- store:
|
50
|
+
:source: _id.s
|
51
|
+
:type: TEXT
|
52
|
+
- time:
|
53
|
+
:source: id.t
|
54
|
+
:type: TEXT
|
55
|
+
- var:
|
56
|
+
:source: var
|
57
|
+
:type: TEXT
|
58
|
+
|
42
59
|
EOF
|
43
60
|
|
44
61
|
before do
|
@@ -87,8 +104,8 @@ EOF
|
|
87
104
|
end
|
88
105
|
|
89
106
|
it 'Can find the primary key of the SQL table' do
|
90
|
-
assert_equal('id', @map.primary_sql_key_for_ns('db.collection'))
|
91
|
-
assert_equal('_id', @map.primary_sql_key_for_ns('db.old_conf_syntax'))
|
107
|
+
assert_equal(['id'], @map.primary_sql_key_for_ns('db.collection'))
|
108
|
+
assert_equal(['_id'], @map.primary_sql_key_for_ns('db.old_conf_syntax'))
|
92
109
|
end
|
93
110
|
|
94
111
|
it 'can create a SQL schema' do
|
@@ -98,6 +115,7 @@ EOF
|
|
98
115
|
db.expects(:create_table?).with('sqltable3')
|
99
116
|
db.expects(:create_table?).with('sqltable4')
|
100
117
|
db.expects(:create_table?).with('sqltable5')
|
118
|
+
db.expects(:create_table?).with('sqltable6')
|
101
119
|
|
102
120
|
@map.create_schema(db)
|
103
121
|
end
|
@@ -127,6 +145,11 @@ EOF
|
|
127
145
|
stub_5.expects(:column).with('_id', 'TEXT', {})
|
128
146
|
stub_5.expects(:column).with('arry', 'TEXT', {})
|
129
147
|
stub_5.expects(:primary_key).with([:_id])
|
148
|
+
stub_6 = stub('table 6')
|
149
|
+
stub_6.expects(:column).with('store', 'TEXT', {})
|
150
|
+
stub_6.expects(:column).with('time', 'TEXT', {})
|
151
|
+
stub_6.expects(:column).with('var', 'TEXT', {})
|
152
|
+
stub_6.expects(:primary_key).with([:store, :time])
|
130
153
|
(class << db; self; end).send(:define_method, :create_table?) do |tbl, &blk|
|
131
154
|
case tbl
|
132
155
|
when "sqltable"
|
@@ -139,6 +162,8 @@ EOF
|
|
139
162
|
o = stub_4
|
140
163
|
when "sqltable5"
|
141
164
|
o = stub_5
|
165
|
+
when "sqltable6"
|
166
|
+
o = stub_6
|
142
167
|
else
|
143
168
|
assert(false, "Tried to create an unexpected table: #{tbl}")
|
144
169
|
end
|
@@ -177,6 +202,13 @@ EOF
|
|
177
202
|
assert_equal(["row 1", nil, oid.to_s, nil], out)
|
178
203
|
end
|
179
204
|
|
205
|
+
it 'converts DBRef to object id in arrays' do
|
206
|
+
oid = [ BSON::ObjectId.new, BSON::ObjectId.new]
|
207
|
+
o = {'_id' => "row 1", "str" => [ BSON::DBRef.new('db.otherns', oid[0]), BSON::DBRef.new('db.otherns', oid[1]) ] }
|
208
|
+
out = @map.transform('db.collection', o)
|
209
|
+
assert_equal(["row 1", nil, JSON.dump(oid.map! {|o| o.to_s}), nil ], out)
|
210
|
+
end
|
211
|
+
|
180
212
|
it 'changes NaN to null in extra_props' do
|
181
213
|
out = @map.transform('db.with_extra_props', {'_id' => 7, 'nancy' => 0.0/0.0})
|
182
214
|
extra = JSON.parse(out[1])
|
metadata
CHANGED
@@ -1,174 +1,153 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mosql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nelson Elhage
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-
|
11
|
+
date: 2014-10-01 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: sequel
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: pg
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ">="
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: mongo
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - ">="
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :runtime
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - ">="
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: bson_ext
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - ">="
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '0'
|
70
62
|
type: :runtime
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - ">="
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '0'
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: rake
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
|
-
- -
|
73
|
+
- - ">="
|
84
74
|
- !ruby/object:Gem::Version
|
85
75
|
version: '0'
|
86
76
|
type: :runtime
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
|
-
- -
|
80
|
+
- - ">="
|
92
81
|
- !ruby/object:Gem::Version
|
93
82
|
version: '0'
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: log4r
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
|
-
- -
|
87
|
+
- - ">="
|
100
88
|
- !ruby/object:Gem::Version
|
101
89
|
version: '0'
|
102
90
|
type: :runtime
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
|
-
- -
|
94
|
+
- - ">="
|
108
95
|
- !ruby/object:Gem::Version
|
109
96
|
version: '0'
|
110
97
|
- !ruby/object:Gem::Dependency
|
111
98
|
name: json
|
112
99
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
100
|
requirements:
|
115
|
-
- -
|
101
|
+
- - ">="
|
116
102
|
- !ruby/object:Gem::Version
|
117
103
|
version: '0'
|
118
104
|
type: :runtime
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
107
|
requirements:
|
123
|
-
- -
|
108
|
+
- - ">="
|
124
109
|
- !ruby/object:Gem::Version
|
125
110
|
version: '0'
|
126
111
|
- !ruby/object:Gem::Dependency
|
127
112
|
name: mongoriver
|
128
113
|
requirement: !ruby/object:Gem::Requirement
|
129
|
-
none: false
|
130
114
|
requirements:
|
131
|
-
- -
|
115
|
+
- - '='
|
132
116
|
- !ruby/object:Gem::Version
|
133
|
-
version: '0'
|
117
|
+
version: '0.4'
|
134
118
|
type: :runtime
|
135
119
|
prerelease: false
|
136
120
|
version_requirements: !ruby/object:Gem::Requirement
|
137
|
-
none: false
|
138
121
|
requirements:
|
139
|
-
- -
|
122
|
+
- - '='
|
140
123
|
- !ruby/object:Gem::Version
|
141
|
-
version: '0'
|
124
|
+
version: '0.4'
|
142
125
|
- !ruby/object:Gem::Dependency
|
143
126
|
name: minitest
|
144
127
|
requirement: !ruby/object:Gem::Requirement
|
145
|
-
none: false
|
146
128
|
requirements:
|
147
|
-
- -
|
129
|
+
- - ">="
|
148
130
|
- !ruby/object:Gem::Version
|
149
131
|
version: '0'
|
150
132
|
type: :development
|
151
133
|
prerelease: false
|
152
134
|
version_requirements: !ruby/object:Gem::Requirement
|
153
|
-
none: false
|
154
135
|
requirements:
|
155
|
-
- -
|
136
|
+
- - ">="
|
156
137
|
- !ruby/object:Gem::Version
|
157
138
|
version: '0'
|
158
139
|
- !ruby/object:Gem::Dependency
|
159
140
|
name: mocha
|
160
141
|
requirement: !ruby/object:Gem::Requirement
|
161
|
-
none: false
|
162
142
|
requirements:
|
163
|
-
- -
|
143
|
+
- - ">="
|
164
144
|
- !ruby/object:Gem::Version
|
165
145
|
version: '0'
|
166
146
|
type: :development
|
167
147
|
prerelease: false
|
168
148
|
version_requirements: !ruby/object:Gem::Requirement
|
169
|
-
none: false
|
170
149
|
requirements:
|
171
|
-
- -
|
150
|
+
- - ">="
|
172
151
|
- !ruby/object:Gem::Version
|
173
152
|
version: '0'
|
174
153
|
description: A library for streaming MongoDB to SQL
|
@@ -179,8 +158,8 @@ executables:
|
|
179
158
|
extensions: []
|
180
159
|
extra_rdoc_files: []
|
181
160
|
files:
|
182
|
-
- .gitignore
|
183
|
-
- .travis.yml
|
161
|
+
- ".gitignore"
|
162
|
+
- ".travis.yml"
|
184
163
|
- Gemfile
|
185
164
|
- Gemfile.lock
|
186
165
|
- LICENSE
|
@@ -198,41 +177,40 @@ files:
|
|
198
177
|
- mosql.gemspec
|
199
178
|
- test/_lib.rb
|
200
179
|
- test/functional/_lib.rb
|
201
|
-
- test/functional/functional.rb
|
202
180
|
- test/functional/schema.rb
|
203
181
|
- test/functional/sql.rb
|
204
182
|
- test/functional/streamer.rb
|
183
|
+
- test/functional/transform.rb
|
205
184
|
- test/unit/lib/mosql/schema.rb
|
206
185
|
homepage: https://github.com/stripe/mosql
|
207
186
|
licenses: []
|
187
|
+
metadata: {}
|
208
188
|
post_install_message:
|
209
189
|
rdoc_options: []
|
210
190
|
require_paths:
|
211
191
|
- lib
|
212
192
|
required_ruby_version: !ruby/object:Gem::Requirement
|
213
|
-
none: false
|
214
193
|
requirements:
|
215
|
-
- -
|
194
|
+
- - ">="
|
216
195
|
- !ruby/object:Gem::Version
|
217
196
|
version: '0'
|
218
197
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
219
|
-
none: false
|
220
198
|
requirements:
|
221
|
-
- -
|
199
|
+
- - ">="
|
222
200
|
- !ruby/object:Gem::Version
|
223
201
|
version: '0'
|
224
202
|
requirements: []
|
225
203
|
rubyforge_project:
|
226
|
-
rubygems_version:
|
204
|
+
rubygems_version: 2.2.2
|
227
205
|
signing_key:
|
228
|
-
specification_version:
|
206
|
+
specification_version: 4
|
229
207
|
summary: MongoDB -> SQL streaming bridge
|
230
208
|
test_files:
|
231
209
|
- test/_lib.rb
|
232
210
|
- test/functional/_lib.rb
|
233
|
-
- test/functional/functional.rb
|
234
211
|
- test/functional/schema.rb
|
235
212
|
- test/functional/sql.rb
|
236
213
|
- test/functional/streamer.rb
|
214
|
+
- test/functional/transform.rb
|
237
215
|
- test/unit/lib/mosql/schema.rb
|
238
216
|
has_rdoc:
|