flydata 0.3.16 → 0.3.17
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 +4 -4
 - data/VERSION +1 -1
 - data/flydata-core/lib/flydata-core/record/record.rb +13 -0
 - data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +107 -5
 - data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +62 -11
 - data/flydata-core/spec/table_def/mysql_table_def_spec.rb +37 -1
 - data/flydata-core/spec/table_def/mysql_to_redshift_table_def_spec.rb +120 -0
 - data/flydata-core/spec/table_def/mysqldump_test_column_charset.dump +45 -0
 - data/flydata-core/spec/table_def/redshift_table_def_spec.rb +70 -88
 - data/flydata.gemspec +13 -8
 - data/lib/flydata/command/setup.rb +4 -4
 - data/lib/flydata/command/sync.rb +18 -29
 - data/lib/flydata/compatibility_check.rb +2 -2
 - data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +15 -10
 - data/lib/flydata/fluent-plugins/mysql/binlog_record_handler.rb +6 -3
 - data/lib/flydata/fluent-plugins/mysql/dml_record_handler.rb +15 -8
 - data/lib/flydata/fluent-plugins/mysql/table_meta.rb +6 -34
 - data/lib/flydata/{fluent-plugins/mysql → mysql}/binlog_position.rb +2 -0
 - data/lib/flydata/{util → mysql}/mysql_util.rb +34 -1
 - data/lib/flydata/mysql/table_ddl.rb +118 -0
 - data/lib/flydata/parser/mysql/dump_parser.rb +5 -5
 - data/lib/flydata/parser/mysql/mysql_alter_table.treetop +29 -5
 - data/lib/flydata/sync_file_manager.rb +15 -2
 - data/spec/flydata/command/sync_spec.rb +22 -1
 - data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +7 -2
 - data/spec/flydata/fluent-plugins/mysql/dml_record_handler_spec.rb +130 -0
 - data/spec/flydata/fluent-plugins/mysql/shared_query_handler_context.rb +1 -0
 - data/spec/flydata/fluent-plugins/mysql/table_meta_spec.rb +14 -8
 - data/spec/flydata/fluent-plugins/mysql/truncate_query_handler_spec.rb +2 -1
 - data/spec/flydata/{fluent-plugins/mysql → mysql}/binlog_position_spec.rb +3 -2
 - data/spec/flydata/{util → mysql}/mysql_util_spec.rb +2 -2
 - data/spec/flydata/mysql/table_ddl_spec.rb +193 -0
 - data/spec/flydata/parser/mysql/alter_table_parser_spec.rb +37 -9
 - data/spec/flydata/sync_file_manager_spec.rb +102 -27
 - metadata +12 -7
 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'mysql2'
         
     | 
| 
       2 
2 
     | 
    
         
             
            require 'flydata/command_loggable'
         
     | 
| 
       3 
     | 
    
         
            -
            require 'flydata/ 
     | 
| 
      
 3 
     | 
    
         
            +
            require 'flydata/mysql/mysql_util'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module Flydata
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
         @@ -123,7 +123,7 @@ module Flydata 
     | 
|
| 
       123 
123 
     | 
    
         
             
                end
         
     | 
| 
       124 
124 
     | 
    
         | 
| 
       125 
125 
     | 
    
         
             
                def check_mysql_protocol_tcp_compat
         
     | 
| 
       126 
     | 
    
         
            -
                  query =  
     | 
| 
      
 126 
     | 
    
         
            +
                  query = Mysql::MysqlUtil.generate_mysql_show_grants_cmd(@db_opts)
         
     | 
| 
       127 
127 
     | 
    
         | 
| 
       128 
128 
     | 
    
         
             
                  Open3.popen3(query) do |stdin, stdout, stderr|
         
     | 
| 
       129 
129 
     | 
    
         
             
                    stdin.close
         
     | 
| 
         @@ -9,13 +9,14 @@ lib = File.expand_path(File.join(File.dirname(__FILE__), '../../')) 
     | 
|
| 
       9 
9 
     | 
    
         
             
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         
     | 
| 
       10 
10 
     | 
    
         
             
            require 'flydata'
         
     | 
| 
       11 
11 
     | 
    
         
             
            require 'flydata/sync_file_manager'
         
     | 
| 
       12 
     | 
    
         
            -
            require 'flydata/ 
     | 
| 
      
 12 
     | 
    
         
            +
            require 'flydata/mysql/mysql_util'
         
     | 
| 
       13 
13 
     | 
    
         
             
            require 'flydata/fluent-plugins/preference'
         
     | 
| 
       14 
14 
     | 
    
         
             
            require 'flydata/fluent-plugins/mysql/binlog_position_file'
         
     | 
| 
       15 
15 
     | 
    
         
             
            require 'flydata/fluent-plugins/mysql/binlog_record_dispatcher'
         
     | 
| 
       16 
16 
     | 
    
         
             
            require 'flydata/fluent-plugins/mysql/context'
         
     | 
| 
       17 
17 
     | 
    
         
             
            require 'flydata/fluent-plugins/idle_event_detector'
         
     | 
| 
       18 
18 
     | 
    
         
             
            require 'flydata/fluent-plugins/mysql/table_meta'
         
     | 
| 
      
 19 
     | 
    
         
            +
            require 'flydata/mysql/table_ddl'
         
     | 
| 
       19 
