flydata 0.6.14 → 0.7.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 +4 -4
- data/VERSION +1 -1
- data/flydata-core/Gemfile +1 -0
- data/flydata-core/Gemfile.lock +5 -0
- data/flydata-core/lib/flydata-core/errors.rb +4 -2
- data/flydata-core/lib/flydata-core/mysql/binlog_pos.rb +4 -0
- data/flydata-core/lib/flydata-core/postgresql/compatibility_checker.rb +119 -0
- data/flydata-core/lib/flydata-core/postgresql/config.rb +58 -0
- data/flydata-core/lib/flydata-core/postgresql/pg_client.rb +170 -0
- data/flydata-core/lib/flydata-core/postgresql/snapshot.rb +49 -0
- data/flydata-core/lib/flydata-core/postgresql/source_pos.rb +71 -10
- data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +1 -1
- data/flydata-core/lib/flydata-core/table_def/postgresql_table_def.rb +76 -17
- data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +59 -10
- data/flydata-core/spec/mysql/binlog_pos_spec.rb +10 -2
- data/flydata-core/spec/postgresql/compatibility_checker_spec.rb +148 -0
- data/flydata-core/spec/postgresql/config_spec.rb +85 -0
- data/flydata-core/spec/postgresql/pg_client_spec.rb +195 -0
- data/flydata-core/spec/postgresql/snapshot_spec.rb +55 -0
- data/flydata-core/spec/postgresql/source_pos_spec.rb +70 -8
- data/flydata-core/spec/table_def/postgresql_table_def_spec.rb +80 -19
- data/flydata-core/spec/table_def/redshift_table_def_spec.rb +211 -14
- data/flydata.gemspec +0 -0
- data/lib/flydata.rb +1 -0
- data/lib/flydata/command/sender.rb +10 -7
- data/lib/flydata/command/sync.rb +4 -1
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/base.rb +1 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/fluent_log_ext.rb +73 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync.rb +35 -10
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based.rb +29 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based.rb +26 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/preference.rb +29 -13
- data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +10 -18
- data/lib/flydata/fluent-plugins/in_postgresql_query_based_flydata.rb +64 -0
- data/lib/flydata/helpers.rb +1 -3
- data/lib/flydata/plugin_support/context.rb +14 -2
- data/lib/flydata/plugin_support/source_position_file.rb +35 -0
- data/lib/flydata/plugin_support/sync_record_emittable.rb +2 -1
- data/lib/flydata/query_based_sync/client.rb +101 -0
- data/lib/flydata/query_based_sync/record_size_estimator.rb +39 -0
- data/lib/flydata/query_based_sync/resource_requester.rb +70 -0
- data/lib/flydata/query_based_sync/response.rb +122 -0
- data/lib/flydata/query_based_sync/response_handler.rb +30 -0
- data/lib/flydata/source/sync_generate_table_ddl.rb +1 -1
- data/lib/flydata/source_mysql/plugin_support/binlog_record_dispatcher.rb +2 -2
- data/lib/flydata/source_mysql/plugin_support/binlog_record_handler.rb +3 -9
- data/lib/flydata/source_mysql/plugin_support/context.rb +26 -2
- data/lib/flydata/source_mysql/plugin_support/source_position_file.rb +14 -0
- data/lib/flydata/source_mysql/table_ddl.rb +3 -3
- data/lib/flydata/source_mysql/{plugin_support/table_meta.rb → table_meta.rb} +3 -10
- data/lib/flydata/source_postgresql/generate_source_dump.rb +44 -63
- data/lib/flydata/source_postgresql/parse_dump_and_send.rb +2 -0
- data/lib/flydata/source_postgresql/plugin_support/context.rb +13 -0
- data/lib/flydata/source_postgresql/plugin_support/source_position_file.rb +14 -0
- data/lib/flydata/source_postgresql/query_based_sync/client.rb +16 -0
- data/lib/flydata/source_postgresql/query_based_sync/diff_query_generator.rb +135 -0
- data/lib/flydata/source_postgresql/query_based_sync/resource_requester.rb +86 -0
- data/lib/flydata/source_postgresql/query_based_sync/response.rb +12 -0
- data/lib/flydata/source_postgresql/query_based_sync/response_handler.rb +12 -0
- data/lib/flydata/source_postgresql/sync_generate_table_ddl.rb +25 -79
- data/lib/flydata/source_postgresql/table_meta.rb +168 -0
- data/lib/flydata/sync_file_manager.rb +5 -5
- data/lib/flydata/table_meta.rb +19 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_context.rb +85 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based_shared_examples.rb +36 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based_shared_examples.rb +37 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_shared_examples.rb +67 -0
- data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +119 -96
- data/spec/flydata/fluent-plugins/in_postgresql_query_based_flydata_spec.rb +82 -0
- data/spec/flydata/fluent-plugins/sync_source_plugin_context.rb +29 -0
- data/spec/flydata/plugin_support/context_spec.rb +37 -3
- data/spec/flydata/query_based_sync/client_spec.rb +79 -0
- data/spec/flydata/query_based_sync/query_based_sync_context.rb +116 -0
- data/spec/flydata/query_based_sync/record_size_estimator_spec.rb +54 -0
- data/spec/flydata/query_based_sync/resource_requester_spec.rb +58 -0
- data/spec/flydata/query_based_sync/response_handler_spec.rb +36 -0
- data/spec/flydata/query_based_sync/response_spec.rb +157 -0
- data/spec/flydata/source_mysql/plugin_support/context_spec.rb +7 -1
- data/spec/flydata/source_mysql/plugin_support/dml_record_handler_spec.rb +2 -15
- data/spec/flydata/source_mysql/plugin_support/drop_database_query_handler_spec.rb +1 -1
- data/spec/flydata/source_mysql/plugin_support/shared_query_handler_context.rb +12 -11
- data/spec/flydata/source_mysql/plugin_support/source_position_file_spec.rb +53 -0
- data/spec/flydata/source_mysql/plugin_support/truncate_query_handler_spec.rb +1 -1
- data/spec/flydata/source_mysql/table_ddl_spec.rb +5 -5
- data/spec/flydata/source_mysql/{plugin_support/table_meta_spec.rb → table_meta_spec.rb} +6 -7
- data/spec/flydata/source_postgresql/generate_source_dump_spec.rb +165 -77
- data/spec/flydata/source_postgresql/query_based_sync/diff_query_generator_spec.rb +213 -0
- data/spec/flydata/source_postgresql/query_based_sync/query_based_sync_postgresql_context.rb +76 -0
- data/spec/flydata/source_postgresql/query_based_sync/resource_requester_spec.rb +70 -0
- data/spec/flydata/source_postgresql/table_meta_spec.rb +77 -0
- metadata +49 -6
- data/lib/flydata/source_mysql/plugin_support/binlog_position_file.rb +0 -23
- data/lib/flydata/source_postgresql/pg_client.rb +0 -43
|
@@ -18,13 +18,14 @@ module PluginSupport
|
|
|
18
18
|
# "records" : A record or records for emitting
|
|
19
19
|
# Each record needs to be Hash
|
|
20
20
|
# "options"
|
|
21
|
+
# type: : (required) type (insert, update, delete)
|
|
21
22
|
# tag : (optional) tag (default: @context.tag)
|
|
22
23
|
# timestamp : (optional) timestamp (default: current timestamp)
|
|
23
24
|
# src_pos : (required) source position (used for sync:repair)
|
|
24
25
|
# table : (optional) table name
|
|
25
26
|
# increment_table_rev : (optional) set true when incrementing table revision
|
|
26
27
|
def emit_sync_records(records, options)
|
|
27
|
-
return if records.nil? # skip
|
|
28
|
+
return if records.nil? || records.empty? # skip
|
|
28
29
|
records = [records] unless records.kind_of?(Array)
|
|
29
30
|
|
|
30
31
|
# Check options
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
require 'flydata-core/logger'
|
|
3
|
+
|
|
4
|
+
module Flydata
|
|
5
|
+
module QueryBasedSync
|
|
6
|
+
|
|
7
|
+
class Client
|
|
8
|
+
include FlydataCore::Logger
|
|
9
|
+
|
|
10
|
+
DEFAULT_FETCH_INTERVAL = 60 # 1 minute
|
|
11
|
+
DEFAULT_RETRY_INTERVAL = 30 # 1 minute
|
|
12
|
+
|
|
13
|
+
# params
|
|
14
|
+
# fetch_interval
|
|
15
|
+
# resource_names
|
|
16
|
+
def initialize(context)
|
|
17
|
+
@context = context
|
|
18
|
+
@resource_requester = self.class::RESOURCE_REQUESTER_CLASS.new(context)
|
|
19
|
+
@response_handler = self.class::RESPONSE_HANDLER_CLASS.new(context)
|
|
20
|
+
|
|
21
|
+
context.params.tap do |c|
|
|
22
|
+
@fetch_interval = c.nil? ? DEFAULT_FETCH_INTERVAL : c[:fetch_interval]
|
|
23
|
+
@retry_interval = c.nil? ? DEFAULT_RETRY_INTERVAL : c[:retry_interval]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_reader :context
|
|
28
|
+
attr_reader :resource_requester
|
|
29
|
+
attr_reader :response_handler
|
|
30
|
+
|
|
31
|
+
def start
|
|
32
|
+
raise "Already started - thread:#{@running_thread}" if @running_thread
|
|
33
|
+
raise "Already stop requested" if @stop_requested
|
|
34
|
+
@running_thread = Thread.current
|
|
35
|
+
run_loop
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def stop_request
|
|
39
|
+
@finish = true
|
|
40
|
+
if @running_thread && @running_thread.alive?
|
|
41
|
+
@running_thread.run
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stop_requested?
|
|
46
|
+
!!@finish
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def handle_response(response)
|
|
50
|
+
log_debug("Handling response", table_name: response.table_name, count:response.records.count,
|
|
51
|
+
new_snapshot: response.new_source_pos, base_snapshot: context.table_meta.current_snapshot)
|
|
52
|
+
@response_handler.handle(response)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def run_loop
|
|
58
|
+
begin
|
|
59
|
+
until stop_requested?
|
|
60
|
+
reset_log_context
|
|
61
|
+
run_once
|
|
62
|
+
break if stop_requested?
|
|
63
|
+
sleep @fetch_interval
|
|
64
|
+
end
|
|
65
|
+
rescue => e
|
|
66
|
+
#TODO: Introduce retryable error notions
|
|
67
|
+
log_error_with_backtrace("Unexpected error occured", error: e)
|
|
68
|
+
sleep @retry_interval
|
|
69
|
+
retry
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def run_once
|
|
74
|
+
resource_requester.start do |req| # open connection
|
|
75
|
+
context.tables.each do |table_name|
|
|
76
|
+
add_log_context_items(table_name: table_name)
|
|
77
|
+
return if stop_requested?
|
|
78
|
+
req.each_response(table_name) do |response|
|
|
79
|
+
return if stop_requested?
|
|
80
|
+
handle_response(response)
|
|
81
|
+
|
|
82
|
+
unless response.empty?
|
|
83
|
+
log_info("Emitted records", count: response.record_count, src_pos:response.new_source_pos)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
return if stop_requested?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
delete_log_context_item(:table_name)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Set the current snapshot to master(resume) binlog.pos file
|
|
94
|
+
context.cur_src_pos_file.save(context.table_meta.current_snapshot)
|
|
95
|
+
|
|
96
|
+
log_info("Updated source position -", resume_pos:context.table_meta.current_snapshot)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Flydata
|
|
2
|
+
module QueryBasedSync
|
|
3
|
+
|
|
4
|
+
class RecordSizeEstimator
|
|
5
|
+
# Data size for the static part of sent record
|
|
6
|
+
# {"row":{},"type":"update","respect_order":true,
|
|
7
|
+
# "src_pos":"10051:10051:\t10520:10052:\t{\"id\":\"1000000000\"}",
|
|
8
|
+
# "v":2,"seq":10000000,"table_name":"","table_rev":1}
|
|
9
|
+
BASE_DATA_SIZE = 162
|
|
10
|
+
|
|
11
|
+
def initialize(table_name, num_columns)
|
|
12
|
+
@base_record_size = calc_base_record_size(table_name, num_columns)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Calculate a record size for emitting
|
|
16
|
+
def calc_record_size(record)
|
|
17
|
+
@base_record_size + record.values.inject(0){|r, v| r+=v.to_s.size}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def calc_base_record_size(table_name, num_columns)
|
|
23
|
+
col_cnt = num_columns
|
|
24
|
+
# static part of {"1":"16","2":"cc", ...}
|
|
25
|
+
col_size_for_static = 6 * col_cnt - 1 + 2
|
|
26
|
+
col_size_for_key = 0
|
|
27
|
+
col_cnt_length = col_cnt.to_s.size # get position of number, ex: 9->1, 12->2, 132->3
|
|
28
|
+
# calc size of each key per position, 1, 10x, 100x,... ex: 123 -> calc size 1-9 and 10-99
|
|
29
|
+
1.upto(col_cnt_length-1){|i| col_size_for_key += ( i * (10**i - (10**(i-1))) )}
|
|
30
|
+
# calc size of the key for the hight position
|
|
31
|
+
col_size_for_key += (col_cnt - 10**(col_cnt_length-1) + 1) * col_cnt_length
|
|
32
|
+
col_size = col_size_for_static + col_size_for_key
|
|
33
|
+
BASE_DATA_SIZE + table_name.size + col_size
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'flydata-core/logger'
|
|
2
|
+
|
|
3
|
+
module Flydata
|
|
4
|
+
module QueryBasedSync
|
|
5
|
+
|
|
6
|
+
class ResourceRequester
|
|
7
|
+
include FlydataCore::Logger
|
|
8
|
+
|
|
9
|
+
def initialize(context)
|
|
10
|
+
@context = context
|
|
11
|
+
@resource_client = create_resource_client(context)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :context
|
|
15
|
+
|
|
16
|
+
# Start fetching resources by keeping a same connection
|
|
17
|
+
# Caller needs to call methods using the connection with block
|
|
18
|
+
#
|
|
19
|
+
# ex:
|
|
20
|
+
# requester.start do |req|
|
|
21
|
+
# req.fetch_resources(table_name) do |response|
|
|
22
|
+
# handle(response)
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
def start
|
|
26
|
+
context.table_meta.reload(@resource_client)
|
|
27
|
+
yield self
|
|
28
|
+
ensure
|
|
29
|
+
@resource_client.close
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Fetch resources for a table
|
|
33
|
+
# It may call the callback multiple times in the following cases
|
|
34
|
+
# - The resource size is bigger than the max size per request
|
|
35
|
+
# - Resume to fetch resources if resume info exists in per-table position file
|
|
36
|
+
def each_response(table_name, interval = 1)
|
|
37
|
+
latest_src_pos = context.source_pos_class.new(context.table_meta.current_snapshot)
|
|
38
|
+
|
|
39
|
+
loop do
|
|
40
|
+
responses = fetch_responses_once(table_name, latest_src_pos)
|
|
41
|
+
break if responses.nil? || responses.empty?
|
|
42
|
+
responses.each do |response|
|
|
43
|
+
yield response
|
|
44
|
+
end
|
|
45
|
+
break if responses.last.new_source_pos >= latest_src_pos
|
|
46
|
+
sleep interval # to avoid rush
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Override
|
|
53
|
+
# Return a resource client object
|
|
54
|
+
# ex: pg_client, mysql_connection
|
|
55
|
+
def create_resource_client(context)
|
|
56
|
+
raise "Not implemented"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Override
|
|
60
|
+
# Fetch the resource and return a response object
|
|
61
|
+
# Returned object must be kind of Flydata::QueryBasedSync::Response
|
|
62
|
+
def fetch_responses_once(table_name, latest_src_pos)
|
|
63
|
+
raise "Not implemented"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'flydata/query_based_sync/record_size_estimator'
|
|
2
|
+
|
|
3
|
+
module Flydata
|
|
4
|
+
module QueryBasedSync
|
|
5
|
+
|
|
6
|
+
class Response
|
|
7
|
+
|
|
8
|
+
DEFAULT_EMIT_CHUNK_LIMIT = 64 * (1024**2) # 64mb
|
|
9
|
+
|
|
10
|
+
def initialize(context, table_name, records, query_cond = {})
|
|
11
|
+
@table_name = table_name
|
|
12
|
+
@records = records
|
|
13
|
+
@context = context
|
|
14
|
+
@query_cond = query_cond
|
|
15
|
+
|
|
16
|
+
@table_meta = context.table_meta[table_name.to_sym]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :context, :table_name, :src_result, :query_cond
|
|
20
|
+
|
|
21
|
+
# Set records for sending as flydata sync records
|
|
22
|
+
# The format is an array of Hash objects
|
|
23
|
+
# {"1"=>"1000", "2"=>"Andy", ....}
|
|
24
|
+
attr_reader :records
|
|
25
|
+
|
|
26
|
+
# Store the source position for the resume
|
|
27
|
+
# This needs to be saved at the per-table binlog.pos after sending the records
|
|
28
|
+
attr_reader :new_source_pos
|
|
29
|
+
|
|
30
|
+
# Set per-table source position for resume
|
|
31
|
+
def set_new_source_pos(required_pk_values = false)
|
|
32
|
+
pk_values =
|
|
33
|
+
# Set pk_values if next_response exists or returned row size is the same as the specified limit size.
|
|
34
|
+
# It is for resuming in the middle of the transaction.
|
|
35
|
+
if required_pk_values
|
|
36
|
+
values = []
|
|
37
|
+
pk_positions = @table_meta[:pk_positions]
|
|
38
|
+
@table_meta[:primary_keys].each_with_index do |pk, index|
|
|
39
|
+
pk_pos = pk_positions[index]
|
|
40
|
+
values << { pk.to_s => @records.last[pk_pos.to_s] }
|
|
41
|
+
end
|
|
42
|
+
values
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
args =
|
|
46
|
+
if pk_values
|
|
47
|
+
[@query_cond[:from_sid], @query_cond[:to_sid], pk_values]
|
|
48
|
+
else
|
|
49
|
+
[@query_cond[:to_sid]]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@new_source_pos = @context.source_pos_class.new(*args)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def record_count
|
|
56
|
+
records.nil? ? 0 : records.count
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def empty?
|
|
60
|
+
record_count == 0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.create_responses(context, table_name, raw_result, query_cond = {})
|
|
64
|
+
emit_chunk_limit = context.params[:emit_chunk_limit] || DEFAULT_EMIT_CHUNK_LIMIT
|
|
65
|
+
table_meta = context.table_meta[table_name.to_sym]
|
|
66
|
+
record_size_estimator = RecordSizeEstimator.new(table_name, table_meta[:columns].count)
|
|
67
|
+
|
|
68
|
+
all_records = convert_result_to_hash(raw_result)
|
|
69
|
+
responses = []
|
|
70
|
+
|
|
71
|
+
if all_records.nil? || all_records.empty?
|
|
72
|
+
res = self.new(context, table_name, [], query_cond)
|
|
73
|
+
res.set_new_source_pos
|
|
74
|
+
return [res]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
start_index = 0
|
|
78
|
+
cur_chunk_size = 0
|
|
79
|
+
|
|
80
|
+
# Split records and create response object
|
|
81
|
+
all_records.each_with_index do |r, i|
|
|
82
|
+
record_size = record_size_estimator.calc_record_size(r)
|
|
83
|
+
# Set another response object, if the chunk size exceeds the emit_chunk_limit
|
|
84
|
+
if cur_chunk_size > 0 && (cur_chunk_size + record_size) > emit_chunk_limit
|
|
85
|
+
responses << self.new(context, table_name, all_records[start_index..i-1], query_cond)
|
|
86
|
+
start_index = i
|
|
87
|
+
cur_chunk_size = record_size
|
|
88
|
+
else
|
|
89
|
+
cur_chunk_size += record_size
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if cur_chunk_size > 0
|
|
94
|
+
responses << self.new(context, table_name, all_records[start_index..-1], query_cond)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Set new source positions for each response
|
|
98
|
+
responses[0..-2].each{|res| res.set_new_source_pos(true)}
|
|
99
|
+
more_response = (all_records.size == table_meta[:max_num_rows_per_query])
|
|
100
|
+
responses.last.set_new_source_pos(more_response)
|
|
101
|
+
|
|
102
|
+
responses
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Convert to sync record style
|
|
106
|
+
# ex)
|
|
107
|
+
# input: {"id"=>1, "name"=>"Andy"}
|
|
108
|
+
# output: {"1"=>1, "2"=>"Andy"}
|
|
109
|
+
def self.convert_result_to_hash(result)
|
|
110
|
+
first_record = result.first
|
|
111
|
+
return result if first_record.kind_of?(Hash) && first_record.has_key?('1')
|
|
112
|
+
|
|
113
|
+
result.collect do |r|
|
|
114
|
+
h = {}
|
|
115
|
+
r.values.each.with_index(1){|v, i| h[i.to_s] = v }
|
|
116
|
+
h
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'flydata/plugin_support/sync_record_emittable'
|
|
2
|
+
|
|
3
|
+
module Flydata
|
|
4
|
+
module QueryBasedSync
|
|
5
|
+
|
|
6
|
+
class ResponseHandler
|
|
7
|
+
include Flydata::PluginSupport::SyncRecordEmittable
|
|
8
|
+
|
|
9
|
+
def initialize(context)
|
|
10
|
+
@context = context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def handle(response)
|
|
14
|
+
records = response.records.collect{|r| {row: r} }
|
|
15
|
+
|
|
16
|
+
emit_sync_records(records,
|
|
17
|
+
type: :update, # upsert only
|
|
18
|
+
src_pos: response.new_source_pos.to_s,
|
|
19
|
+
table: response.table_name)
|
|
20
|
+
|
|
21
|
+
# TODO: Revisit and check if transaction management is needed
|
|
22
|
+
# However, since we use 'upsert' for query based sync,
|
|
23
|
+
# duplicate event will not cause an issue.
|
|
24
|
+
context.table_src_pos_files[response.table_name.to_sym].
|
|
25
|
+
save(response.new_source_pos)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -41,7 +41,7 @@ class SyncGenerateTableDdl < Component
|
|
|
41
41
|
if missing_tables
|
|
42
42
|
missing_tables.each {|missing_table| error_list << { error: "table does not exist in the #{data_source_type_display_name}", table: missing_table } }
|
|
43
43
|
end
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
[flydata_tabledefs, error_list]
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -24,14 +24,14 @@ module PluginSupport
|
|
|
24
24
|
|
|
25
25
|
class FlydataBinlogRecordDispatcher < BinlogRecordDispatcher
|
|
26
26
|
def initialize(context)
|
|
27
|
-
context.
|
|
27
|
+
context.cur_src_pos_file = ""
|
|
28
28
|
@context = context
|
|
29
29
|
@query_dispatcher = FlydataBinlogQueryDispatcher.new(context)
|
|
30
30
|
@dml_record_handler = DmlRecordHandler.new(context)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def on_rotate(record)
|
|
34
|
-
@context.
|
|
34
|
+
@context.cur_src_pos_file = record["binlog_file"]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def on_write_rows(record)
|
|
@@ -16,19 +16,13 @@ module PluginSupport
|
|
|
16
16
|
@table_meta = @context.table_meta
|
|
17
17
|
|
|
18
18
|
# Load per-table binlog position
|
|
19
|
-
@table_binlog_pos =
|
|
20
|
-
@context.tables.each do |table_name|
|
|
21
|
-
table_binlog_pos_str = @context.sync_fm.get_table_source_raw_pos(table_name)
|
|
22
|
-
if table_binlog_pos_str
|
|
23
|
-
@table_binlog_pos[table_name] = FlydataCore::Mysql::BinlogPos.new(table_binlog_pos_str)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
19
|
+
@table_binlog_pos = @context.table_binlog_pos
|
|
26
20
|
end
|
|
27
21
|
|
|
28
22
|
private
|
|
29
23
|
|
|
30
24
|
def binlog_pos(record)
|
|
31
|
-
"#{@context.
|
|
25
|
+
"#{@context.cur_src_pos_file}\t#{record['next_position'] - record['event_length']}"
|
|
32
26
|
end
|
|
33
27
|
|
|
34
28
|
def supported_database
|
|
@@ -81,7 +75,7 @@ module PluginSupport
|
|
|
81
75
|
|
|
82
76
|
def check_empty_binlog
|
|
83
77
|
#Log one warning per consecutive records that have empty binlog filename
|
|
84
|
-
if @context.
|
|
78
|
+
if @context.cur_src_pos_file.to_s.empty?
|
|
85
79
|
if @first_empty_binlog
|
|
86
80
|
$log.warn "Binlog file name is empty. Rotate event not received!"
|
|
87
81
|
@first_empty_binlog = false
|
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
require 'flydata/plugin_support/context'
|
|
2
|
+
require 'flydata-core/mysql/binlog_pos'
|
|
3
|
+
|
|
4
|
+
module Flydata
|
|
5
|
+
module SourceMysql
|
|
6
|
+
module PluginSupport
|
|
2
7
|
|
|
3
|
-
module Flydata::SourceMysql::PluginSupport
|
|
4
8
|
class Context < ::Flydata::PluginSupport::Context
|
|
5
|
-
register_mandatory_opts :database
|
|
9
|
+
register_mandatory_opts :database
|
|
10
|
+
|
|
11
|
+
def initialize(opts)
|
|
12
|
+
super
|
|
13
|
+
set_table_binlog_pos
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :table_binlog_pos
|
|
17
|
+
|
|
18
|
+
def set_table_binlog_pos
|
|
19
|
+
@table_binlog_pos = {}
|
|
20
|
+
tables.each do |table_name|
|
|
21
|
+
table_binlog_pos_str = sync_fm.get_table_source_raw_pos(table_name)
|
|
22
|
+
if table_binlog_pos_str
|
|
23
|
+
@table_binlog_pos[table_name] = ::FlydataCore::Mysql::BinlogPos.load(table_binlog_pos_str)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
6
27
|
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
7
31
|
end
|