From 4ff2c51ab533749d1e039c24cacdb7e7e239128c Mon Sep 17 00:00:00 2001 From: Steve McHugh Date: Thu, 6 Feb 2014 17:55:20 -0500 Subject: [PATCH 1/4] [#64831866] Adding the ability to use MySQL with MoSQL - Fix issues with primary key length - Added checks for adapter type (postgres, mysql2, ...) when code paths diverge - Added mysql-specific and generic non-postgres initial load scripts - Added mysql-specific and (untested) generic non-postgres check for duplicate key error. Used to check if the oplog has changed since the last run --- README.md | 3 ++- bin/mosql | Bin 63 -> 21504 bytes lib/mosql/cli.rb | 2 +- lib/mosql/schema.rb | 59 ++++++++++++++++++++++++++++++++++--------- lib/mosql/sql.rb | 26 +++++++++++++------ lib/mosql/tailer.rb | 11 +++++--- lib/mosql/version.rb | 2 +- 7 files changed, 77 insertions(+), 26 deletions(-) mode change 100755 => 100644 bin/mosql diff --git a/README.md b/README.md index 11f7310..05e4185 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ types. An example collection map might be: :columns: - id: :source: _id - :type: TEXT + :type: TEXT (Must be VARCHAR(255) for MySQL) - author_name: :source: author.name :type: TEXT @@ -117,6 +117,7 @@ Once you have a collection map. MoSQL usage is easy. The basic form is: mosql [-c collections.yml] [--sql postgres://sql-server/sql-db] [--mongo mongodb://mongo-uri] + mosql [-c collections.yml] [--sql mysql2://username:password@sql-server/sql-db] [--mongo mongodb://mongo-uri] By default, `mosql` connects to both PostgreSQL and MongoDB instances running on default ports on localhost without authentication. You can diff --git a/bin/mosql b/bin/mosql old mode 100755 new mode 100644 index 757273d14bb926f38598994f436a0e4f3ab1bb9d..e39f97b415be27546256b63937266f16ba2ce4ce GIT binary patch literal 21504 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 z zyq~;qZO;^@mgP=%-8ZG!*jPr-oy*>Q9RI(95wO^8sx0}3(~m*qN_8F z<66FcG^gxxrvtsE_c4`#Firyv0+R*+&b6DpJ%CT$Nr2&&Lfc(&uBeege=*bB9#yqow`YzYzUQb{w;f zK21xSaPn!mRv?V$(cf7Yry%E#I-nk^rs%36LlXQWiBeTOc8HJ^vZW zOH`NobyuvoJmzuL$(;0*4eN0)d77SPl6~evj|VgwC`O&~^U9@pAAr7oJS6^f zNxlM2K@iitsHNUNhTdPt-m`xZbJM00QHXruRb`8K1mvQ{+T{&pfC&z7l zLYT*cY`}{ht+_!8? zbr3%)_OCFQzGuz$MCL}<&t~BKMJsTpMqv@x|w}oCpBe^lIj2F5EoIi$X7{O=i z=9R6zjKeVhdClzEn#JC)ub1cV@pS`W?79`?8m$`CbIzh+nk_c&G#QY^Na|WPLg&g8 zM&8NuP&}q(Bm{p&cv;?Y8*0e2x*iG1hzM+OU*_)@0(nt$LOinr7XMlkad#semFH{D zEcZoToS{#^kI3X0%^0-1?N2deiJb4sYP_Z!c^?}M_kvt&Io(W>xyPI5%`GS(fJv6W zlD3YJK)6+KU@x5uzZ5*SsSckH(u z7eb&W;LH*S2Q=~%qXQ#DUq7e3Ok~p6$KTqc3+>K94~YgqS&VC*cN#^p-GObUT2(6r zF-dX=Q5fxTv0fPEqEO5KGf-YhOL&9{j${doVU0*Fmd$1fSMkYIIeL3oTru+rH8#EY7m9o} z?au4ybv@g-kpRjHfg$}LG1c6{CAFpfg7ktCt7=)X18=hm=`I-ZS{ZU>e8i> ztjBavsi=HUcb!MK4^!pO?BWpa*eSH^lC1n)uVrg0r7DJcWnE&~vpymEhh7xwswoo- zD04q$Mjj|!yM<*bIcPQ+X*L+K_8DmZm6sxKl-OK~w9_PV#O-ts?vqevq);ZM=Lmw@ zdUJZMo7aX+@!=Okvai=Isn!UA%GVuz%D3nkA>(RXsL-fN6N~Z=fGHPydG5Ah z#vdFJ@VDKg6X$RKng#iywylLmgnYR@oHhBP5l^ob><(^heY>!9M*~~kRxO~2%OLO5 zO9yZ5s75s`qH;_fp|riK!+vQ1lQ0Ku*+Zg#sbfj0W?KgFcQ*>~ApL2Uc%b6jRTF&= zTm#a{D5VeMp$e~&*XKDrQr&LOw6Bfh!RRpPr)UFaFigzWZSYVP4*QX!=JCbEhzWyR z@5fKQp<$F?YkBY+*nH>`Xoh`UUj=P9vR!>odbtGmE+h?@+!akeYlY zR88wHn~`X2Rv&ZTp8N9tD=aHy-6>yH-+I1vKB(6(@!gxBaHjmS{1Txl_GU+06&|ST{2aZtkf zYUkuz9HIbsMM7CoLyK-@c_VilFQIwdXdF;WpqM+#y)Y?Z1|l zk$AuFiM5fQkH$b#z`o!+CJ&(bsY|Z=iGn_#q~SWedE$m;OeMNhVLrK*B+x;jFKi&| zhu$@@(+5%~ov`PzRg2g641wE1gRMqXCMPGkz)nOx)a}}2W?VJ>P|Df1-g1|vFW!*c z1Vfy~7Ch_bBjy?EiYIK8cIy0Ht>B9Eacg1phG#2>i^EPN%OcrUVHMtVh~y*ipF7ZN}P`mZO#BFiRVn$tgPeZ&wWN zbMCfHRPz}}%&fT^eMa1(X70)jl7GPvKrPKtlu@==PaxRa=To+p5d0GW-Wyj zr%sNvP{QwTTv%42c)iclO0Ve%DeLSE{tJYwrY>)3UxsF*TKh3O!M(t=hY#hxWp;Fj zOFuTg<;3{NF1O|HQnB`(<^uRuUUvc~-bQ1}F_U)hbMDjO?s3@eB&S97VT>Al!Iz_yPp5`Mdxm^I5OyD z9tlW*9m9*aaA5R5_0}UvtV(GO>O!U>bq3tXD|2+Yfu@p2tR09b0hmb<2~LS3*?V|V zsjy@e=9xj2&<<+JU_vO0Na*o&`cd#%1=;j93A72w4z0Y9MXQX?zwh$`2hGjD8OH*>i+HD1F#? z2+eb3qc*}pY2<@#d=VZC*620^nRbg={fJY*&6S!^ENnmvw{N(OfapxBA9|QuDSb6) zZEao+cZ=I)6#AJ?91~3>Bg?g-dZ#jbZl`vP7=t10?6FqL)o07LM8^uKrA;Unmv4*S zZb+#msH2c~Yrou2uD5hOE2=Rh!uj*uJk+dVt=ZSh*M3|19eMM zcIft(BNG_+K+NApVCv`HW1d$0zp`t@er*pG*gcJ97jwURbIwE2jXUo-jD6znx^4u` z>)M7tfBucEKi=1}9uXd@EAqUf?_L$I8_#KKi`%8?7F5pSp%2N*Kce(ZaUs|sUb$0W z#+a}qTPTG$$1Scx^Y<%(r0yg$c?e!m7c9fdZ)PWuzvgExSHPOvW$DTC|$Tqem>&QIrq&52uvxA_wNRi(ifeYFI0zsBLDt5tsv*&^4_W_O#j%e=yLVJPkZ2%zk7La>}1K<=y za#}mwXAC@eFXhN`{kQXtgr)noa)&8E?k76~nq-;PSb-`3v*EL^BXhyXW%rG(aBqPy zcQYa@(32Bul?0)}fFqJmIe1+k7S5@jt}f*;P-8ZHM!ShB*ME&J`y4!*?OwK+dTG6P zv}nAS3*1^ot1=G)Na+o5NuW9{Vyt6ot&kQ>g=IK*XGX$(FHYNZ#8Z5gC(FWJyRfUNM$%#h9|zWNC;H zvXd8MyWOAeIroSA+wI(YPJhAoobP$g^TTtV^Ql>|f-hbU<|kC9sMd`0_(cLUjL~Je z-Fh|ak{`^D{z7-DiZKC90dizs zR~(WZ5v%WeDjuy)LIS0R7&dfBBW#Pl{}}G53*@AeWh2t!nSIxo_Ug&V{2m$Mv!*U1 z+TxN*rG47&bQNidC(OMQfTx?Xp(S?%s%ylQv2>-)H)Xr7{G{hjdXb*o$8dXx)%p#KhFY+*FYoRZS8s@wT_Z{iLAfr(vA zr90UL5vbgfWSn*W!zYOPh??@KntCG|8BSZ0J+JKCrzVyoi?TO=7^?1s>5-BsN&^mfp3pCh*C5U>1r)pc6u zTDe0Bo;erajqB~R($T2h+9;g2N1hD?r#1mQOBA#{?rFk^yogEK{#|G!;yDyCr*kzYB{20o55TqD5T5qX%$T;$}~ zW~}u|CI2TtbhM$uy_|P4zAsfv>yDM61{$=7T1~{luZ!m;yXDhk(HBTzY*;hEq#%<+ z*v9|vdLo>7CSL>OZMdZPlGIl=4S>++lD-k5lGq(ZF|t|&m(@KSrmA8BH!v$u{sI*= z=4m3y2;<8~mp&t(c{z?vXr4E{lQ=uRw-vQ@WUUxb^(~!~8EjOKxxb#3|4rN>#KixD z;OK_giw>PGpyXjX9c!0oO&@eCv%UBytx1F%%!&;9x~%NU%}wt@ z^+(-S*z8s?Wx0TtlTuS&g&TV65YrNVoC=I~*q0{Ex8ejSpV<$@W^W#Ky56{~PI&R9 zk+qLz?T@mGJ$O$8MKIFEoJ9`1-6-Nqdu`hG6^3v;uA{h9v~6j`!Z!>doV2OWJAou@ z>XsQ5S|}C7%$D;xpZMB6TkQvZyVtX(C0!SX?GO#KxbQ_m9=g3S1x%{BqB-rJ*QwHk z5aK(%HpWnV?|}2IU!{%`CBvvVJ3@tAO2^|d2&GZwxl6KWhBj!@vO-<7S|CxKV3{o< zD5n({a5sLaI^eSU6#wgy0^FO_tpPmXk-TnSII31M%hfMmD652M*MBiE-dzX|W-%>io@szL+Kh5TNkLa!L4D3w59NuYavOK+)}n< z83HBM)W2J_`wb!!51Xx}b%kH5V?by6N0ogW{Rse1}9 z7pt`mtyOzzj5V;t?F-bi_Fc->7s7yHO1_=~%#vb!`N*d~&6(7StZA7)q%d*jg9v_o#{AD>%^*xscj z9$xriXdE5!a7jRxg1GbmKSR{`|H|<8!+8frLu{YX9OT7EKO&xc2bg>R!}w)fKP8CbFBW@w>A5I&hwRO4XgW>u|W(Q7i}JwdQlJ zAzSS^$P`Wg&Y;EG6v0;9Rd^1k%~N-?bt~j`#>F0a-VW6oSaQV(v|m#D eV{c3%7eWy0U;Jx3{EUJ7mGP^AUk&`vH1H4DgkRGD literal 63 zcmY#Z)GsYA(of3F(@)JSQz$A;s^sD-N-ZqSEJ{^S&&@9`%+XKI$yDd!^34wp^s%yX S_VLs!D$Uby3~~?C options[:service]) + {:service => options[:service]}, @sql.db.adapter_scheme) @streamer = Streamer.new(:options => @options, :tailer => @tailer, diff --git a/lib/mosql/schema.rb b/lib/mosql/schema.rb index f57930a..ee33e42 100644 --- a/lib/mosql/schema.rb +++ b/lib/mosql/schema.rb @@ -224,19 +224,54 @@ def all_columns_for_copy(schema) def copy_data(db, ns, objs) schema = find_ns!(ns) - db.synchronize do |pg| - sql = "COPY \"#{schema[:meta][:table]}\" " + - "(#{all_columns_for_copy(schema).map {|c| "\"#{c}\""}.join(",")}) FROM STDIN" - pg.execute(sql) - objs.each do |o| - pg.put_copy_data(transform_to_copy(ns, o, schema) + "\n") - end - pg.put_copy_end - begin - pg.get_result.check - rescue PGError => e - db.send(:raise_error, e) + db.synchronize do |adaptor| + # For Postgres we can use the COPY table (*columns) FROM STDIN syntax + if db.adapter_scheme == :postgres + sql = "COPY \"#{schema[:meta][:table]}\" " + + "(#{all_columns_for_copy(schema).map {|c| "\"#{c}\""}.join(",")}) FROM STDIN" + + adaptor.execute(sql) + + objs.each do |o| + adaptor.put_copy_data(transform_to_copy(ns, o, schema) + "\n") + end + adaptor.put_copy_end + begin + adaptor.get_result.check + rescue PGError => e + db.send(:raise_error, e) + end + + # For MySQL we can use the LOAD DATA INFILE 'file.csv' INTO TABLE table syntax + elsif [:mysql, :mysql2].include? db.adapter_scheme + tmp_file = 'tmp_mongo.csv' + begin + File.open(tmp_file, 'w+') { |file| file.write("(#{objs.map { |o| o.join(',') }.join('),(')})") } + rescue Exception => e + log.error("Unable to open/create #{tmp_file}") + log.error(e.to_s) + end + + sql = "LOAD DATA INFILE 'tmp_mongo.csv' INTO TABLE `#{schema[:meta][:table]}`" + + db.execute_dui(sql) + + begin + File.delete(tmp_file) + rescue Exception => e + log.error("Unable to delete #{tmp_file}") + log.error(e.to_s) + end + + # For all other SQL servers we'll use the standard INSERT INTO table (*columns) VALUES values syntax + else + sql = "INSERT INTO `#{schema[:meta][:table]}` " + + "(#{all_columns_for_copy(schema).map {|c| "\"#{c}\""}.join(",")}) " + + "VALUES (#{objs.map { |o| o.join(',') }.join('),(')})" + + db.execute_insert(sql) end + end end diff --git a/lib/mosql/sql.rb b/lib/mosql/sql.rb index 030e299..ec83c91 100644 --- a/lib/mosql/sql.rb +++ b/lib/mosql/sql.rb @@ -52,7 +52,7 @@ def upsert!(table, table_primary_key, item) begin table.insert(item) rescue Sequel::DatabaseError => e - raise e unless self.class.duplicate_key_error?(e) + raise e unless self.class.duplicate_key_error?(e, @db.adapter_scheme) log.info("RACE during upsert: Upserting #{item} into #{table}: #{e}") end elsif rows > 1 @@ -60,13 +60,23 @@ def upsert!(table, table_primary_key, item) end end - def self.duplicate_key_error?(e) - # c.f. http://www.postgresql.org/docs/9.2/static/errcodes-appendix.html - # for the list of error codes. - # - # No thanks to Sequel and pg for making it easy to figure out - # how to get at this error code.... - e.wrapped_exception.result.error_field(PG::Result::PG_DIAG_SQLSTATE) == "23505" + def self.duplicate_key_error?(e, adapter_scheme) + if adapter_scheme == :postgres + # c.f. http://www.postgresql.org/docs/9.2/static/errcodes-appendix.html + # for the list of error codes. + # + # No thanks to Sequel and pg for making it easy to figure out + # how to get at this error code.... + e.wrapped_exception.result.error_field(PG::Result::PG_DIAG_SQLSTATE) == "23505" + elsif [:mysql, :mysql2].include? adapter_scheme + # Using a string comparison of the error message in the same way as Sequel determines MySQL errors + # https://github.com/jeremyevans/sequel/blob/master/lib/sequel/adapters/mysql.rb#L191 + /duplicate entry .* for key/.match(e.message.downcase) + else + # TODO this needs to be tracked down for the particular adaptor's duplicate key error, + # but the mysql solution might be a good approximation + /duplicate entry .* for key/.match(e.message.downcase) + end end end end diff --git a/lib/mosql/tailer.rb b/lib/mosql/tailer.rb index 9abca4f..5cf8617 100644 --- a/lib/mosql/tailer.rb +++ b/lib/mosql/tailer.rb @@ -1,18 +1,23 @@ module MoSQL class Tailer < Mongoriver::AbstractPersistentTailer def self.create_table(db, tablename) + # MySQL needs primary keys to have defined lengths, so we can't use a TEXT column type + pk_column_type = ([:mysql, :mysql2].include? db.adapter_scheme) ? 'VARCHAR(255)' : 'TEXT' + + # Create the table db.create_table?(tablename) do - column :service, 'TEXT' + column :service, pk_column_type column :timestamp, 'INTEGER' primary_key [:service] end db[tablename.to_sym] end - def initialize(backends, type, table, opts) + def initialize(backends, type, table, opts, adapter_scheme) super(backends, type, opts) @table = table @service = opts[:service] || "mosql" + @adapter_scheme = adapter_scheme end def read_timestamp @@ -29,7 +34,7 @@ def write_timestamp(ts) begin @table.insert({:service => @service, :timestamp => ts.seconds}) rescue Sequel::DatabaseError => e - raise unless MoSQL::SQLAdapter.duplicate_key_error?(e) + raise unless MoSQL::SQLAdapter.duplicate_key_error?(e, @adapter_scheme) end @did_insert = true end diff --git a/lib/mosql/version.rb b/lib/mosql/version.rb index a3bee64..1267483 100644 --- a/lib/mosql/version.rb +++ b/lib/mosql/version.rb @@ -1,3 +1,3 @@ module MoSQL - VERSION = "0.3.1" + VERSION = "0.3.2" end From 35d3e653aa55f7100933c8be9c47b34c3aaace69 Mon Sep 17 00:00:00 2001 From: Steve McHugh Date: Fri, 7 Feb 2014 16:50:04 -0500 Subject: [PATCH 2/4] [#64831866] Adding column options and general bug fixes - Added the ability to specify column as an index, null or not null, and a default value - Condensed initial input copy to only use the INSERT syntax for all non-postgres adapters - Improved readme documentation --- README.md | 13 ++++++++++++- bin/mosql | Bin 21504 -> 63 bytes lib/mosql/schema.rb | 34 ++++++++-------------------------- lib/mosql/streamer.rb | 2 +- 4 files changed, 21 insertions(+), 28 deletions(-) mode change 100644 => 100755 bin/mosql diff --git a/README.md b/README.md index 05e4185..e42b0ae 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,14 @@ the `title` and `created` attributes, above. Every defined collection must include a mapping for the `_id` attribute. +You can also specify [column options](http://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html) +if you use the long-form YAML notation. However, in this case you must +specify both the type and source for each column even if name and source +are identical. Supported options are: + * :default + * :index + * :null + `:meta` contains metadata about this collection/table. It is required to include at least `:table`, naming the SQL table this collection will be mapped to. `extra_props` determines the handling of @@ -117,13 +125,16 @@ Once you have a collection map. MoSQL usage is easy. The basic form is: mosql [-c collections.yml] [--sql postgres://sql-server/sql-db] [--mongo mongodb://mongo-uri] - mosql [-c collections.yml] [--sql mysql2://username:password@sql-server/sql-db] [--mongo mongodb://mongo-uri] By default, `mosql` connects to both PostgreSQL and MongoDB instances running on default ports on localhost without authentication. You can point it at different targets using the `--sql` and `--mongo` command-line parameters. +You can also specify a different adapter than 'postgres' with the `--sql` +parameter, such as 'mysql2'. Currently testing has only been done for mysql2 +and postgres, though adapter supported by the Sequel gem should work. + `mosql` will: 1. Create the appropriate SQL tables diff --git a/bin/mosql b/bin/mosql old mode 100644 new mode 100755 index e39f97b415be27546256b63937266f16ba2ce4ce..757273d14bb926f38598994f436a0e4f3ab1bb9d GIT binary patch literal 63 zcmY#Z)GsYA(of3F(@)JSQz$A;s^sD-N-ZqSEJ{^S&&@9`%+XKI$yDd!^34wp^s%yX S_VLs!D$Uby3~~?C4qnV+Knfp#N3G^dGUYu>t+>{6G4iIWrp*GY~Ti3nx1hGZQBhD-aVi zD;oXecfH%j9vay$WmJNf@<+y7QD4-GK z7ZniDTP<1J9VVpyYt1U(si{OeQN)OdidhvH=_2Tt1(i6u`7If;zsV;_)=G4aZfNWX{gEs^WZbhj}z@O<7SEVUsjC-6(7|f-$W?JMx)2@uSgDJ{E5agRp<-`+{Jx6* z2vz>m*U=*+O9>@~!Fk`XVAM2G+f$ktq(MQHOW88}<0h#Bl>5 z;rAZdxU!mz{l81rIpmm*dgd$hkga%1dL`4uN%1xYu9Y>1@uhYVWPd0ZSDlIK{ zk~enajHx{1lFbYB=IZOsI=ON4a`P9C)x4PcVdK2~L09kzBpH|wz{@qsm+M5^rt{l# zY@usJ4#U5@bK{RNYX3w)-Z@2}TyF7wr%`GLPiSo>=##B-l^iegH=?%Cae@BEdjx2yc`~T{({C`ZZ{{#E~L%{#{@SlyD zm7Vi{#(!20*8d;>|DSMz{|o5Cw*1(B|Vj1SzmhI&!Go*}+fwiFBb~9c~K# z3158Q#`>>uuB{Uacs+Eu|DFIE8XY+R?TdB-TOlEdYn;X37SD^N1JSPo{^9dvG4)7F zT{t`moRvEz@1Ju&xqcAA=iH$i@ZXbp34ErrH;R7Su4{1vyZ*nJeluLH4Ej0$9NT|$ z6FHu*0m9D?KY{6qmY?US*E`nHPD_P2ho@EMP=9DRW(a5HNnM6zJrBgb?a)5XgLfDKl2-a~^=5f8rcMmx+^^N-$_adUd}pC$HmX#m~p zuGf9P&b?k;fZ;JJc&gJu)D=_0MCW7@Y#wjvB;4?Gjq$}>nC|D1KuaQ%WoD}fpM?hz}f?~sjRIw9x|cw zHWR?uVHF;KXhu~1E|38B_VQkO*F8W;e~|J)@YCvKbZxMu1Zu)qKYnELb$27WC|TaC zDh497IDCv_ZP}ykYoQYXB+?w3Z26FMq2|x999o3o3V|bNk)&RLpTa6()tU26u{%78 zBJkVA5U8I;@8&+Yx3kyF7nUX1s;FtlPlbQewt!dv`go)b8r-I*Xv$0U04Bjv6-xov$K}L#B4Y0dDpG7!=dR z1jYj_V5eymC}%9?5ou6tNZZdm909EzoN3zVAY{r4mKRG-B7so%q5!3;0thiaGC`R; zgPq3RX4HdDu~R_EHOz;+VaE$g&IE~E8L#`~$VR9*nDY-Z1KT$L$N+-7E#IZD1DhRIdu-9aR4f9Ck0goe;2$kmk%P*DNF=?%+^-xHN%JCf z(>PF^zr-aTjS)js+o)J2Ez%dFsR{DC@J5qWWBd8SaY{`e+6Uf4=x*>iFZP*wKJ+78 z!I~O^oo-c(e5(OSpfzzI3WHqv>Yp??Izhhs%wB^<`d9|C*9uXyBTJ8_nBxI>VJimx zxfHE{8u|o8!@Tcq*A)!kiKE%YLq^}8TYcqC?uh;Lh!6Mslhwm*RABN14+Mn()JVqB zxgp0R!FL1?T#0`Jdw6Y$iDQS!8@@jlfsK9JNWPHs@B4Rm_`PWJMSDZUNEZZ2)Ij{) zXd6Xf;36Pis*h zubcuq;1LVk;(z+`E&tg&!0^2K1zO&_i{9Mz?s;!k=tKgYN*OUn?>07Wy+Cbk2LlQU zZ}17Qm?-8E5fu^+EJ1y|-BQ$k-0wSgk$5G2C%A|h6A*y;5GYr;$cU8gi)NLas>CfM zilBZ2e0LO_H|_6lKM%L(%f1ZnTvzrs{dYMe-#AkNxQPS;8+2cR1bb8e`L8x| zW9V_WFvZv8LE#ts)-iT4gra- zxr90(9Z?F;d(OAx-p7Fto#A_R|Fi2q?8nSm!I}%g>ux8d9t+iask^n8C6o-jH2HcBa9UIO`eNAZ64P^n^{Vg z@~O6p@|)RNc&mU{fF1b5_}G*-?txdv2f$}@Ug;P#{&n!W)uPLUnoJ; z4$1~yiX3&i%B6+h^=5PO8>@fI1xD4(YJ|NKh6EEk|iGwsx>@N`Ug`^m_N(_ z(KrkKFX&Zs<6Pswi3z{Wv2vA?nZnmi==P?VxLqvJ-DUML$ZUCA%+?i5+oJAo7xjvn`v zf0Tftor=uj+_Cue7Uom&-5$4aQnN#{L?rDAJh!<^iH#~`b`q@fD+(-eA{&R$DE%h> z6l+R!volJdFbxjdxA)_)L)XU{$SH5b2i?IFIcf#g)}rvxL1;mCjwN1Cr?YPwihXN6 zivI0su1I#;8_9XA7{_i9v16gwj??7;rfQOF&No^d;o~;eXX(cK64J={us?qdu$Dyo z6ysvinU9WE?&t(3o2F&ranket#xeT{1}=?51>)> z2%vL-JJxiI6e6u;oz7#ii2~)O9IY;?R4w^Z#VJx1+6MLptCjLqWarN;#}ElhC;2CA z7Y?gJNaE4sWXSp#ZVfRr)vkaETU(V~g5+Alh%ZVx0I`#$Jg+_K(pYqQqr>D9OjzM^ z>qW0RteIg6d8Mu|BA^6^yB-y76wgrDFup?>gvnYK)Sg%anLc&lQJY?VPlC)Ht|SW> z8e_s$LMBaTP?_{Zd&Llg8)d-8%cZ@g*6|z=B zUUv!7K&kSALR&q7F3aPo2B?f#O)N7zEhEBYw{nn+tgehrfz3C7Zs}r_IwM(C8t0SC zO=u!69ZVP_!9Qetg3MHfyY@YU2tlYoQi8?61*V<}>@0CG`GMp@I!>fi7!FGuYFg^u z4u%nQUpsx(JM0uHY1n_d_xhxR^H%j}MN`z12@Fb^C8Q=Z73wSMSyl!+8V6%-iVfGL ziDkvI6z9S<5X~J>k>rMVX{?4n2r;SaA)^;}rkE$Q=blqemFJ2T%yHRBH+l=#A}ql0 zW6FeJ!)=0D1)e8O8Z334joGQ^fNT$+JY$ZYp(Ky1MfwKC230Q>!KN4mmm*=xmI@TB zYEjNK>S9gBp15iSR#%G(KFl&!J?@}b+(D&WJW`jH(jR0L%4&L4I(|p%qIrZZ#YyC8 z7FIJ3)dsRkU5+&f#a9~KIv8aYrMlFiFysZr;AN}}u5mZQdUV8f+_d1b7*iQ7B9IkE zOy5M*OY|2d!X_xL0+>d(u4+xHdd*{g4zC8}g?97t>HuwZd3ja6K3`Rc_b<<4D#TV6p0>aSpDpOcPma#BEv=9F63)KFiIyOkdp;9s3)#N6YQrfshQu!-DnEO) zeMrd(f|WmZ!;-&uWqS(`QWx{p@QAu!rMSw_y!y&%ep3IIKg}plkxJ;=+Qs%2nOn~( zDx}%Xau}%iQqJlOvYti^Lz*=R8{eZek|-iM+t3;!pHxW?$^^fQ5|)ss5NT$eO%uxC zx9}-?{JR;B>5fYjY5Z!w%D^c~<}PozInZ0ZV`GYTO3rO3pZS23`$3gM))6;kV=@%h z@fMV`C5gUvQ4^bSa5IS_ZFU_T9e`)CO1Y7Jw1PSTcp?jqC7cA28`}oHxqYaK4qGR9)O;5y0uF*BA5&*nIZjrI`X*J30o>FYsaxP#Sfp4IJzKkq zTs(x#baf~Uj1-m~%bV301~ybcniI0h%`@DFP0v&0bklkw;c zF>q|371Bsd2y#X<$&L=l0mKnRpTcC~t$EUUfqO#yQ)>ka9j^*-Vze*?h8!zO@3{dN z(*}%fYqfg$3REKA&eIK+$fEiSiMP^xyD}?ijE%V!o?D zQpUr14a9Ny>?e_nH=DBkT%XO0;6(iyh;>Y`RpJT3P?a#sA;!Y8ZE9L|q2yF~aE<(% zWp*G%a^u8LbWN2KOlIv-nKMf+DHMo3C8pHL@Rfw}nguWF1~rP*)mEKgvD5|^l+z@@ zdb(=}@~&DfnQp0lB=my8W#+b$Fv#2aL2mMB$CJB9Fo;6U6la)yMI;=+@NAgOBttH& zb>NFHsgvDl=jd_ax83@7orH7*l zTe4f+AC&zx`l-;*-Ep+M?=m^?oEn^lGfUneGCp0Y7t*=viYEhiOTsQ%7-s&NT`e5Zbh#G(V%0( z1DANeFpE>WZPo-MxNNy7+{{FLBp#21s^28hQ|6Du6FZ-yCR-F;JW45yW1WMaD)?%b zm7V({mO~|RCt;w_)W?w* zzCoBWN@h;5)sjk*q37H;`kLC;^T-uS>Zrkb;3uei&gmGO`&@5%0%j@P*B>`N&(%^F|>11I#iqYQw^wB^j7x0 z&lKlD-NUx#;x{~*37B!-Yx`CuDQuW8Y7YfC5K7zxbVTO`UdM|TWN;hcuOJ!%%$P6! z;HHp}!B}_<&|6y1;^%l2zDxHt1EVO>=uc;ePOVVWIa3^1zHF15Y$Xe%7b{3X$NdX# z=$_%(yONVCJ6V|FtduH8A8V^glwNHRa%y4uriL8+Jv?Yx{VJ~Yp+q* zgjLi6P$m*g^GIxLEl~ppbC+%T=Wlb30cjU;-^2}x6uh`CgY74iWz2KmL0K60qyh1+ z3X*H#&*g|f^gMv)rcWwDz~jZC{`Z9DXH``sO+;uTG4h0ln=(7xH>u9Fe9MoPo6bi& zt>HpOwK36Ws}AME5g0*kQF&>|FYzx>g@N5LO4Mlkh1!8^xoqO#$`$0GT)Vd{Bq8wz zfuyHHEarnJA=;`!I>yTTexpBPZPgaLXrvc{$V4;YWJXyj?Vz|Xm2SaGjmx6CGwSRR zKJWnA1de+-Copa8ulJ*PQX%MNBL~2L-IC)ud>v3)U%z<{AUItIoQ<#Teik;O0^A+# zQNnGdAc%HPs1w7eomQp?U29d8I&|jW-{q3p+ww~@2UhMuL;$;jgT7{JDjNWer$ATN zTBqOpp|jmDK+RL3LZbR>Ibu!ATP|YcDQwccnr{^3RSh@<$xq|zx2sjl1KdwVxaEl% ztpEk<6f`X3hO>6xu|kIyVDfYT2~i6P(Dz&QN!0O67zE7ce;(H_wfV^n(+dzK-Tk=q^3sEC{D^@}Ad$l`1`J~0ZZ=(;AS^MOM zkbiM{pMVyJow=_f)^SNK4^}qq?Cv7lp!Ax#ic>?Os~$cN$^j;$p{=?<-!roQ5`PO0 zA?4KH3FOXxQhbCojoM#PZMy_L6Vc>vVa^DWDbO}ZE3iwTJT^Ef_?NM6Drk>|o?ECd z7j@sjLwQ@e-Wg4_M?lElL*bpi&=3Sen9|f`S`*LhKhEwIJRXms5~(Y35mu#c0P%-w z&$r73KHNd>kwOd+m0Jtsu-DUcnT^)H{!DeuW|j(Jdn*o;nUOcX#&-FVhc= zP5072mQ(aA$*9C-G7cCMsafrKuSU{&En7bPtjy%~`X;!(0uFV;`<(y>zS)kVit5sc zD%yacQ*J^Zb9W`+i|h^T#VN>AqvZ2VqCzsY7L|GIX>U6=BYiznEbBfX${4@PK|!qX zka$%%j`04;Tb;s0rRnivE$?k0knON{*jzK1n+3wvIyWX)S2P*L32BTr8Kw+-O$Tw0 z4@UOdF40<*=wS!dtcwjCE|*62k|A=7m%?BWns0kzj^5-h8{Ps`-l)F>_jXU9$$7`o zqv&@$f0Yt9%O)Ru^!jqYOwUY%jWnsFDnjS{l3q)hm;cLiD5JLy?U~pAjBSVZ0Z^pz zxkbKe){{lK_`yV?#A7RCKt+ujK44492y3XYcS5_5@IQmHTShb53s&=-%dt}_ijV6a z*T=G|JHbYy5Rgtn5O8kB8R6Bk_8c#RyOTyH*zfc8gWHXc+;MkpeB!XTSDYh2TKxmdl;bsWm;0$6>;<(qer4-_ynrff=-Ph(%G(Nhs8 zUDw9s6OP~&tk7H>Kf>Rz1x(8~UJA9^@bPbdtS{v>{gP24eTAlycAY!Sp$1D?$TysZ zLvj6msb;`r2ExHL13Y=ks@O=+;9Xu|DOwEiu0Ns`IDsYs&UX=*&J3Hn9^-Q-5^=kP zNv(wv!*e|?_6fU~1nxp$vy+}!r#$IuCE!7>$8orjfFM9Q zY_mE4_oP=Tj=o;%`@3a9W9x?s0+hOLp46DJ>87B9lRT}edZ#QnTMb3MMyG2yoebY) zFkqRPo6yE#tmFKjqSe7P`B2&LIx4&Ki=qiqTj&XU~y zA80&vREKZ1@vGC~13tH`U7(?MDL3Uj?e^CQ3OWn_hE7>;h04!pZ1$sz}yCuStcESgFr- zdy;MRO|5A&U~_GSBGMZE_2xs{Hj=MB$4i?vvP@V@0TpI~fwJJ?9ME%Ut-@Q-<*V)S z`3+rgA(gB&&XDsJcv)!U9@ou>fV?pwb)+c|cv-nAW2R!JiJUtqp$y%) zUv;X5Ib2QECu+(Neg;>?T~J0W7Lv?=TIX;-kdAox(@ed-j>=YtlGDg|_i-17h)xDs zquKDEH(_BiJVS0sw}YrdQNqO-Aze?m5$;qJ{g|tDvml z$F4+NE*8lQGNDvcV4(W!FX+^ZNd<9g^{_Y$e^Z7BEG~Z>^{>LuLH;uNulttt-LwYv z(uveBtMl-qhoQfhjXPem(p(I%rmu>2k$5&uum+jMlzRKo3xpAlCcEDC5~5Ovy_&5W z9d_v!0TFEiiyLjYtFPl!mf5_2@|Eo>Qe)7%eSnjA%vK9JvD=2)zVp6x>aJ(7WF=mb z7!7jub^C5lkP5nlRZYrSq`Rx1AYa3w1>lftXf%Aj#zVsf^{X|vR(CP~E@x+OF`hLO zfskve_7m^0xH+)=YxsiEZQ7*y0k0cdUC?BD2npDS?yzRmDU=<%YU7?kU;!xX|AU`> zX0c`X&VDA!RDcmt@Vi`N!|yuVMR1E%(`?9YxU_t)$l1@4V8nqFlYi6TL#E$#cTsF@ zMl4_pLtr|m#>A$>WI9_>Zfrz|x4-U1GPi#XXf9Tx=R!eJkObHBlvXnuO|PCqThR${ zQlD2}h9(;#}f$+I)*#pCl&XG&qpk+j%AOV zR-w1)SupP#sB{+`s^MZ`{cs~-Z__U@lH$pJajVoBl*?1%ywv6@Y4zQ03FY#*(OCfW z5r6cESl#e|@@Nhw#xAc+G*t<6VZX8#OKOVFqzt#Ey8yJdsYlwn>-Qu@-|1G%+@p#% zJfUc*3Kekpf~OxGDWAFW^G8EB0s0+WF}g8La#$%-q{^vS$svUw((G|FNXwEE3e&;p zK7?*BXk1b?di6^84Rf^9unBPi*e@ZnO{Oh9md@*u%P_j+2*Dv75b{f%)GS{ z9&pALMRN6^)&v$Oa!(m?efIKl*x1{VGP!9PLIl~>JM&dQnVs#Kc|9R*SsDd@gq~kd zzYT2=t-cEJ2?<(t=Joc6>qNh=lN5~aH{qT6@Lxe0No$)e_^?TTz47_wEp@_nFD(-<^Ap^hV_G`N&h zp+18*=z%7!e2mNaK;NWLNryv7>#Z)tctN;6Jp{`TE>vm{j)wSWi%M$ZKYa5RI8W?& zLIb6P&Zj$sbWk#C0E(N{2gw%InbSsq-ev zRsubY@Qt{D;o@EA-kF>T!cnu?w+>tRen+2Y@Cy1*1%c!7#ieLj9kRu9yp;*6m{Z!Q^74%&1YfnSW?8QO_yZnZ1%-1*+{4c zGQoisrs#|WEseV%1uR%0K0jAU8BOyRlN$|RW0j$6|1;o@xCjAr_G|9(BmxSs>svRM zH-68FD>^BVkuR)tN`_&7Yyc7A=A~cK^4CO0e-w=f*R-Kse~!>%*d2mY7VZQMQto07 zMARG5uvWX`^ns-5CZ>d(aNFYzg{~3_GS&}FJZ`X7)~3}^Cr~^x;4zOzSp+2zl3D_z zQ7>T#03!RzMu`t%HTy5MJ)jj^}BsT zp|C?zZ4EVE${cI3>4sM>geVa)=6`wkkZdWuFN0(<0rt;=E#VbVc?%3H5T z%hsaM=nc%A(=hbevZUp8lArY`sT@Go@olt`uq{c+=L4?s^>^-f`|Z#4nW`R;f^&hJSIfDu znhQb^p#bPUT`)DT)8BBWP${mLj7zh*Uu6ltUuNhcidRsHKLyJq?&Y+v!9j4|?{`Hd z12dyf8pk!i3WCvKjPopvn>^(~6B^gyt;(sxIs4?53CQ``=Zj^Ay0z|meZH6j&->xK zUpP#8o-dnK43>o!Vz5@<<{mU#kS-2J0d35<2PbeLiyJtjE)Dx*=_u@BP_b4N$G?lK z33$jEv=VuA%XgC9ocNq-f7uGj7?N-_)Y&t2#^P-$uZnXltrh| zrD08yUWMQFdVEOT7kVD@dOixte$Qi1f&RM*cf3}08AJhmoiG8<@W+8EAMfG8{Mn`5 z9s_Q)+M7|S3-(cu-pNCi$|u<)<}`LVSr!#G{vKk1-pYJA+lrnmX2uMg%}kjtiuqup z{8vbhIwYWb?}9UkAK>_g>}^ymomEU30ZogeQMG)6Hurh0V~@|8x9hxS)-`8JL)DBR znOL&*7}>J1)0iEomJ1?FpwjA8!?eyfFt^3y#=yf1A~GQI!`kC>`xLS?;*Z_ zyWZVW2fTVYJ7_RhO7H8^?};|zp5axNTO6qsLs0b^L%fZ=nKOZ1$2e+LvV}z-PK%x2 zL=iX0y>fL;#(yYNnQMwzyPK+V^*#;g1)bu-ZZ~VBMB!XpT1B=Y-NZwl>O#!nyCExm z*~b)2A|}z=u2et-F06B<8_N{?qnr^4?Sn9B7eTrHjDi6gj3;5O6SzY{D!@sP$#BsYI9 zK}(b>iHd^Ndcc4K<|p>kBzrR!%Sw5@=Sww#ga zE@k0`l;PZ&IOE|ZgK5tw+AuLF%FflLiC5s=@;TP37V@>yKW6+MrKa1Cen$B38r7LP z#t`#wX$WvfS!ofRC$K$zc4?*_q6Rst=2&l$MLRT6ty)XN+0>Lrh!hC~nkJ2#u1TdO zf)sVdsk({7&9JJLANF;3v#290(LH*=^)i^=O15-GoM-M1;k3r$!Y|V7i5zX;`MP2y z^1&B$ON;ONibbhh?NjYaX)St^T;u=&LYA&s+MFQt_9h6mF`syZdu2nLGbo`g*r`N~ z)*cX218TmXtp8H@Ep4(jigb&VpHo#PQ151_l}gstM;yIJ5p?J{8oYVDdl> zFB(h?)vkcnYtsMCpN5&^i1dSn+Mrt3OJ=S zgD?d*<9@7D`Yf*Xn$|52L5b*_!uj|OQ$1Cc6a&ivB2-B8{5zzV2N^t@8QJT}{dsM9 zWqG;Cc#L`@=stYYf*TC`B*^d8nJoxtYx}&o`(Ed41&~qGWe-|gTva^f$wS^6`T{n8 zZ?Qa;Va0Kt(kwy~3^-Q2ryZ(|qle;b_^GNc2{7xOkxO|fuGwC7JOnWpAr+O8~gWOU4kiV9wEWw={z(#pG~{?|7kp&!Q7KGsk0Jcyj4~_FYh>fQYQ|qh6e_oRS}7i@;19ty zWk;i8TXR^o`sh-QRSLt+J0N#9KPkDn@sj9@4kaOAZt76?uPEYE>3P)rU_6!&Vg}+| z>9Yw5UeWT))#HSI2e<=aWc)(_iLhvZM>+m?cTegmXVEcL3&GM9rj2T$m%Ex#j{Xsh z+}^w3MWYPY0BOAFbHYQA@n8*#2+#UiL8)F&Sv ztr|W!kV9pa@I{z|ZUJqoz{F1gnpEw-K3X@|vp0E4n#zq|!ar~OfV_j(Or`+S-U$PU zACPOG-?xj8hbm7euO(mSk2fPv`F(VEKAzPs2Z!i;aeGjJAL24+eWnYoLPEv@$#Z2y z6yF3k>oRc=r}FO&WO~)&->+sgi5kHvdh{@{?7{XiXoI&y$SbP?1;MLyt6{=J-NVJgT%UUF;nssz~?F9Z;G`9#Ba7-g)3v7rLBCk|#$3EP#P+WQ^ zY-eA~(-ejJ<3gW|qwPnBU^dD{65o@So%!jTZ8Y^MlizFXNPiU!cS9l4SbH@Df1A7Z z%tC{$$^?T?I{?b*I<G zhUk2o;D`fSuakX!V9Zpyy^_8w7$OVMjy$M${yx%D!hICnf(ov#D*QuhWtea2dGj^E zfzDij$Z?XqKyEfN1-^a9BPw;)*Ymob8ArSgS0h(;)!elaJ1}PmH%=KJethf(DZ1`b z?I#MA<>=i=k6w28qxLxB4bSR|Yj?&xpA%cX=auF96-(?VYmw5rmcbr!7d-bohnwC; z<6qf*6%Q*vOBq5&m-$Q9_|AtCl*wx2+-)#noe1SLWIS-EI;n38ckEJomBccXolTV^ z?|puiPIpfxn^Za2y~$ibecrs;Z}*_Xh+H)HnmePW2l4TFzhaFJ1VUm%Z)OWR8#~X* z9ujI2{DtVaBR6v#Pn_SQu-=8`o!Dyt`?1u*8BCn0QEc5(+XlEymRppnyQ7$Vd!YFu zvEin{QPT958#h8OD95@j`Qst7`iD^4Er~+99Me0@o=J@awY0o^mvVh@@2K9Xf*ixi zt>w%OdcPD~@91{e5P_@+{>9tV^THJ>njamb@p!J?>({E_!b*b+JzuGWXHjY7r=LkM zRn)2aUoTkleQlaX))}GX(As4RPg`9~{H{ZwTFaBL^Mt&rujVxRSVrM>@xkZGtvR|& zZ0^Col!DIw6w%}0<}JWFgYXJ&U6HB41@#4eIh$hh5&0+Ur*Mnv!%Aq+`BUQp{ISR9 zQ2;fEpT>NEeP35w@&Lfxw(s-60dSvt3vhnYdSvxAxJanfu&pF44#pT5`kNk0QaDrF6CS6uKsSaQ^L1yQwA@+WG~nG+M~lt+vNpRFH-5CsawzjZf?PaY;%KBLle zB#vTpFsLyUM4Yhbuk>4$H^ay@({#?MHNC?ukO>)cx@u9}>M1>5F2Pdff0#C3X0!FD z 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 diff --git a/lib/mosql/schema.rb b/lib/mosql/schema.rb index ee33e42..7f299a6 100644 --- a/lib/mosql/schema.rb +++ b/lib/mosql/schema.rb @@ -13,12 +13,14 @@ def to_array(lst) :source => ent.fetch(:source), :type => ent.fetch(:type), :name => (ent.keys - [:source, :type]).first, + :opts => ent.select {|k,v| [:default, :index, :null].include? k } } elsif ent.is_a?(Hash) && ent.keys.length == 1 && ent.values.first.is_a?(String) array << { :source => ent.first.first, :name => ent.first.first, - :type => ent.first.last + :type => ent.first.last, + :opts => {} } else raise SchemaError.new("Invalid ordered hash entry #{ent.inspect}") @@ -80,7 +82,8 @@ def create_schema(db, clobber=false) if col[:source] == '$timestamp' opts[:default] = Sequel.function(:now) end - column col[:name], col[:type], opts + opts.merge!(col[:opts]) + column col[:name].to_sym, col[:type], opts if col[:source].to_sym == :_id primary_key [col[:name].to_sym] @@ -242,32 +245,11 @@ def copy_data(db, ns, objs) db.send(:raise_error, e) end - # For MySQL we can use the LOAD DATA INFILE 'file.csv' INTO TABLE table syntax - elsif [:mysql, :mysql2].include? db.adapter_scheme - tmp_file = 'tmp_mongo.csv' - begin - File.open(tmp_file, 'w+') { |file| file.write("(#{objs.map { |o| o.join(',') }.join('),(')})") } - rescue Exception => e - log.error("Unable to open/create #{tmp_file}") - log.error(e.to_s) - end - - sql = "LOAD DATA INFILE 'tmp_mongo.csv' INTO TABLE `#{schema[:meta][:table]}`" - - db.execute_dui(sql) - - begin - File.delete(tmp_file) - rescue Exception => e - log.error("Unable to delete #{tmp_file}") - log.error(e.to_s) - end - # For all other SQL servers we'll use the standard INSERT INTO table (*columns) VALUES values syntax else - sql = "INSERT INTO `#{schema[:meta][:table]}` " - + "(#{all_columns_for_copy(schema).map {|c| "\"#{c}\""}.join(",")}) " - + "VALUES (#{objs.map { |o| o.join(',') }.join('),(')})" + sql = "INSERT INTO `#{schema[:meta][:table]}` + (#{all_columns_for_copy(schema).map {|c| "\"#{c}\""}.join(",")}) + VALUES (#{objs.map { |o| o.join(',') }.join('),(')})" db.execute_insert(sql) end diff --git a/lib/mosql/streamer.rb b/lib/mosql/streamer.rb index 7400f68..6e8a3ec 100644 --- a/lib/mosql/streamer.rb +++ b/lib/mosql/streamer.rb @@ -39,7 +39,7 @@ def unsafe_handle_exceptions(ns, obj) yield rescue Sequel::DatabaseError => e wrapped = e.wrapped_exception - if wrapped.result && options[:unsafe] + if @sql.db.adapter_scheme == :postgres && wrapped.result && options[:unsafe] log.warn("Ignoring row (#{obj.inspect}): #{e}") else log.error("Error processing #{obj.inspect} for #{ns}.") From 00639d22fe720be927fb0da395cd670a2904725e Mon Sep 17 00:00:00 2001 From: Steve McHugh Date: Tue, 11 Feb 2014 11:13:56 -0500 Subject: [PATCH 3/4] [#65427808] Add ability to read from slaves in mosql - Added the ability for the mongo instance being read from to be a replica slave --- lib/mosql/cli.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/mosql/cli.rb b/lib/mosql/cli.rb index d22ddfd..ac73169 100644 --- a/lib/mosql/cli.rb +++ b/lib/mosql/cli.rb @@ -37,7 +37,8 @@ def parse_args :collections => 'collections.yml', :sql => 'postgres:///', :mongo => 'mongodb://localhost', - :verbose => 0 + :verbose => 0, + :mongo_slave => false } optparse = OptionParser.new do |opts| opts.banner = "Usage: #{$0} [options] " @@ -94,6 +95,10 @@ def parse_args opts.on("--unsafe", "Ignore rows that cause errors on insert") do @options[:unsafe] = true end + + opts.on("--mongo-slave", "Set to true when connecting to a single, slave node") do + @options[:mongo_slave] = true + end end optparse.parse!(@args) @@ -108,7 +113,7 @@ def parse_args end def connect_mongo - @mongo = Mongo::MongoClient.from_uri(options[:mongo]) + @mongo = Mongo::MongoClient.from_uri(options[:mongo], {slave_ok: options[:mongo_slave]}) config = @mongo['admin'].command(:ismaster => 1) if !config['setName'] && !options[:skip_tail] log.warn("`#{options[:mongo]}' is not a replset.") From 8d274b5c81ce5eed4005b9bb913bc992169be584 Mon Sep 17 00:00:00 2001 From: Steve McHugh Date: Thu, 13 Feb 2014 15:14:21 -0500 Subject: [PATCH 4/4] Reverting slaveOk flag --- lib/mosql/cli.rb | 9 ++------- lib/mosql/version.rb | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/mosql/cli.rb b/lib/mosql/cli.rb index ac73169..d22ddfd 100644 --- a/lib/mosql/cli.rb +++ b/lib/mosql/cli.rb @@ -37,8 +37,7 @@ def parse_args :collections => 'collections.yml', :sql => 'postgres:///', :mongo => 'mongodb://localhost', - :verbose => 0, - :mongo_slave => false + :verbose => 0 } optparse = OptionParser.new do |opts| opts.banner = "Usage: #{$0} [options] " @@ -95,10 +94,6 @@ def parse_args opts.on("--unsafe", "Ignore rows that cause errors on insert") do @options[:unsafe] = true end - - opts.on("--mongo-slave", "Set to true when connecting to a single, slave node") do - @options[:mongo_slave] = true - end end optparse.parse!(@args) @@ -113,7 +108,7 @@ def parse_args end def connect_mongo - @mongo = Mongo::MongoClient.from_uri(options[:mongo], {slave_ok: options[:mongo_slave]}) + @mongo = Mongo::MongoClient.from_uri(options[:mongo]) config = @mongo['admin'].command(:ismaster => 1) if !config['setName'] && !options[:skip_tail] log.warn("`#{options[:mongo]}' is not a replset.") diff --git a/lib/mosql/version.rb b/lib/mosql/version.rb index 1267483..a3bee64 100644 --- a/lib/mosql/version.rb +++ b/lib/mosql/version.rb @@ -1,3 +1,3 @@ module MoSQL - VERSION = "0.3.2" + VERSION = "0.3.1" end