20 
     | 
    
         
             
            require 'flydata-core/fluent/config_helper'
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
            #Monkey-patch fluentd class (EngineClass) to support shutdown for input plugin.
         
     | 
| 
         @@ -75,11 +76,11 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput 
     | 
|
| 
       75 
76 
     | 
    
         
             
                end
         
     | 
| 
       76 
77 
     | 
    
         | 
| 
       77 
78 
     | 
    
         
             
                # Db access opts
         
     | 
| 
       78 
     | 
    
         
            -
                db_opts = { host: @host, port: @port, username: @username, password: @password, database: @database, ssl_ca: @ssl_ca_path }
         
     | 
| 
      
 79 
     | 
    
         
            +
                @db_opts = { host: @host, port: @port, username: @username, password: @password, database: @database, ssl_ca: @ssl_ca_path }
         
     | 
| 
       79 
80 
     | 
    
         | 
| 
       80 
81 
     | 
    
         
             
                $log.info "mysql host:\"#{@host}\" port:\"#{@port}\" username:\"#{@username}\" database:\"#{@database}\" tables:\"#{@tables}\" tables_append_only:\"#{tables_append_only}\""
         
     | 
| 
       81 
82 
     | 
    
         
             
                $log.info "mysql client version: #{`mysql -V`}"
         
     | 
| 
       82 
     | 
    
         
            -
                server_version = `echo 'select version();' | #{Flydata:: 
     | 
| 
      
 83 
     | 
    
         
            +
                server_version = `echo 'select version();' | #{Flydata::Mysql::MysqlUtil.generate_mysql_cmd(@db_opts)} 2>/dev/null`
         
     | 
| 
       83 
84 
     | 
    
         
             
                $log.info "mysql server version: #{server_version}"
         
     | 
| 
       84 
85 
     | 
    
         | 
| 
       85 
86 
     | 
    
         
             
                @tables = @tables.split(/,\s*/)
         
     | 
| 
         @@ -94,7 +95,7 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput 
     | 
|
| 
       94 
95 
     | 
    
         
             
                @tables -= new_tables
         
     | 
| 
       95 
96 
     | 
    
         
             
                $log.info "Not watching these tables: #{new_tables.join(", ")}"
         
     | 
| 
       96 
97 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
                table_meta = Mysql::TableMeta.new(db_opts.merge(tables: @tables))
         
     | 
| 
      
 98 
     | 
    
         
            +
                table_meta = Flydata::Mysql::TableMeta.new(@db_opts.merge(tables: @tables))
         
     | 
| 
       98 
99 
     | 
    
         | 
| 
       99 
100 
     | 
    
         
             
                table_revs = tables.inject({}) do |h, table_name|
         
     | 
| 
       100 
101 
     | 
    
         
             
                  h[table_name] = @sync_fm.table_rev(table_name)
         
     | 
| 
         @@ -145,6 +146,11 @@ EOS 
     | 
|
| 
       145 
146 
     | 
    
         | 
| 
       146 
147 
     | 
    
         
             
              def run
         
     | 
| 
       147 
148 
     | 
    
         
             
                @context.table_meta.update
         
     | 
| 
      
 149 
     | 
    
         
            +
                Flydata::Mysql::TableDdl.migrate_tables(@context.tables, @db_opts,
         
     | 
| 
      
 150 
     | 
    
         
            +
                                                        @context.sync_fm, @position_file,
         
     | 
| 
      
 151 
     | 
    
         
            +
                                                        @context) do |event|
         
     | 
| 
      
 152 
     | 
    
         
            +
                  @record_dispatcher.dispatch(event)
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
       148 
154 
     | 
    
         
             
                start_kodama(mysql_url) do |c|
         
     | 
| 
       149 
155 
     | 
    
         
             
                  c.binlog_position_file = @position_file
         
     | 
| 
       150 
156 
     | 
    
         
             
                  if @ssl_ca_path.to_s != '' && c.respond_to?(:ssl_ca=)
         
     | 
| 
         @@ -167,7 +173,7 @@ EOS 
     | 
|
| 
       167 
173 
     | 
    
         
             
              rescue => e
         
     | 
| 
       168 
174 
     | 
    
         
             
                # HACK: mysql-replication-listener has a network connection leak bug which doesn't release a connection
         
     | 
| 
       169 
175 
     | 
    
         
             
                # to MySQL.  Rather than fixing the bug, restarting the fluentd process for now.
         
     | 
| 
       170 
     | 
    
         
            -
                $log.warn "kodama died with an error.  Restart the process after #{@retry_wait} seconds.  error: #{e.class.to_s} '#{e.to_s}'"
         
     | 
| 
      
 176 
     | 
    
         
            +
                $log.warn "kodama died with an error.  Restart the process after #{@retry_wait} seconds.  error: #{e.class.to_s} '#{e.to_s}'\n#{e.backtrace.join("\n")}"
         
     | 
| 
       171 
177 
     | 
    
         
             
                sleep @retry_wait
         
     | 
| 
       172 
178 
     | 
    
         
             
                Process.kill(:HUP, Process.ppid) # Send SIGHUP to the supervisor to restart fluentd
         
     | 
| 
       173 
179 
     | 
    
         
             
              rescue SignalException
         
     | 
| 
         @@ -260,16 +266,15 @@ class Client 
     | 
|
| 
       260 
