mosql 0.3.2 → 0.4.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.
- 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:
|