flydata 0.7.5 → 0.7.6
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/table_def/base.rb +74 -5
- data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +8 -3
- data/flydata-core/lib/flydata-core/table_def/postgresql_table_def.rb +16 -3
- data/flydata-core/spec/table_def/base_spec.rb +2 -2
- data/flydata.gemspec +0 -0
- data/lib/flydata/api/data_entry.rb +17 -5
- data/lib/flydata/command/sync.rb +12 -5
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync.rb +22 -0
- data/lib/flydata/source/sync.rb +30 -0
- data/lib/flydata/source/sync_generate_table_ddl.rb +25 -1
- data/lib/flydata/source_mysql/command/mysqldump.rb +24 -1
- data/lib/flydata/source_mysql/data_entry.rb +1 -0
- data/lib/flydata/source_mysql/parser/dump_parser.rb +10 -5
- data/lib/flydata/source_postgresql/data_entry.rb +1 -0
- data/lib/flydata/source_postgresql/table_meta.rb +8 -4
- data/lib/flydata/sync_file_manager.rb +1 -1
- data/lib/flydata/table_attribute.rb +58 -0
- data/spec/flydata/command/sync_spec.rb +15 -2
- data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +23 -5
- data/spec/flydata/fluent-plugins/in_postgresql_query_based_flydata_spec.rb +22 -4
- data/spec/flydata/source_mysql/sync_generate_table_ddl_spec.rb +5 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a74c7dd378333e32076bf9d8dfb6f445f2a6b07
|
4
|
+
data.tar.gz: 4fbc074d26fee614f5315b674159543db96da68b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e88618472bc0641320931b46ab357ba02c0f17d678b1b66e5d1ef467e2ca4b0518414d8c2037f6177c310b18c2dbb7825b090649ad6615ec621e687a9355118
|
7
|
+
data.tar.gz: ad9d6fd284b20b55f36ed25e4c86d54fc323e856d691179e7611a87c424f94c892b33dc1ea09d8e35b7b6e49b861e573ce03611272020d69913982ea9c15473e
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
1
|
+
0.7.6
|
@@ -9,24 +9,28 @@ class Base
|
|
9
9
|
params ? self.new(*params, options) : nil
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(table_def, table_name, columns, column_def, default_charset,
|
12
|
+
def initialize(table_def, table_name, columns, column_def, unique_keys, default_charset,
|
13
13
|
default_source_charset, comment, options = {})
|
14
14
|
@table_def = table_def
|
15
15
|
@table_name = table_name
|
16
16
|
@columns = columns
|
17
17
|
@column_def = column_def
|
18
|
+
@unique_keys = unique_keys
|
18
19
|
@default_charset = default_charset
|
19
20
|
@default_source_charset = default_source_charset
|
20
21
|
@comment = comment
|
21
22
|
|
23
|
+
# example of opts
|
24
|
+
# { :skip_primary_key_check => true,
|
25
|
+
# :pk_override => { "table_3" => ["id", "group_id"], "table_5" => ["product_id"] }
|
26
|
+
# }
|
22
27
|
opts = [:skip_primary_key_check, :pk_override].inject({}) do |h, key|
|
23
28
|
v = options[key]
|
24
29
|
v ||= options[key.to_s]
|
25
30
|
h[key] = v if v
|
26
31
|
h
|
27
32
|
end
|
28
|
-
|
29
|
-
@pk_override = self.class.override_pk(@table_name, @columns, opts)
|
33
|
+
@pk_override, @pk_status = get_pk_override_with_status(@table_name, @columns, @unique_keys, opts)
|
30
34
|
self.class.check_pk(@table_name, @columns, opts)
|
31
35
|
end
|
32
36
|
|
@@ -54,12 +58,59 @@ class Base
|
|
54
58
|
collect{|col| col[:column]}
|
55
59
|
end
|
56
60
|
|
61
|
+
def get_pk_override_as_hash
|
62
|
+
@pk_override ? { @table_name => @pk_override } : nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def converting_uk_to_pk?
|
66
|
+
@pk_status == :pk_unique
|
67
|
+
end
|
68
|
+
|
57
69
|
private
|
58
70
|
|
59
71
|
def self._create(source_data, options)
|
60
72
|
raise "subclass must implement"
|
61
73
|
end
|
62
74
|
|
75
|
+
# Get pk_override with pk_status
|
76
|
+
# [Arguments]
|
77
|
+
# - table_name (String):
|
78
|
+
# - columns (Hash):
|
79
|
+
# columns that includes all column info from source DB
|
80
|
+
# - unique_keys (Hash):
|
81
|
+
# valid unique keys on the table which can use as a primary key
|
82
|
+
# - opts (Hash):
|
83
|
+
# including pk_override values. See a comment above.
|
84
|
+
# [Return values]
|
85
|
+
# pk_status (Symbol)
|
86
|
+
# :pk_original using original pk from source DB
|
87
|
+
# :pk_overridden using user_pk_override from web server
|
88
|
+
# :pk_unique converting unique key (uk) to pk. Need to update the table attributes (UK_AS_PK_OVERRIDE) on web server
|
89
|
+
# :pk_none none of above
|
90
|
+
# pk_override (Hash):
|
91
|
+
# pk_override or nil
|
92
|
+
def get_pk_override_with_status(table_name, columns, unique_keys, opts)
|
93
|
+
|
94
|
+
pk_override = self.class.override_pk(table_name, columns, opts)
|
95
|
+
|
96
|
+
if pk_override
|
97
|
+
pk_status = :pk_overridden
|
98
|
+
elsif self.class.pk_exist?(@columns)
|
99
|
+
# real primary key exist. Not using pk_override
|
100
|
+
pk_status = :pk_original
|
101
|
+
else
|
102
|
+
# use an unique key as a pk_override if any.
|
103
|
+
new_pk_override = self.class.uk_to_pk_override(table_name, columns, unique_keys)
|
104
|
+
if new_pk_override
|
105
|
+
pk_override = self.class.override_pk(table_name, columns, new_pk_override)
|
106
|
+
pk_status = :pk_unique
|
107
|
+
else
|
108
|
+
pk_status = :pk_none
|
109
|
+
end
|
110
|
+
end
|
111
|
+
[pk_override, pk_status]
|
112
|
+
end
|
113
|
+
|
63
114
|
def self.override_pk(table_name, columns, options)
|
64
115
|
pk_override = options[:pk_override]
|
65
116
|
return nil unless pk_override.kind_of?(Hash) && pk_override[table_name]
|
@@ -83,11 +134,29 @@ class Base
|
|
83
134
|
end
|
84
135
|
|
85
136
|
def self.check_pk(table_name, columns, options)
|
86
|
-
options
|
87
|
-
unless columns.any? {|column| column[:primary_key]} or options[:skip_primary_key_check]
|
137
|
+
unless pk_exist?(columns) || options[:skip_primary_key_check]
|
88
138
|
raise TableDefError, {error: "no primary key defined", table: table_name}
|
89
139
|
end
|
90
140
|
end
|
141
|
+
|
142
|
+
def self.pk_exist?(columns)
|
143
|
+
columns.any? {|column| column[:primary_key]}
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.uk_exist?(columns, unique_keys)
|
147
|
+
unique_keys.any?
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.uk_to_pk_override(table_name, columns, unique_keys)
|
151
|
+
return nil unless uk_exist?(columns, unique_keys)
|
152
|
+
|
153
|
+
# use the first unique key as a substitue of primary key
|
154
|
+
new_pk_override = {}
|
155
|
+
new_pk_override[:pk_override] = { table_name => unique_keys.first }
|
156
|
+
$log.info %Q|Using unique key as primary key: {"#{table_name}"=>#{new_pk_override[:pk_override][table_name].inspect}}|
|
157
|
+
|
158
|
+
new_pk_override
|
159
|
+
end
|
91
160
|
end
|
92
161
|
|
93
162
|
end
|
@@ -90,6 +90,7 @@ class MysqlTableDef < Base
|
|
90
90
|
table_name = nil
|
91
91
|
columns = []
|
92
92
|
column_def = {}
|
93
|
+
unique_keys = []
|
93
94
|
default_charset = nil
|
94
95
|
default_source_charset = nil
|
95
96
|
comment = nil
|
@@ -133,7 +134,8 @@ class MysqlTableDef < Base
|
|
133
134
|
elsif stripped_line.start_with?("CONSTRAINT")
|
134
135
|
# constraint definition. No acction required.
|
135
136
|
elsif stripped_line.start_with?("UNIQUE KEY")
|
136
|
-
parse_key(line, columns, :unique)
|
137
|
+
unique_key, include_null_column = parse_key(line, columns, :unique)
|
138
|
+
unique_keys << unique_key unless include_null_column
|
137
139
|
else
|
138
140
|
# "Unknown table definition. Skip. (#{line})"
|
139
141
|
end
|
@@ -142,7 +144,7 @@ class MysqlTableDef < Base
|
|
142
144
|
break
|
143
145
|
end
|
144
146
|
end
|
145
|
-
position == :after_create_table ? [table_def, table_name, columns, column_def, default_charset, default_source_charset, comment] : nil
|
147
|
+
position == :after_create_table ? [table_def, table_name, columns, column_def, unique_keys, default_charset, default_source_charset, comment] : nil
|
146
148
|
end
|
147
149
|
|
148
150
|
# Replaces a MySQL string with an empty string ('')
|
@@ -307,15 +309,18 @@ class MysqlTableDef < Base
|
|
307
309
|
line = /\((?:`.*?`(?:\(.*?\))?(?:,\s*)?)+\)/.match(line)[0]
|
308
310
|
keys = line.scan(/`(.*?)`/).collect{|item| item[0]}
|
309
311
|
|
312
|
+
include_null_column = false
|
310
313
|
keys.each do |key|
|
311
314
|
column = columns.detect {|column|
|
312
315
|
column[:column] === key
|
313
316
|
}
|
314
317
|
raise "Key #{key} must exist in the definition " if column.nil?
|
315
318
|
column[type] = true
|
319
|
+
include_null_column = true unless column[:not_null]
|
316
320
|
end
|
317
|
-
end
|
318
321
|
|
322
|
+
[keys, include_null_column]
|
323
|
+
end
|
319
324
|
end
|
320
325
|
|
321
326
|
end
|
@@ -123,7 +123,7 @@ class PostgresqlTableDef < Base
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def self._create(information_schema_columns, options)
|
126
|
-
table_def = information_schema_columns.inspect
|
126
|
+
table_def = information_schema_columns.collect {|iscol| iscol.first}.inspect
|
127
127
|
table_name = nil
|
128
128
|
columns = []
|
129
129
|
column_def = {}
|
@@ -132,8 +132,12 @@ class PostgresqlTableDef < Base
|
|
132
132
|
default_charset = 'UTF_8'
|
133
133
|
default_charset_postgresql = 'UTF8'
|
134
134
|
comment = nil
|
135
|
+
unique_keys_hash = Hash.new {|h, k| h[k] = []}
|
135
136
|
|
136
|
-
information_schema_columns.each do |
|
137
|
+
information_schema_columns.each do |iscol_arr|
|
138
|
+
# An iscol_arr represents a column. Column information in all elements in iscol_arr is the same.
|
139
|
+
# Only difference between elements is index information.
|
140
|
+
iscol = iscol_arr.first
|
137
141
|
column = parse_one_column_def(iscol)
|
138
142
|
if table_name
|
139
143
|
unless table_name == column[:table]
|
@@ -145,8 +149,17 @@ class PostgresqlTableDef < Base
|
|
145
149
|
columns << column
|
146
150
|
coldef = iscol.inspect
|
147
151
|
column_def[column[:column]] = coldef
|
152
|
+
|
153
|
+
iscol_arr.each do |iscol|
|
154
|
+
# gather information for unique keys that this column belongs to.
|
155
|
+
if iscol['is_primary'] == 'f' && iscol['is_unique'] == 't' && iscol['is_valid'] == 't' && iscol['is_partial'] == 'f'
|
156
|
+
unique_keys_hash[iscol['index_name'].to_sym] << iscol['column_name']
|
157
|
+
end
|
158
|
+
end
|
148
159
|
end
|
149
|
-
|
160
|
+
unique_keys = unique_keys_hash.values
|
161
|
+
|
162
|
+
[table_def, table_name, columns, column_def, unique_keys, default_charset, default_charset_postgresql, comment]
|
150
163
|
end
|
151
164
|
|
152
165
|
def self.parse_one_column_def(information_schema_column)
|
@@ -5,13 +5,14 @@ module TableDef
|
|
5
5
|
|
6
6
|
describe Base do
|
7
7
|
let(:subject_object) { described_class.new(table_def, table_name, columns,
|
8
|
-
column_def, default_charset,
|
8
|
+
column_def, unique_keys, default_charset,
|
9
9
|
default_source_charset, comment) }
|
10
10
|
|
11
11
|
let(:table_def) { double('table_def') }
|
12
12
|
let(:table_name) { double('table_name') }
|
13
13
|
let(:columns) { double('columns') }
|
14
14
|
let(:column_def) { double('column_def') }
|
15
|
+
let(:unique_keys) { double('unique_keys') }
|
15
16
|
let(:default_charset) { double('default_charset') }
|
16
17
|
let(:default_source_charset) { double('default_source_charset') }
|
17
18
|
let(:comment) { double('comment') }
|
@@ -27,7 +28,6 @@ describe Base do
|
|
27
28
|
{column: "user_id", primary_key: pk2_value},
|
28
29
|
]
|
29
30
|
end
|
30
|
-
|
31
31
|
|
32
32
|
context 'with unordered primary keys' do
|
33
33
|
let(:pk1_value) { 't' }
|
data/flydata.gemspec
CHANGED
Binary file
|
@@ -164,11 +164,23 @@ module Flydata
|
|
164
164
|
end
|
165
165
|
|
166
166
|
# Update validity of tables
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
|
171
|
-
|
167
|
+
# -- Params ( Agent 0.7.4 and older )
|
168
|
+
# params = {
|
169
|
+
# updated_tables: { "bad_table"=>"error reason", "good_table"=>nil}
|
170
|
+
# }
|
171
|
+
# -- Params ( Agent 0.7.5 and later )
|
172
|
+
# table_update_hash = {
|
173
|
+
# updated_tables: {
|
174
|
+
# "<table-name>" => {
|
175
|
+
# "invalid_table_reason" => "<error reason>",
|
176
|
+
# "uk_as_pk" => ['xx', ...],
|
177
|
+
# }
|
178
|
+
# }
|
179
|
+
# }
|
180
|
+
# - If value is nil, the setting will be deleted.
|
181
|
+
# - If table's attribute hash doesn't have an attribute key, the setting for the attribute for the table will not be changed
|
182
|
+
def update_table_validity(data_entry_id, table_update_hash)
|
183
|
+
@client.post("/#{@model_name.pluralize}/#{data_entry_id}/update_table_validity", {:headers => {:content_type => :json}}, table_update_hash.to_json)
|
172
184
|
end
|
173
185
|
|
174
186
|
# Tells the server that an initial sync has completed
|
data/lib/flydata/command/sync.rb
CHANGED
@@ -15,6 +15,7 @@ require 'flydata/output/forwarder'
|
|
15
15
|
require 'flydata/parser'
|
16
16
|
require 'flydata/preference/data_entry_preference'
|
17
17
|
require 'flydata/sync_file_manager'
|
18
|
+
require 'flydata/table_attribute'
|
18
19
|
require 'flydata-core/table_def'
|
19
20
|
require 'flydata/table_ddl'
|
20
21
|
require 'flydata/event/api_event_sender'
|
@@ -1181,8 +1182,9 @@ EOM
|
|
1181
1182
|
raise "`tables` (or `tables_append_only`) is neither defined in the data entry nor the local config file"
|
1182
1183
|
end
|
1183
1184
|
|
1184
|
-
flydata_tabledefs, error_list = context.generate_flydata_tabledef(tables,
|
1185
|
+
flydata_tabledefs, error_list, uk_as_pk_override = context.generate_flydata_tabledef(tables,
|
1185
1186
|
skip_primary_key_check: opts.skip_primary_key_check?)
|
1187
|
+
|
1186
1188
|
create_flydata_ctl_table = true
|
1187
1189
|
append_only = tables & @append_only_tables
|
1188
1190
|
flydata_tabledefs.each do |flydata_tabledef|
|
@@ -1191,7 +1193,8 @@ EOM
|
|
1191
1193
|
flydata_ctl_table: create_flydata_ctl_table, schema_name: schema_name, ctl_only: opts.ctl_only?, skip_drop_table: skip_drop_table, skip_primary_key_check: opts.skip_primary_key_check?)
|
1192
1194
|
create_flydata_ctl_table = false
|
1193
1195
|
end
|
1194
|
-
|
1196
|
+
|
1197
|
+
table_validity_hash = Hash.new {|h,k| h[k] = {}}
|
1195
1198
|
tables_without_error = tables
|
1196
1199
|
unless error_list.empty?
|
1197
1200
|
log_error_stderr("\n\nERROR: FlyData Sync will not sync the following table(s) due to an error.")
|
@@ -1200,7 +1203,7 @@ EOM
|
|
1200
1203
|
group_error[error].each do |hash|
|
1201
1204
|
if table = hash[:table]
|
1202
1205
|
log_error_stderr(" - #{table} (#{error})")
|
1203
|
-
table_validity_hash[table] = error
|
1206
|
+
table_validity_hash[table][TableAttribute::INVALID_TABLE_REASON] = error
|
1204
1207
|
end
|
1205
1208
|
end
|
1206
1209
|
end
|
@@ -1215,9 +1218,13 @@ The other tables are ready to sync. To start sync, run the generated script on
|
|
1215
1218
|
EOS
|
1216
1219
|
end
|
1217
1220
|
end
|
1221
|
+
tables_without_error.each {|table| table_validity_hash[table][TableAttribute::INVALID_TABLE_REASON] = nil }
|
1222
|
+
|
1223
|
+
# update table validity and uk_as_pk info on web server
|
1224
|
+
uk_as_pk_override.each_pair {|table, val| table_validity_hash[table][TableAttribute::UK_AS_PK_OVERRIDE] = val}
|
1225
|
+
(tables - uk_as_pk_override.keys).each {|table| table_validity_hash[table][TableAttribute::UK_AS_PK_OVERRIDE] = nil }
|
1218
1226
|
|
1219
|
-
|
1220
|
-
flydata.data_entry.update_table_validity(de['id'], {updated_tables: table_validity_hash}) unless table_validity_hash.empty?
|
1227
|
+
flydata.data_entry.update_table_validity(de['id'], updated_tables: table_validity_hash) unless table_validity_hash.empty?
|
1221
1228
|
|
1222
1229
|
sync_fm = create_sync_file_manager(de)
|
1223
1230
|
sync_fm.save_generated_ddl(tables_without_error, TableDdl::VERSION)
|
@@ -3,6 +3,7 @@ require 'flydata/source'
|
|
3
3
|
require 'flydata-core/fluent/config_helper'
|
4
4
|
require 'flydata/fluent-plugins/flydata_plugin_ext/flush_support'
|
5
5
|
require 'flydata/fluent-plugins/flydata_plugin_ext/transaction_support'
|
6
|
+
require 'flydata/table_attribute'
|
6
7
|
|
7
8
|
module Fluent
|
8
9
|
|
@@ -17,6 +18,7 @@ module Fluent
|
|
17
18
|
config_param :tables, :string
|
18
19
|
config_param :tables_append_only, :string
|
19
20
|
config_param :pk_override, :hash, default: {}
|
21
|
+
config_param :table_attributes, :array, default: []
|
20
22
|
config_param :tag, :string
|
21
23
|
config_param :position_file, :string, default: 'position.binlog.pos'
|
22
24
|
end
|
@@ -61,6 +63,26 @@ module Fluent
|
|
61
63
|
h
|
62
64
|
end
|
63
65
|
|
66
|
+
# Re-construct pk_override & table_attributes
|
67
|
+
# @pk_override should include both USER_PK_OVERRIDE and UK_AS_PK_OVERRIDE.
|
68
|
+
# If mismatch happens here, @pk_override value should come from a local conf file.
|
69
|
+
#TODO support UK_AS_PK_OVERRIDE for tables existing only in local conf, not registered on the web.
|
70
|
+
|
71
|
+
pk_override_from_table_attrs = Flydata::TableAttribute::generate_pk_override(@table_attributes)
|
72
|
+
|
73
|
+
if @pk_override != pk_override_from_table_attrs
|
74
|
+
# @pk_override in a local conf file only includes USER_PK_OVERRIDE. Update table_attributes.
|
75
|
+
Flydata::TableAttribute::delete_attribute(@table_attributes, Flydata::TableAttribute::USER_PK_OVERRIDE)
|
76
|
+
Flydata::TableAttribute::save_attribute(@table_attributes, @pk_override, Flydata::TableAttribute::USER_PK_OVERRIDE)
|
77
|
+
|
78
|
+
@pk_override = Flydata::TableAttribute::generate_pk_override(@table_attributes)
|
79
|
+
$log.info "Update primary key override. Using #{@pk_override}"
|
80
|
+
|
81
|
+
prefs = @data_entry['mysql_data_entry_preference'] || @data_entry['postgresql_data_entry_preference']
|
82
|
+
prefs['table_attributes'] = @table_attributes
|
83
|
+
prefs['pk_override'] = @pk_override
|
84
|
+
end
|
85
|
+
|
64
86
|
$log.info("Source position - resume_pos:'#{@source_position_file.read rescue IOError}' " +
|
65
87
|
"sent_pos:'#{@sent_position_file.read rescue nil}'")
|
66
88
|
end
|
data/lib/flydata/source/sync.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'flydata/source'
|
2
2
|
require 'flydata/source/component'
|
3
3
|
require 'flydata/source/errors'
|
4
|
+
require 'flydata/table_attribute'
|
4
5
|
|
5
6
|
module Flydata
|
6
7
|
module Source
|
@@ -68,6 +69,35 @@ class Sync < Component
|
|
68
69
|
prefs['new_tables'] =
|
69
70
|
prefs['new_tables'].kind_of?(String) ?
|
70
71
|
prefs['new_tables'].split(/(?:\s*,\s*|\s+)/).uniq : []
|
72
|
+
|
73
|
+
setup_pk_override_prefs(prefs)
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_pk_override_prefs(prefs)
|
77
|
+
prefs['pk_override'] = {} if prefs['pk_override'].nil?
|
78
|
+
prefs['user_pk_override'] = TableAttribute::extract_attribute(prefs['table_attributes'],
|
79
|
+
TableAttribute::USER_PK_OVERRIDE)
|
80
|
+
prefs['uk_as_pk_override'] = TableAttribute::extract_attribute(prefs['table_attributes'],
|
81
|
+
TableAttribute::UK_AS_PK_OVERRIDE)
|
82
|
+
|
83
|
+
# Re-construct pk_override & table_attributes
|
84
|
+
# prefs['pk_override'] should include both USER_PK_OVERRIDE and UK_AS_PK_OVERRIDE.
|
85
|
+
# If mismatch happens here, prefs['pk_override'] value should come from a local conf file.
|
86
|
+
#TODO support UK_AS_PK_OVERRIDE for tables existing only in local conf, not registered on the web.
|
87
|
+
|
88
|
+
pk_override_from_table_attrs = TableAttribute::generate_pk_override(prefs['table_attributes'])
|
89
|
+
|
90
|
+
if prefs['pk_override'] != pk_override_from_table_attrs
|
91
|
+
# prefs['pk_override'] in a local conf file only includes USER_PK_OVERRIDE.
|
92
|
+
prefs['user_pk_override'] = prefs['pk_override']
|
93
|
+
|
94
|
+
# Update table_attributes.
|
95
|
+
TableAttribute::delete_attribute(prefs['table_attributes'], TableAttribute::USER_PK_OVERRIDE)
|
96
|
+
TableAttribute::save_attribute(prefs['table_attributes'], prefs['user_pk_override'], TableAttribute::USER_PK_OVERRIDE)
|
97
|
+
|
98
|
+
prefs['pk_override'] = TableAttribute::generate_pk_override(prefs['table_attributes'])
|
99
|
+
$log.info "Primary key override is updated. Using pk_override: #{prefs['pk_override']}"
|
100
|
+
end
|
71
101
|
end
|
72
102
|
end
|
73
103
|
|
@@ -28,21 +28,30 @@ class SyncGenerateTableDdl < Component
|
|
28
28
|
|
29
29
|
def generate_flydata_tabledef(tables, options)
|
30
30
|
prefs = data_entry_prefs
|
31
|
+
reset_uk_as_pk_override(tables, prefs)
|
31
32
|
options = options.merge(prefs)
|
32
33
|
flydata_tabledefs = []
|
33
34
|
error_list = []
|
35
|
+
uk_as_pk_override = {}
|
36
|
+
|
34
37
|
missing_tables = each_source_tabledef(tables, options) do |source_tabledef, error|
|
35
38
|
if error
|
36
39
|
error_list << error.err_hash
|
37
40
|
next
|
38
41
|
end
|
42
|
+
|
43
|
+
if source_tabledef.converting_uk_to_pk?
|
44
|
+
new_pk_override = source_tabledef.get_pk_override_as_hash
|
45
|
+
uk_as_pk_override.merge!(new_pk_override)
|
46
|
+
end
|
39
47
|
flydata_tabledefs << source_tabledef.to_flydata_tabledef
|
40
48
|
end
|
49
|
+
|
41
50
|
if missing_tables
|
42
51
|
missing_tables.each {|missing_table| error_list << { error: "table does not exist in the #{data_source_type_display_name}", table: missing_table } }
|
43
52
|
end
|
44
53
|
|
45
|
-
[flydata_tabledefs, error_list]
|
54
|
+
[flydata_tabledefs, error_list, uk_as_pk_override]
|
46
55
|
end
|
47
56
|
|
48
57
|
private
|
@@ -77,6 +86,21 @@ class SyncGenerateTableDdl < Component
|
|
77
86
|
def each_source_tabledef(tables, options, &block)
|
78
87
|
raise UnsupportedSourceError, "subclass must implement"
|
79
88
|
end
|
89
|
+
|
90
|
+
def reset_uk_as_pk_override(target_tables, prefs)
|
91
|
+
# Reset uk_as_pk_override for target tables.
|
92
|
+
prefs['uk_as_pk_override'].delete_if do |table_name, val|
|
93
|
+
target_tables.include?(table_name)
|
94
|
+
end
|
95
|
+
update_pk_override_pref(prefs)
|
96
|
+
end
|
97
|
+
|
98
|
+
def update_pk_override_pref(prefs)
|
99
|
+
# pk_override = user_pk_override + uk_as_pk_override
|
100
|
+
# (`user_pk_override` takes precedence)
|
101
|
+
prefs['pk_override'] = prefs['uk_as_pk_override'].merge( prefs['user_pk_override'] )
|
102
|
+
$log.info "primary key override: #{prefs['pk_override']}"
|
103
|
+
end
|
80
104
|
end
|
81
105
|
|
82
106
|
end
|
@@ -9,10 +9,33 @@ module Flydata
|
|
9
9
|
|
10
10
|
def generate_command(dbconf, args)
|
11
11
|
dbconf.delete('tables')
|
12
|
-
dbconf[:custom_option_end] = args
|
12
|
+
dbconf[:custom_option_end] = build_custom_option_end(args)
|
13
13
|
dbconf[:command] = 'mysqldump'
|
14
14
|
FlydataCore::Mysql::CommandGenerator::generate_mysql_cmd(dbconf)
|
15
15
|
end
|
16
|
+
|
17
|
+
# Workaround to build --where options correctly
|
18
|
+
#
|
19
|
+
# [Background]
|
20
|
+
# Arguments including single/double quotations don't work because those quotations
|
21
|
+
# are removed from the system.
|
22
|
+
#
|
23
|
+
# User's command:
|
24
|
+
# flydata mysqldump table_a --where "id = 1001"
|
25
|
+
# ARGV:
|
26
|
+
# ["table_a", "--where", "id", "=", "1001"]
|
27
|
+
#
|
28
|
+
def build_custom_option_end(args)
|
29
|
+
ret = []
|
30
|
+
while v = args.shift
|
31
|
+
ret << v
|
32
|
+
if v == '--where'
|
33
|
+
ret << %Q|"#{args.join(' ')}"|
|
34
|
+
break
|
35
|
+
end
|
36
|
+
end
|
37
|
+
ret.join(' ')
|
38
|
+
end
|
16
39
|
end
|
17
40
|
end
|
18
41
|
end
|
@@ -475,11 +475,16 @@ EOS
|
|
475
475
|
def parse(line)
|
476
476
|
start_ruby_prof
|
477
477
|
bench_start_time = Time.now
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
478
|
+
begin
|
479
|
+
_parse(line)
|
480
|
+
rescue DumpParseError => e
|
481
|
+
$log.error "Encountered an error while parsing this chunk:\n #{e.message}. backtrace: #{e.backtrace}"
|
482
|
+
raise
|
483
|
+
ensure
|
484
|
+
stop_ruby_prof
|
485
|
+
if ENV['FLYDATA_BENCHMARK']
|
486
|
+
puts " -> time:#{Time.now.to_f - bench_start_time.to_f} size:#{target_line.size}"
|
487
|
+
end
|
483
488
|
end
|
484
489
|
end
|
485
490
|
|
@@ -50,7 +50,11 @@ SELECT
|
|
50
50
|
FROM (SELECT length(substring('999999'::money::char varying, '\.([0-9]+)$')) AS scale) s)
|
51
51
|
ELSE c.numeric_scale END AS numeric_scale,
|
52
52
|
c.datetime_precision,
|
53
|
-
i.
|
53
|
+
i.indexrelid::regclass as index_name,
|
54
|
+
i.indisprimary as is_primary,
|
55
|
+
i.indisunique as is_unique,
|
56
|
+
i.indisvalid as is_valid,
|
57
|
+
CASE WHEN i.indpred!='NULL' THEN true ELSE false END AS is_partial
|
54
58
|
FROM
|
55
59
|
pg_index i
|
56
60
|
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
|
@@ -66,7 +70,7 @@ WHERE
|
|
66
70
|
AND c.table_schema IN (%{schema})
|
67
71
|
AND c.table_name IN (%{tables})
|
68
72
|
ORDER BY
|
69
|
-
c.table_name, c.ordinal_position, i.indisprimary asc;
|
73
|
+
c.table_name, c.ordinal_position, i.indisprimary desc, i.indisunique desc, i.indisvalid asc;
|
70
74
|
EOT
|
71
75
|
|
72
76
|
GET_CURRENT_SNAPSHOT_SQL = "SELECT txid_current_snapshot();"
|
@@ -119,8 +123,8 @@ EOT
|
|
119
123
|
column_name = col['column_name'].to_sym
|
120
124
|
table_name = col['table_name'].to_sym
|
121
125
|
t_meta = ret[table_name]
|
122
|
-
t_meta[:raw_columns]
|
123
|
-
t_meta[:raw_columns][column_name]
|
126
|
+
t_meta[:raw_columns] = Hash.new {|h,k| h[k] = []} unless t_meta[:raw_columns]
|
127
|
+
t_meta[:raw_columns][column_name] << col
|
124
128
|
end
|
125
129
|
|
126
130
|
ret.each do |table_name, t_meta|
|
@@ -514,7 +514,7 @@ module Flydata
|
|
514
514
|
OLD_DUMP_DIR_PROPERTY = 'mysqldump_dir'
|
515
515
|
DUMP_DIR_PROPERTY = 'dump_dir'
|
516
516
|
def dump_dir
|
517
|
-
pref = @data_entry['mysql_data_entry_preference']
|
517
|
+
pref = @data_entry['mysql_data_entry_preference'] || @data_entry['postgresql_data_entry_preference']
|
518
518
|
dump_dir = nil
|
519
519
|
dump_dir_property = DUMP_DIR_PROPERTY
|
520
520
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Flydata
|
2
|
+
module TableAttribute
|
3
|
+
USER_PK_OVERRIDE = 'pk_override'
|
4
|
+
UK_AS_PK_OVERRIDE = 'uk_as_pk'
|
5
|
+
INVALID_TABLE_REASON = 'invalid_table_reason'
|
6
|
+
|
7
|
+
def self.generate_pk_override(table_attributes)
|
8
|
+
user_pk_override = extract_attribute(table_attributes, USER_PK_OVERRIDE)
|
9
|
+
uk_as_pk_override = extract_attribute(table_attributes, UK_AS_PK_OVERRIDE)
|
10
|
+
|
11
|
+
uk_as_pk_override.merge(user_pk_override)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.delete_attribute(table_attributes, key)
|
15
|
+
table_attributes.each {|tbl_attr| tbl_attr.delete(key.to_s)}
|
16
|
+
table_attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
# Extract {<table_name> => <value_for_a_given_key>} pairs from table attributes
|
20
|
+
# arguments:
|
21
|
+
# - table_attributes (hash):
|
22
|
+
# { "<table-name1>" => {
|
23
|
+
# "invalid_table_reason" => "<error reason>",
|
24
|
+
# "pk_override" => ['id1', ...],
|
25
|
+
# "uk_as_pk" => ['uid', ...],...
|
26
|
+
# },
|
27
|
+
# "<table-name2> => {...},
|
28
|
+
# }
|
29
|
+
#
|
30
|
+
# - key (symbol or String): hash key in table_attributes
|
31
|
+
#
|
32
|
+
# return value (hash):
|
33
|
+
# { "table_1" => <value_1>, "table_2" => <value_2>,...}
|
34
|
+
def self.extract_attribute(table_attributes, key)
|
35
|
+
tbl_value_hash = {}
|
36
|
+
table_attributes.each do |tbl_attr|
|
37
|
+
|
38
|
+
table_name = tbl_attr['table_name']
|
39
|
+
next unless tbl_attr.has_key?(key.to_s)
|
40
|
+
tbl_value_hash[table_name] = tbl_attr[key.to_s]
|
41
|
+
end
|
42
|
+
tbl_value_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.save_attribute(table_attributes, values_hash, key)
|
46
|
+
return unless values_hash
|
47
|
+
values_hash.each_pair do |tbl_name, val|
|
48
|
+
tbl_attr = table_attributes.find { |ta| ta['table_name'] == tbl_name }
|
49
|
+
unless tbl_attr
|
50
|
+
# Add a new table
|
51
|
+
tbl_attr = { 'table_name' => tbl_name }
|
52
|
+
table_attributes << tbl_attr
|
53
|
+
end
|
54
|
+
tbl_attr[key.to_s] = val
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -175,7 +175,13 @@ module Flydata
|
|
175
175
|
subject { subject_object.send(:data_entry) }
|
176
176
|
|
177
177
|
let(:de) { { 'mysql_data_entry_preference' => mp } }
|
178
|
-
let(:mp) { { 'tables' => 'Users,Addresses'
|
178
|
+
let(:mp) { { 'tables' => 'Users,Addresses',
|
179
|
+
'table_attributes' => [
|
180
|
+
{"table_name"=>"Users", "status"=>"init_sync_pending"},
|
181
|
+
{"table_name"=>"Addresses", "status"=>"init_sync_pending"}
|
182
|
+
],
|
183
|
+
'pk_override' => {"Users"=>["id"]}
|
184
|
+
} }
|
179
185
|
|
180
186
|
let(:sfm) { double('sfm') }
|
181
187
|
let(:ssl_ca_content) { double('ssl_ca_content') }
|
@@ -200,7 +206,14 @@ module Flydata
|
|
200
206
|
end
|
201
207
|
end
|
202
208
|
context 'with tables_append_only' do
|
203
|
-
before
|
209
|
+
before do
|
210
|
+
mp['tables_append_only'] = 'Invoices,Sessions,Addresses'
|
211
|
+
mp['table_attributes'] += [
|
212
|
+
{"table_name"=>"Invoices", "omit_events"=>["delete"], "status"=>"init_sync_pending"},
|
213
|
+
{"table_name"=>"Sessions", "omit_events"=>["delete"], "status"=>"init_sync_pending"},
|
214
|
+
{"table_name"=>"Addresses", "omit_events"=>["delete"], "status"=>"init_sync_pending"}
|
215
|
+
]
|
216
|
+
end
|
204
217
|
it "creates an array of tables from 'tables' and 'tables_append_only' combined" do
|
205
218
|
subject
|
206
219
|
expect(mp['tables']).to eq %w(Users Addresses Invoices Sessions)
|
@@ -15,7 +15,9 @@ module Fluent
|
|
15
15
|
tag #{TEST_TAG}
|
16
16
|
database #{TEST_DB}
|
17
17
|
tables test_table,test_table_1,test_table_2
|
18
|
-
tables_append_only test_table_3
|
18
|
+
tables_append_only test_table_3,error_table_4
|
19
|
+
pk_override {"test_table_1":["id1","id2"],"test_table_3":["uid"]}
|
20
|
+
table_attributes [{"table_name":"test_table","status":"init_sync_completed"},{"table_name":"test_table_1","status":"init_sync_completed","pk_override":["id1","id2"]},{"table_name":"test_table_2","status":"init_sync_pending"},{"table_name":"test_table_3","omit_events":["delete"],"status":"init_sync_pending","uk_as_pk":["uid"]},{"table_name":"error_table_4","omit_events":["delete"],"status":"init_sync_pending","invalid_table_reason":"table does not exist in the MySQL database"}]
|
19
21
|
position_file #{TEST_POSITION_FILE}
|
20
22
|
host localhost
|
21
23
|
port 3306
|
@@ -237,6 +239,13 @@ EOT
|
|
237
239
|
|
238
240
|
describe '#configure' do
|
239
241
|
let(:config) { default_config }
|
242
|
+
let(:table_attributes_array) {
|
243
|
+
[{"table_name"=>"test_table", "status"=>"init_sync_completed"},
|
244
|
+
{"table_name"=>"test_table_1", "status"=>"init_sync_completed", "pk_override"=>["id1", "id2"]},
|
245
|
+
{"table_name"=>"test_table_2", "status"=>"init_sync_pending"},
|
246
|
+
{"table_name"=>"test_table_3", "omit_events"=>["delete"], "status"=>"init_sync_pending", "uk_as_pk"=>["uid"]},
|
247
|
+
{"table_name"=>"error_table_4", "omit_events"=>["delete"], "status"=>"init_sync_pending", "invalid_table_reason"=>"table does not exist in the MySQL database"}]
|
248
|
+
}
|
240
249
|
before do
|
241
250
|
Test.configure_plugin(plugin, config)
|
242
251
|
end
|
@@ -248,8 +257,9 @@ EOT
|
|
248
257
|
'mysql_data_entry_preference' => {
|
249
258
|
'database' => 'test_db',
|
250
259
|
'tables' => 'test_table,test_table_1,test_table_2',
|
251
|
-
'tables_append_only' => 'test_table_3',
|
252
|
-
'pk_override' => {},
|
260
|
+
'tables_append_only' => 'test_table_3,error_table_4',
|
261
|
+
'pk_override' => {"test_table_1" => ["id1", "id2"], "test_table_3" => ["uid"]},
|
262
|
+
'table_attributes' => table_attributes_array,
|
253
263
|
'host' => 'localhost',
|
254
264
|
'port' => 3306,
|
255
265
|
'username' => 'test_admin',
|
@@ -265,7 +275,7 @@ EOT
|
|
265
275
|
end
|
266
276
|
|
267
277
|
it 'sets data tables_append_only as array' do
|
268
|
-
expect(plugin.instance_variable_get(:@tables_append_only)).to eq(%w(test_table_3))
|
278
|
+
expect(plugin.instance_variable_get(:@tables_append_only)).to eq(%w(test_table_3 error_table_4))
|
269
279
|
end
|
270
280
|
|
271
281
|
it 'sets sync_fm' do
|
@@ -273,7 +283,15 @@ EOT
|
|
273
283
|
end
|
274
284
|
|
275
285
|
it 'sets omit_events' do
|
276
|
-
expect(plugin.instance_variable_get(:@omit_events)).to eq({'test_table_3' => [:delete, :truncate_table]})
|
286
|
+
expect(plugin.instance_variable_get(:@omit_events)).to eq({'test_table_3' => [:delete, :truncate_table],'error_table_4' => [:delete, :truncate_table]})
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'sets pk_override' do
|
290
|
+
expect(plugin.instance_variable_get(:@pk_override)).to eq({"test_table_1" => ["id1", "id2"], "test_table_3" => ["uid"]})
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'set table_attributes' do
|
294
|
+
expect(plugin.instance_variable_get(:@table_attributes)).to eq(table_attributes_array)
|
277
295
|
end
|
278
296
|
end
|
279
297
|
|
@@ -14,7 +14,9 @@ module Fluent
|
|
14
14
|
tag #{TEST_TAG}
|
15
15
|
database #{TEST_DB}
|
16
16
|
tables test_table,test_table_1,test_table_2
|
17
|
-
tables_append_only test_table_3
|
17
|
+
tables_append_only test_table_3,error_table_4
|
18
|
+
pk_override {"test_table_1":["id1","id2"],"test_table_3":["uid"]}
|
19
|
+
table_attributes [{"table_name":"test_table","status":"init_sync_completed"},{"table_name":"test_table_1","status":"init_sync_completed","pk_override":["id1","id2"]},{"table_name":"test_table_2","status":"init_sync_pending"},{"table_name":"test_table_3","omit_events":["delete"],"status":"init_sync_pending","uk_as_pk":["uid"]},{"table_name":"error_table_4","omit_events":["delete"],"status":"init_sync_pending","invalid_table_reason":"table does not exist in the MySQL database"}]
|
18
20
|
position_file #{TEST_POSITION_FILE}
|
19
21
|
host localhost
|
20
22
|
port 5433
|
@@ -32,6 +34,13 @@ EOT
|
|
32
34
|
let(:config) { default_config }
|
33
35
|
subject { Test.configure_plugin(plugin, config) }
|
34
36
|
|
37
|
+
let(:table_attributes_array) {
|
38
|
+
[{"table_name"=>"test_table", "status"=>"init_sync_completed"},
|
39
|
+
{"table_name"=>"test_table_1", "status"=>"init_sync_completed", "pk_override"=>["id1", "id2"]},
|
40
|
+
{"table_name"=>"test_table_2", "status"=>"init_sync_pending"},
|
41
|
+
{"table_name"=>"test_table_3", "omit_events"=>["delete"], "status"=>"init_sync_pending", "uk_as_pk"=>["uid"]},
|
42
|
+
{"table_name"=>"error_table_4", "omit_events"=>["delete"], "status"=>"init_sync_pending", "invalid_table_reason"=>"table does not exist in the MySQL database"}]
|
43
|
+
}
|
35
44
|
before do
|
36
45
|
create_file(TEST_POSITION_FILE, "1001:1001:\t\t")
|
37
46
|
create_table_pos_file('test_table', '0')
|
@@ -48,8 +57,9 @@ EOT
|
|
48
57
|
'postgresql_data_entry_preference' => {
|
49
58
|
'database' => 'test_db',
|
50
59
|
'tables' => 'test_table,test_table_1,test_table_2',
|
51
|
-
'tables_append_only' => 'test_table_3',
|
52
|
-
'pk_override' => {},
|
60
|
+
'tables_append_only' => 'test_table_3,error_table_4',
|
61
|
+
'pk_override' => {"test_table_1" => ["id1", "id2"], "test_table_3" => ["uid"]},
|
62
|
+
'table_attributes' => table_attributes_array,
|
53
63
|
'host' => 'localhost',
|
54
64
|
'port' => 5433,
|
55
65
|
'username' => 'test_admin',
|
@@ -68,9 +78,17 @@ EOT
|
|
68
78
|
end
|
69
79
|
|
70
80
|
it 'sets omit_events' do
|
71
|
-
expect(plugin.instance_variable_get(:@omit_events)).to eq({'test_table_3' => [:delete, :truncate_table]})
|
81
|
+
expect(plugin.instance_variable_get(:@omit_events)).to eq({'test_table_3' => [:delete, :truncate_table],'error_table_4' => [:delete, :truncate_table]})
|
72
82
|
end
|
73
83
|
|
84
|
+
it 'sets pk_override' do
|
85
|
+
expect(plugin.instance_variable_get(:@pk_override)).to eq({"test_table_1" => ["id1", "id2"], "test_table_3" => ["uid"]})
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'set table_attributes' do
|
89
|
+
expect(plugin.instance_variable_get(:@table_attributes)).to eq(table_attributes_array)
|
90
|
+
end
|
91
|
+
|
74
92
|
it 'sets context' do
|
75
93
|
expect(plugin.instance_variable_get(:@context)).to be_kind_of(Flydata::SourcePostgresql::PluginSupport::Context)
|
76
94
|
end
|
@@ -36,6 +36,11 @@ describe SyncGenerateTableDdl do
|
|
36
36
|
end
|
37
37
|
describe '#generate_flydata_tabledef' do
|
38
38
|
subject { subject_object.generate_flydata_tabledef(tables, options) }
|
39
|
+
before do
|
40
|
+
de['mysql_data_entry_preference']['user_pk_override'] = {}
|
41
|
+
de['mysql_data_entry_preference']['uk_as_pk_override'] = {}
|
42
|
+
de['mysql_data_entry_preference']['pk_override'] = {}
|
43
|
+
end
|
39
44
|
|
40
45
|
let(:tables) { %w| table1 table2 table4 table3 | }
|
41
46
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flydata
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Koichi Fujikawa
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2016-06-
|
15
|
+
date: 2016-06-15 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: rest-client
|
@@ -758,6 +758,7 @@ files:
|
|
758
758
|
- lib/flydata/source_zendesk/sync_generate_table_ddl.rb
|
759
759
|
- lib/flydata/source_zendesk/zendesk_flydata_tabledefs.rb
|
760
760
|
- lib/flydata/sync_file_manager.rb
|
761
|
+
- lib/flydata/table_attribute.rb
|
761
762
|
- lib/flydata/table_ddl.rb
|
762
763
|
- lib/flydata/table_meta.rb
|
763
764
|
- lib/flydata/util/encryptor.rb
|
@@ -869,7 +870,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
869
870
|
version: '0'
|
870
871
|
requirements: []
|
871
872
|
rubyforge_project:
|
872
|
-
rubygems_version: 2.
|
873
|
+
rubygems_version: 2.2.2
|
873
874
|
signing_key:
|
874
875
|
specification_version: 4
|
875
876
|
summary: FlyData Agent
|