266 
     | 
    
         
             
                    def rows
         
     | 
| 
       261 
267 
     | 
    
         
             
                      rs = super
         
     | 
| 
       262 
268 
     | 
    
         
             
                      # HACK
         
     | 
| 
       263 
     | 
    
         
            -
                      #  
     | 
| 
       264 
     | 
    
         
            -
                      #  
     | 
| 
       265 
     | 
    
         
            -
                      # But how?
         
     | 
| 
      
 269 
     | 
    
         
            +
                      # Set string encoding to binary because ruby-binlog has no knowledge
         
     | 
| 
      
 270 
     | 
    
         
            +
                      # about the encoding of strings.
         
     | 
| 
       266 
271 
     | 
    
         
             
                      new_rs = rs.collect {|row|
         
     | 
| 
       267 
272 
     | 
    
         
             
                                 row.collect{|value|
         
     | 
| 
       268 
273 
     | 
    
         
             
                                   if (value.kind_of?(Array))
         
     | 
| 
       269 
274 
     | 
    
         
             
                                     # Update has two rows in it
         
     | 
| 
       270 
     | 
    
         
            -
                                     value.collect{|val| val.force_encoding(" 
     | 
| 
      
 275 
     | 
    
         
            +
                                     value.collect{|val| val.force_encoding("binary") if val.respond_to?(:force_encoding); val}
         
     | 
| 
       271 
276 
     | 
    
         
             
                                   else
         
     | 
| 
       272 
     | 
    
         
            -
                                     value.force_encoding(" 
     | 
| 
      
 277 
     | 
    
         
            +
                                     value.force_encoding("binary") if value.respond_to?(:force_encoding); value
         
     | 
| 
       273 
278 
     | 
    
         
             
                                   end
         
     | 
| 
       274 
279 
     | 
    
         
             
                                 }
         
     | 
| 
       275 
280 
     | 
    
         
             
                               }
         
     | 
| 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'fluent/plugin/in_mysql_binlog'
         
     | 
| 
       2 
2 
     | 
    
         
             
            require 'binlog'
         
     | 
| 
       3 
     | 
    
         
            -
            require 'flydata/ 
     | 
| 
      
 3 
     | 
    
         
            +
            require 'flydata/mysql/binlog_position'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'flydata-core/record/record'
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
6 
     | 
    
         
             
            module Mysql
         
     | 
| 
       6 
7 
     | 
    
         
             
              class BinlogRecordHandler
         
     | 
| 
         @@ -10,6 +11,7 @@ module Mysql 
     | 
|
| 
       10 
11 
     | 
    
         
             
                RESPECT_ORDER = :respect_order
         
     | 
| 
       11 
12 
     | 
    
         
             
                SRC_POS = :src_pos
         
     | 
| 
       12 
13 
     | 
    
         
             
                TABLE_REV = :table_rev
         
     | 
| 
      
 14 
     | 
    
         
            +
                V = :v  # FlyData record format version
         
     | 
| 
       13 
15 
     | 
    
         | 
| 
       14 
16 
     | 
    
         
             
                def initialize(context)
         
     | 
| 
       15 
17 
     | 
    
         
             
                  @context = context
         
     | 
| 
         @@ -21,7 +23,7 @@ module Mysql 
     | 
|
| 
       21 
23 
     | 
    
         
             
                  @context.tables.each do |table_name|
         
     | 
| 
       22 
24 
     | 
    
         
             
                    table_binlog_content = @context.sync_fm.get_table_binlog_pos(table_name)
         
     | 
| 
       23 
25 
     | 
    
         
             
                    if table_binlog_content
         
     | 
| 
       24 
     | 
    
         
            -
                      @table_binlog_pos[table_name] = BinLogPosition.new(table_binlog_content)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      @table_binlog_pos[table_name] = Flydata::Mysql::BinLogPosition.new(table_binlog_content)
         
     | 
| 
       25 
27 
     | 
    
         
             
                    end
         
     | 
| 
       26 
28 
     | 
    
         
             
                  end
         
     | 
| 
       27 
29 
     | 
    
         
             
                end
         
     | 
| 
         @@ -40,7 +42,7 @@ module Mysql 
     | 
|
| 
       40 
42 
     | 
    
         
             
                  acceptable = @context.tables.include?(table)
         
     | 
| 
       41 
43 
     | 
    
         | 
| 
       42 
44 
     | 
    
         
             
                  if acceptable and @table_binlog_pos[record['table_name']]
         
     | 
| 
       43 
     | 
    
         
            -
                    if @table_binlog_pos[record['table_name']] >= BinLogPosition.new(
         
     | 
| 
      
 45 
     | 
    
         
            +
                    if @table_binlog_pos[record['table_name']] >= Flydata::Mysql::BinLogPosition.new(
         
     | 
| 
       44 
46 
     | 
    
         
             
                      "#{@context.current_binlog_file}\t#{record['next_position'] - record['event_length']}")
         
     | 
| 
       45 
47 
     | 
    
         
             
                      acceptable = false
         
     | 
| 
       46 
48 
     | 
    
         
             
                    else
         
     | 
| 
         @@ -84,6 +86,7 @@ module Mysql 
     | 
|
| 
       84 
86 
     | 
    
         
             
                    r[TABLE_NAME] = table
         
     | 
| 
       85 
87 
     | 
    
         
             
                    r[SRC_POS] = "#{@context.current_binlog_file}\t#{position}"
         
     | 
| 
       86 
88 
     | 
    
         
             
                    r[TABLE_REV] = table_rev
         
     | 
| 
      
 89 
     | 
    
         
            +
                    r[V] = FlydataCore::Record::V2
         
     | 
| 
       87 
90 
     | 
    
         
             
                  end
         
     | 
| 
       88 
91 
     | 
    
         | 
| 
       89 
92 
     | 
    
         
             
                  # Use binlog's timestamp
         
     | 
| 
         @@ -49,7 +49,7 @@ module Mysql 
     | 
|
| 
       49 
49 
     | 
    
         
             
                      row_values = { ROW => row }
         
     | 
| 
       50 
50 
     | 
    
         
             
                      row_values = yield(row) if block_given?  # Give the caller a chance to generate the correct row values
         
     | 
| 
       51 
51 
     | 
    
         
             
                      row_types = row_values.keys
         
     | 
| 
       52 
     | 
    
         
            -
                      row_kinds = row_types.inject({}) {|m, k| m[k] = convert_to_flydata_row_format( 
     | 
| 
      
 52 
     | 
    
         
            +
                      row_kinds = row_types.inject({}) {|m, k| m[k] = convert_to_flydata_row_format(row_values[k]); m}
         
     | 
| 
       53 
53 
     | 
    
         
             
                      encode_signless_integer(row_kinds, record["columns"], row_types)
         
     | 
| 
       54 
54 
     | 
    
         
             
                      row_kinds
         
     | 
| 
       55 
55 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -57,22 +57,29 @@ module Mysql 
     | 
|
| 
       57 
57 
     | 
    
         
             
                  end
         
     | 
| 
       58 
58 
     | 
    
         
             
                end
         
     | 
| 
       59 
59 
     | 
    
         | 
| 
       60 
     | 
    
         
            -
                def convert_to_flydata_row_format( 
     | 
| 
      
 60 
     | 
    
         
            +
                def convert_to_flydata_row_format(row)
         
     | 
| 
       61 
61 
     | 
    
         
             
                  row.each.with_index(1).inject({}) do |h, (v, i)|
         
     | 
| 
       62 
62 
     | 
    
         
             
                    if v.kind_of?(String)
         
     | 
| 
       63 
     | 
    
         
            -
                      v = encode_row_value( 
     | 
| 
      
 63 
     | 
    
         
            +
                      v = encode_row_value(v)
         
     | 
| 
      
 64 
     | 
    
         
            +
                      if v.encoding == Encoding::BINARY
         
     | 
| 
      
 65 
     | 
    
         
            +
                        h['attrs'] ||= {}
         
     | 
| 
      
 66 
     | 
    
         
            +
                        h['attrs'][i.to_s] ||= {}
         
     | 
| 
      
 67 
     | 
    
         
            +
                        h['attrs'][i.to_s]['enc'] = 'b'
         
     | 
| 
      
 68 
     | 
    
         
            +
                      end
         
     | 
| 
       64 
69 
     | 
    
         
             
                    end
         
     | 
| 
       65 
70 
     | 
    
         
             
                    h[i.to_s] = v
         
     | 
| 
       66 
71 
     | 
    
         
             
                    h
         
     | 
| 
       67 
72 
     | 
    
         
             
                  end
         
     | 
| 
       68 
73 
     | 
    
         
             
                end
         
     | 
| 
       69 
74 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
                def encode_row_value( 
     | 
| 
       71 
     | 
    
         
            -
                   
     | 
| 
       72 
     | 
    
         
            -
             
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
                    value. 
     | 
| 
      
 75 
     | 
    
         
            +
                def encode_row_value(value)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  value.force_encoding('utf-8')
         
     | 
| 
      
 77 
     | 
    
         
            +
                  unless value.valid_encoding?
         
     | 
| 
      
 78 
     | 
    
         
            +
                    # value can't be treated as UTF-8.  Convert to Flydata binary format.
         
     | 
| 
      
 79 
     | 
    
         
            +
                    value = "0x" + value.unpack('c*').collect{|c| "%02X" % (c < 0 ? c + 256 : c)}.join('')
         
     | 
| 
      
 80 
     | 
    
         
            +
                    value.force_encoding('binary')  # setting to 'binary' to differentiate from UTF-8.  Downstream code may rely on it.
         
     | 
| 
       75 
81 
     | 
    
         
             
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                  value
         
     | 
| 
       76 
83 
     | 
    
         
             
                end
         
     | 
| 
       77 
84 
     | 
    
         | 
| 
       78 
85 
     | 
    
         
             
                def encode_signless_integer(record, column_types, row_types)
         
     | 
| 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'mysql2'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'flydata-core/table_def/mysql_table_def'
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
      
 4 
     | 
    
         
            +
            module Flydata
         
     | 
| 
       3 
5 
     | 
    
         
             
            module Mysql
         
     | 
| 
       4 
6 
     | 
    
         
             
              class TableMeta
         
     | 
| 
       5 
7 
     | 
    
         
             
                MANDATORY_OPTS = [
         
     | 
| 
         @@ -41,45 +43,14 @@ EOT 
     | 
|
| 
       41 
43 
     | 
    
         
             
                    database: @database, tables: @tables.collect{|t| "'#{t}'"}.join(',') }
         
     | 
| 
       42 
44 
     | 
    
         
             
                  columns = conn.query(sql)
         
     | 
| 
       43 
45 
     | 
    
         
             
                  columns.collect do |col|
         
     | 
| 
       44 
     | 
    
         
            -
                     
     | 
| 
      
 46 
     | 
    
         
            +
                    mysql_charset = col['character_set_name']
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @table_meta[col['table_name'].to_sym][:encoding] = FlydataCore::TableDef::MysqlTableDef.ruby_encoding(mysql_charset)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @table_meta[col['table_name'].to_sym][:mysql_charset] = mysql_charset
         
     | 
| 
       45 
49 
     | 
    
         
             
                  end
         
     | 
| 
       46 
50 
     | 
    
         
             
                ensure
         
     | 
| 
       47 
51 
     | 
    
         
             
                  conn.close rescue nil if conn
         
     | 
| 
       48 
52 
     | 
    
         
             
                end
         
     | 
| 
       49 
53 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                # Charset naming conversion rule. mysql => ruby
         
     | 
| 
       51 
     | 
    
         
            -
                #
         
     | 
| 
       52 
     | 
    
         
            -
                # mysql
         
     | 
| 
       53 
     | 
    
         
            -
                #   http://dev.mysql.com/doc/refman/5.6/en/charset-charsets.html
         
     | 
| 
       54 
     | 
    
         
            -
                # mysql(supported CJK character sets)
         
     | 
| 
       55 
     | 
    
         
            -
                #   http://dev.mysql.com/doc/refman/5.6/en/faqs-cjk.html#qandaitem-A-11-1-1
         
     | 
| 
       56 
     | 
    
         
            -
                # For ruby, you can see encoding list with "Encoding.list"
         
     | 
| 
       57 
     | 
    
         
            -
                CHARSET_ENCODE_RULE = {
         
     | 
| 
       58 
     | 
    
         
            -
                  'ascii' => nil, # 'ASCII-8BIT', (not need to be encoded)
         
     | 
| 
       59 
     | 
    
         
            -
                  'utf8' => nil, # 'UTF-8', (not need to be encoded)
         
     | 
| 
       60 
     | 
    
         
            -
                  'utf8mb4' => nil, # 'UTF-8', (not need to be encoded)
         
     | 
| 
       61 
     | 
    
         
            -
                  'utf16' => 'UTF-16',
         
     | 
| 
       62 
     | 
    
         
            -
                  'utf32' => 'UTF-32',
         
     | 
| 
       63 
     | 
    
         
            -
                  'latin1' => 'ISO-8859-1',
         
     | 
| 
       64 
     | 
    
         
            -
                  'latin2' => 'ISO-8859-2',
         
     | 
| 
       65 
     | 
    
         
            -
                  'big5' => 'Big5',
         
     | 
| 
       66 
     | 
    
         
            -
                  'cp932' => 'CP932',
         
     | 
| 
       67 
     | 
    
         
            -
                  'eucjpms' => 'eucJP-ms',
         
     | 
| 
       68 
     | 
    
         
            -
                  'euckr' => 'EUC-KR',
         
     | 
| 
       69 
     | 
    
         
            -
                  'gb18030' => 'GB18030',
         
     | 
| 
       70 
     | 
    
         
            -
                  'gb2312' => 'GB2312',
         
     | 
| 
       71 
     | 
    
         
            -
                  'gbk' => 'GBK',
         
     | 
| 
       72 
     | 
    
         
            -
                  'sjis' => 'Shift_JIS',
         
     | 
| 
       73 
     | 
    
         
            -
                  'ujis' => 'EUC-JP',
         
     | 
| 
       74 
     | 
    
         
            -
                }
         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
                def get_ruby_charset_name(mysql_charset)
         
     | 
| 
       77 
     | 
    
         
            -
                  return nil if mysql_charset.to_s.empty?
         
     | 
| 
       78 
     | 
    
         
            -
                  raise "Unsupported charset:#{mysql_charset}." unless CHARSET_ENCODE_RULE.has_key?(mysql_charset)
         
     | 
| 
       79 
     | 
    
         
            -
                  charset = CHARSET_ENCODE_RULE[mysql_charset]
         
     | 
| 
       80 
     | 
    
         
            -
                  return charset
         
     | 
| 
       81 
     | 
    
         
            -
                end
         
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
54 
     | 
    
         
             
                # Return table meta
         
     | 
| 
       84 
55 
     | 
    
         
             
                #   :character_set_name
         
     | 
| 
       85 
56 
     | 
    
         
             
                def [](table_name)
         
     | 
| 
         @@ -87,3 +58,4 @@ EOT 
     | 
|
| 
       87 
58 
     | 
    
         
             
                end
         
     | 
| 
       88 
59 
     | 
    
         
             
              end
         
     | 
| 
       89 
60 
     | 
    
         
             
            end
         
     | 
| 
      
 61 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,5 +1,8 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'open3'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'flydata-core/table_def/mysql_table_def'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
       1 
4 
     | 
    
         
             
            module Flydata
         
     | 
| 
       2 
     | 
    
         
            -
              module  
     | 
| 
      
 5 
     | 
    
         
            +
              module Mysql
         
     | 
| 
       3 
6 
     | 
    
         
             
                class MysqlUtil
         
     | 
| 
       4 
7 
     | 
    
         
             
                  DEFAULT_MYSQL_CMD_OPTION = "--default-character-set=utf8 --protocol=tcp"
         
     | 
| 
       5 
8 
     | 
    
         | 
| 
         @@ -73,6 +76,36 @@ module Flydata 
     | 
|
| 
       73 
76 
     | 
    
         
             
                    generate_mysql_cmd(opt)
         
     | 
| 
       74 
77 
     | 
    
         
             
                  end
         
     | 
| 
       75 
78 
     | 
    
         | 
| 
      
 79 
     | 
    
         
            +
                  def self.each_mysql_tabledef(tables, option)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    command = generate_mysql_ddl_dump_cmd(option.merge(tables: tables))
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                    create_opt = {}
         
     | 
| 
      
 83 
     | 
    
         
            +
                    if option.has_key?(:skip_primary_key_check)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      create_opt[:skip_primary_key_check] = option[:skip_primary_key_check]
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    Open3.popen3(command) do |stdin, stdout, stderr|
         
     | 
| 
      
 88 
     | 
    
         
            +
                      stdin.close
         
     | 
| 
      
 89 
     | 
    
         
            +
                      stdout.set_encoding("utf-8", "utf-8") # mysqldump output must be in UTF-8
         
     | 
| 
      
 90 
     | 
    
         
            +
                      create_flydata_ctl_table = true
         
     | 
| 
      
 91 
     | 
    
         
            +
                      while !stdout.eof?
         
     | 
| 
      
 92 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 93 
     | 
    
         
            +
                          mysql_tabledef = FlydataCore::TableDef::MysqlTableDef.create(stdout, create_opt)
         
     | 
| 
      
 94 
     | 
    
         
            +
                          break if mysql_tabledef.nil?
         
     | 
| 
      
 95 
     | 
    
         
            +
                          yield(mysql_tabledef, nil)
         
     | 
| 
      
 96 
     | 
    
         
            +
                        rescue FlydataCore::TableDefError=> e
         
     | 
| 
      
 97 
     | 
    
         
            +
                          yield(nil, e)
         
     | 
| 
      
 98 
     | 
    
         
            +
                        end
         
     | 
| 
      
 99 
     | 
    
         
            +
                      end
         
     | 
| 
      
 100 
     | 
    
         
            +
                      errors = ""
         
     | 
| 
      
 101 
     | 
    
         
            +
                      while !stderr.eof?
         
     | 
| 
      
 102 
     | 
    
         
            +
                        line = stderr.gets.gsub('mysqldump: ', '')
         
     | 
| 
      
 103 
     | 
    
         
            +
                        errors << line unless /Warning: Using a password on the command line interface can be insecure./ === line
         
     | 
| 
      
 104 
     | 
    
         
            +
                      end
         
     | 
| 
      
 105 
     | 
    
         
            +
                      raise errors unless errors.empty?
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
       76 
109 
     | 
    
         
             
                  private
         
     | 
| 
       77 
110 
     | 
    
         | 
| 
       78 
111 
     | 
    
         
             
                  def self.convert_keys_to_sym(hash)
         
     | 
| 
         @@ -0,0 +1,118 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'flydata/mysql/binlog_position'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'flydata/mysql/mysql_util'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Flydata
         
     | 
| 
      
 5 
     | 
    
         
            +
            module Mysql
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            class TableDdl
         
     | 
| 
      
 8 
     | 
    
         
            +
              VERSION0 = 0 # the version where no .generated_ddl file was generated.
         
     | 
| 
      
 9 
     | 
    
         
            +
                           # Therefore, this version never shows up in anywhere.
         
     | 
| 
      
 10 
     | 
    
         
            +
              VERSION1 = 1 # the version which doesn't handle server side encoding support.
         
     | 
| 
      
 11 
     | 
    
         
            +
              VERSION2 = 2 # the version with server side encoding support, migrated from
         
     | 
| 
      
 12 
     | 
    
         
            +
                           # the previous versions.  Format/functionality-wise, it's the
         
     | 
| 
      
 13 
     | 
    
         
            +
                           # same as Version 3.
         
     | 
| 
      
 14 
     | 
    
         
            +
              VERSION3 = 3 # the version with server side encoding support, generated by
         
     | 
| 
      
 15 
     | 
    
         
            +
                           # sync:generated_table_ddl command.
         
     | 
| 
      
 16 
     | 
    
         
            +
              VERSION4 = 4 # the version with server side encoding support, generated by
         
     | 
| 
      
 17 
     | 
    
         
            +
                           # the auto-generated CREATE TABLE event.
         
     | 
| 
      
 18 
     | 
    
         
            +
              VERSION = VERSION3
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def self.migrate_tables(tables, mysql_opts, sync_fm, position_file, context,
         
     | 
| 
      
 21 
     | 
    
         
            +
                                      &block)
         
     | 
| 
      
 22 
     | 
    
         
            +
                migrate_to_v2(tables, mysql_opts, sync_fm, position_file, context, &block)
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              private
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              V2_TARGET_VERSION = VERSION2
         
     | 
| 
      
 28 
     | 
    
         
            +
              EVENT_TYPE = 'Query'
         
     | 
| 
      
 29 
     | 
    
         
            +
              ALTER_TABLE_CHARSET_SQL = <<EOS.strip
         
     | 
| 
      
 30 
     | 
    
         
            +
            ALTER TABLE `%s`.`%s` CHARACTER SET = %s;
         
     | 
| 
      
 31 
     | 
    
         
            +
            EOS
         
     | 
| 
      
 32 
     | 
    
         
            +
              CHANGE_COLUMN_SQL = <<EOS.strip
         
     | 
| 
      
 33 
     | 
    
         
            +
            CHANGE COLUMN `%s` %s
         
     | 
| 
      
 34 
     | 
    
         
            +
            EOS
         
     | 
| 
      
 35 
     | 
    
         
            +
              ALTER_TABLE_SQL = <<EOS.strip
         
     | 
| 
      
 36 
     | 
    
         
            +
            ALTER TABLE `%s`.`%s` %s;
         
     | 
| 
      
 37 
     | 
    
         
            +
            EOS
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def self.migrate_to_v2(tables, mysql_opts, sync_fm, position_file, context)
         
     | 
| 
      
 40 
     | 
    
         
            +
                database = mysql_opts[:database]
         
     | 
| 
      
 41 
     | 
    
         
            +
                mysql_tabledefs = nil
         
     | 
| 
      
 42 
     | 
    
         
            +
                original_binlog_file = nil
         
     | 
| 
      
 43 
     | 
    
         
            +
                binlog_pos = nil
         
     | 
| 
      
 44 
     | 
    
         
            +
                event_size = 0 # so that the next binlog position becomes the master binlog position
         
     | 
| 
      
 45 
     | 
    
         
            +
                tables.each do |table|
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # check the current ddl version
         
     | 
| 
      
 47 
     | 
    
         
            +
                  contents = sync_fm.load_generated_ddl([table])
         
     | 
| 
      
 48 
     | 
    
         
            +
                  version = contents.first.to_i
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # return if no need to migrate
         
     | 
| 
      
 50 
     | 
    
         
            +
                  next if version >= V2_TARGET_VERSION
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  if mysql_tabledefs.nil?
         
     | 
| 
      
 53 
     | 
    
         
            +
                    MysqlUtil.each_mysql_tabledef(tables, mysql_opts) do |mysql_tabledef, error|
         
     | 
| 
      
 54 
     | 
    
         
            +
                      raise error if error
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                      mysql_tabledefs ||= {}
         
     | 
| 
      
 57 
     | 
    
         
            +
                      mysql_tabledefs[mysql_tabledef.table_name] = mysql_tabledef
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  mysql_tabledef = mysql_tabledefs[table]
         
     | 
| 
      
 61 
     | 
    
         
            +
                  if binlog_pos.nil?
         
     | 
| 
      
 62 
     | 
    
         
            +
                    # get binlog position
         
     | 
| 
      
 63 
     | 
    
         
            +
                    binlog_pos = BinLogPosition.new(File.open(position_file){|f| f.read })
         
     | 
| 
      
 64 
     | 
    
         
            +
                    original_binlog_file = context.current_binlog_file
         
     | 
| 
      
 65 
     | 
    
         
            +
                    context.current_binlog_file = binlog_pos.file
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # get charset
         
     | 
| 
      
 68 
     | 
    
         
            +
                  charset = mysql_tabledef.default_charset_mysql
         
     | 
| 
      
 69 
     | 
    
         
            +
                  # construct queries
         
     | 
| 
      
 70 
     | 
    
         
            +
                  # column charset
         
     | 
| 
      
 71 
     | 
    
         
            +
                  column_event = nil
         
     | 
| 
      
 72 
     | 
    
         
            +
                  at_subquery = mysql_tabledef.column_def.select{|col, coldef| /CHARACTER SET/.match(coldef) }.collect{|col, coldef| CHANGE_COLUMN_SQL % [col, coldef]}.join(",")
         
     | 
| 
      
 73 
     | 
    
         
            +
                  unless at_subquery.empty?
         
     | 
| 
      
 74 
     | 
    
         
            +
                    column_query = ALTER_TABLE_SQL % [database, table, at_subquery]
         
     | 
| 
      
 75 
     | 
    
         
            +
                    column_event = QueryEvent.new(EVENT_TYPE, database, table,
         
     | 
| 
      
 76 
     | 
    
         
            +
                                                  binlog_pos.pos, event_size, column_query,
         
     | 
| 
      
 77 
     | 
    
         
            +
                                                  Time.now.to_i)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    yield column_event
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                  # table charset
         
     | 
| 
      
 81 
     | 
    
         
            +
                  table_query = ALTER_TABLE_CHARSET_SQL % [database, table, charset]
         
     | 
| 
      
 82 
     | 
    
         
            +
                  # create events and yield
         
     | 
| 
      
 83 
     | 
    
         
            +
                  table_event = QueryEvent.new(EVENT_TYPE, database, table, binlog_pos.pos,
         
     | 
| 
      
 84 
     | 
    
         
            +
                                               event_size, table_query, Time.now.to_i)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  yield table_event
         
     | 
| 
      
 86 
     | 
    
         
            +
                  $log.info "migrating table `#{table}` from version #{version} to version #{V2_TARGET_VERSION}.  Table event #{table_event} Column event #{column_event}"
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  # update generated_ddl
         
     | 
| 
      
 89 
     | 
    
         
            +
                  sync_fm.save_generated_ddl([table], V2_TARGET_VERSION.to_s)
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
                context.current_binlog_file = original_binlog_file if binlog_pos
         
     | 
| 
      
 92 
     | 
    
         
            +
              end
         
     | 
| 
      
 93 
     | 
    
         
            +
            end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
            # mimics RubyBinlog::QueryEvent
         
     | 
| 
      
 96 
     | 
    
         
            +
            class QueryEvent
         
     | 
| 
      
 97 
     | 
    
         
            +
              def initialize(event_type, database_name, event_table_name, next_position,
         
     | 
| 
      
 98 
     | 
    
         
            +
                             event_length, query, timestamp)
         
     | 
| 
      
 99 
     | 
    
         
            +
                @event_type = event_type
         
     | 
| 
      
 100 
     | 
    
         
            +
                @database_name = database_name
         
     | 
| 
      
 101 
     | 
    
         
            +
                @event_table_name = event_table_name
         
     | 
| 
      
 102 
     | 
    
         
            +
                @next_position = next_position
         
     | 
| 
      
 103 
     | 
    
         
            +
                @event_length = event_length
         
     | 
| 
      
 104 
     | 
    
         
            +
                @query = query
         
     | 
| 
      
 105 
     | 
    
         
            +
                @timestamp = timestamp
         
     | 
| 
      
 106 
     | 
    
         
            +
              end
         
     | 
| 
      
 107 
     | 
    
         
            +
              attr_reader :event_type, :database_name, :event_table_name, :next_position,
         
     | 
| 
      
 108 
     | 
    
         
            +
                          :event_length, :query, :timestamp
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
              def to_s
         
     | 
| 
      
 111 
     | 
    
         
            +
                <<EOS
         
     | 
| 
      
 112 
     | 
    
         
            +
            event_type;#{event_type} database_name:#{database_name} event_table_name:#{event_table_name} next_position:#{next_position} event_length:#{event_length} timestamp:#{timestamp} query:#{query}
         
     | 
| 
      
 113 
     | 
    
         
            +
            EOS
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
            end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
            end
         
     | 
| 
      
 118 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,5 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'fiber'
         
     | 
| 
       2 
     | 
    
         
            -
            require 'flydata/ 
     | 
| 
      
 2 
     | 
    
         
            +
            require 'flydata/mysql/mysql_util'
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            module Flydata
         
     | 
| 
       5 
5 
     | 
    
         
             
              module Parser
         
     | 
| 
         @@ -73,16 +73,16 @@ EOS 
     | 
|
| 
       73 
73 
     | 
    
         | 
| 
       74 
74 
     | 
    
         
             
                      begin
         
     | 
| 
       75 
75 
     | 
    
         
             
                        # create pipe for callback function
         
     | 
| 
       76 
     | 
    
         
            -
                        rd_io, wr_io = IO.pipe("utf-8")
         
     | 
| 
      
 76 
     | 
    
         
            +
                        rd_io, wr_io = IO.pipe("utf-8", "utf-8")
         
     | 
| 
       77 
77 
     | 
    
         
             
                        wr_io.sync = true
         
     | 
| 
       78 
     | 
    
         
            -
                        wr_io.set_encoding("utf-8")
         
     | 
| 
      
 78 
     | 
    
         
            +
                        wr_io.set_encoding("utf-8", "utf-8")
         
     | 
| 
       79 
79 
     | 
    
         
             
                        rd_io.extend(DumpStreamIO)
         
     | 
| 
       80 
80 
     | 
    
         
             
                        binlog_pos = nil
         
     | 
| 
       81 
81 
     | 
    
         | 
| 
       82 
82 
     | 
    
         
             
                        # start mysqldump
         
     | 
| 
       83 
83 
     | 
    
         
             
                        Open3.popen3 dump_cmd do |cmd_in, cmd_out, cmd_err, wait_thr|
         
     | 
| 
       84 
84 
     | 
    
         
             
                          cmd_in.close_write
         
     | 
| 
       85 
     | 
    
         
            -
                          cmd_out.set_encoding("utf-8") # mysqldump output must be in UTF-8
         
     | 
| 
      
 85 
     | 
    
         
            +
                          cmd_out.set_encoding("utf-8", "utf-8") # mysqldump output must be in UTF-8
         
     | 
| 
       86 
86 
     | 
    
         | 
| 
       87 
87 
     | 
    
         
             
                          first_line = cmd_out.gets # wait until first line comes
         
     | 
| 
       88 
88 
     | 
    
         
             
                          binfile, pos = table_locker.resume
         
     | 
| 
         @@ -135,7 +135,7 @@ EOS 
     | 
|
| 
       135 
135 
     | 
    
         
             
                    end
         
     | 
| 
       136 
136 
     | 
    
         | 
| 
       137 
137 
     | 
    
         
             
                    def generate_dump_cmd(conf, file_path = nil)
         
     | 
| 
       138 
     | 
    
         
            -
                       
     | 
| 
      
 138 
     | 
    
         
            +
                      Flydata::Mysql::MysqlUtil.generate_mysqldump_without_master_data_cmd(conf.merge(result_file: file_path))
         
     | 
| 
       139 
139 
     | 
    
         
             
                    end
         
     | 
| 
       140 
140 
     | 
    
         | 
| 
       141 
141 
     | 
    
         
             
                    private
         
     |