flydata 0.6.14 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'flydata-core/postgresql/snapshot'
|
2
3
|
|
3
4
|
module FlydataCore
|
4
5
|
module Postgresql
|
@@ -6,27 +7,87 @@ module Postgresql
|
|
6
7
|
class SourcePos
|
7
8
|
include Comparable
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
# Source Position data for PostgreSQL
|
11
|
+
#
|
12
|
+
# Format:
|
13
|
+
# <from-snaphost><TAB><to-snapshot><TAB><pk-values(JSON)>
|
14
|
+
#
|
15
|
+
# from-snapshot: Store txid_snapshot guaranteeing the changes up to this snapshot are sent
|
16
|
+
# pk_values: Store primary key values of the last record on the last request/transaction
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# ex1: No resume position
|
20
|
+
#
|
21
|
+
# 1031:1031:<TAB>
|
22
|
+
#
|
23
|
+
# -> Changes up to 1031:1031: were sent.
|
24
|
+
# Agent will fetch the change between the latest snapshot and 1031:1031:
|
25
|
+
#
|
26
|
+
# ex2: Resume position with single primary key
|
27
|
+
#
|
28
|
+
# 1031:1031:<TAB>1032:1032:<TAB>[{"id":10000}]
|
29
|
+
#
|
30
|
+
# -> Changes up to 1031:1031: and partial changes (id <= 10000) between 1032:1032: and 1031:1031: were sent.
|
31
|
+
# Agent will fetch the change between 1032:1032: and 1031:1031: with a primary key condisions(id > 10000)
|
32
|
+
#
|
33
|
+
# ex3: Resume position with multiple primary keys
|
34
|
+
#
|
35
|
+
# 1031:1031:<TAB>1033:1033:<TAB>[{"group":"apple"},{"name":"TOM"}]
|
36
|
+
#
|
37
|
+
# -> Changes up to 1031:1031: and partial changes (group<='apple' and name<='TOM') between 1033:1033: and 1031:1031: were sent.
|
38
|
+
# Agent will fetch the change between 1033:1033: and 1031:1031: with a primary key condisions(group >= 'apple' and name >= 'TOM')
|
39
|
+
#
|
40
|
+
def initialize(snapshot_or_obj, to_snapshot = nil, pk_values = nil)
|
41
|
+
if snapshot_or_obj.kind_of?(self.class)
|
42
|
+
snapshot_or_obj.tap do |s|
|
43
|
+
@snapshot = s.snapshot
|
44
|
+
@to_snapshot = s.to_snapshot
|
45
|
+
@pk_values = s.pk_values
|
46
|
+
end
|
47
|
+
else
|
48
|
+
@snapshot = Snapshot.new(snapshot_or_obj)
|
49
|
+
@to_snapshot = if to_snapshot.to_s.empty?
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
Snapshot.new(to_snapshot)
|
53
|
+
end
|
54
|
+
@pk_values = pk_values # must be array or nil
|
55
|
+
end
|
12
56
|
end
|
13
57
|
|
14
|
-
attr_reader :
|
58
|
+
attr_reader :snapshot
|
59
|
+
attr_reader :to_snapshot
|
60
|
+
attr_reader :pk_values
|
15
61
|
|
16
62
|
def to_s
|
17
63
|
pk_values = @pk_values ? @pk_values.to_json : ''
|
18
|
-
"#{@
|
64
|
+
"#{@snapshot}\t#{@to_snapshot}\t#{pk_values}"
|
19
65
|
end
|
20
66
|
|
67
|
+
#TODO: Need to revisit the logic along with the spec
|
68
|
+
# http://www.postgresql.org/docs/9.5/static/functions-info.html#FUNCTIONS-TXID-SNAPSHOT-PARTS
|
21
69
|
def <=>(other)
|
22
|
-
|
23
|
-
|
70
|
+
if @snapshot != other.snapshot
|
71
|
+
return @snapshot <=> other.snapshot
|
72
|
+
elsif @pk_values.nil? && !other.pk_values.nil?
|
73
|
+
1
|
74
|
+
elsif !@pk_values.nil? && other.pk_values.nil?
|
75
|
+
-1
|
76
|
+
elsif @pk_values == other.pk_values
|
77
|
+
0
|
78
|
+
else
|
79
|
+
@pk_values.to_s <=> other.pk_values.to_s
|
80
|
+
end
|
24
81
|
end
|
25
82
|
|
26
83
|
def self.load(str)
|
27
|
-
|
28
|
-
pk_values =
|
29
|
-
|
84
|
+
snapshot, to_snapshot, pk_values = str.split("\t").collect{|v| v.strip}
|
85
|
+
pk_values = if pk_values.to_s.empty?
|
86
|
+
nil
|
87
|
+
else
|
88
|
+
JSON.parse(pk_values)
|
89
|
+
end
|
90
|
+
self.new(snapshot, to_snapshot, pk_values)
|
30
91
|
end
|
31
92
|
end
|
32
93
|
|
@@ -21,7 +21,7 @@ class MysqlTableDef < Base
|
|
21
21
|
|
22
22
|
PROC_override_varbinary = ->(type, mysql_type, flydata_type) do
|
23
23
|
return type unless %w(binary varbinary).include?(mysql_type)
|
24
|
-
if type =~ /\((\s*\d+\s*)\)/
|
24
|
+
if type =~ /\((\s*\d+\s*)\)/
|
25
25
|
# expect 2 bytes for each original byte + 2 bytes for the prefix
|
26
26
|
# ex) 4E5DFF => "0x4e5dff"
|
27
27
|
"#{flydata_type}(#{$1.to_i * 2 + 2})"
|
@@ -9,39 +9,82 @@ class PostgresqlTableDef < Base
|
|
9
9
|
|
10
10
|
TYPE_MAP_P2F = {
|
11
11
|
'bigint' => {type: 'int8'},
|
12
|
-
'
|
13
|
-
'
|
14
|
-
'
|
15
|
-
'
|
12
|
+
'int8' => {type: 'int8'},
|
13
|
+
'bigserial' => {type: 'serial8'}, # TODO support
|
14
|
+
'serial8' => {type: 'serial8'}, # TODO support
|
15
|
+
'bit' => {type: 'bit',
|
16
|
+
width_attrs:["character_maximum_length"]},
|
17
|
+
'bit varying' => {type: 'varbit',
|
18
|
+
width_attrs:["character_maximum_length"]},
|
19
|
+
'varbit' => {type: 'varbit',
|
20
|
+
width_attrs:["character_maximum_length"]},
|
21
|
+
'boolean' => {type: 'boolean'},
|
22
|
+
# 'box'
|
23
|
+
'bytea' => {type: 'bytea'},
|
24
|
+
'character' => {type: 'varchar', width_attrs:["character_octet_length"], def_width:[1]},
|
16
25
|
'character varying' => {type: 'varchar',
|
17
26
|
width_attrs:["character_octet_length"],
|
18
27
|
def_width:[1]},
|
28
|
+
'varchar' => {type: 'varchar',
|
29
|
+
width_attrs:["character_octet_length"],
|
30
|
+
def_width:[1]},
|
31
|
+
# 'cidr'
|
32
|
+
# 'circle'
|
19
33
|
'date' => {type: 'date'},
|
20
|
-
'decimal' => {type: 'numeric',
|
21
|
-
width_attrs:["numeric_precision", "numeric_scale"],
|
22
|
-
def_width:[nil, 0] },
|
23
34
|
'double precision' => {type: 'float8'},
|
35
|
+
'float8' => {type: 'float8'},
|
24
36
|
'integer' => {type: 'int4'},
|
25
|
-
'
|
37
|
+
'int' => {type: 'int4'},
|
38
|
+
'int4' => {type: 'int4'},
|
39
|
+
# 'interval'
|
40
|
+
# 'json'
|
41
|
+
# 'line'
|
42
|
+
# 'lseg'
|
43
|
+
# 'macaddr'
|
44
|
+
'money' => {type: 'money',
|
45
|
+
width_attrs:["numeric_precision", "numeric_scale"]},
|
26
46
|
'numeric' => {type: 'numeric',
|
27
|
-
width_attrs:["numeric_precision", "numeric_scale"]
|
28
|
-
|
47
|
+
width_attrs:["numeric_precision", "numeric_scale"]
|
48
|
+
#can be no width values
|
29
49
|
},
|
50
|
+
# 'path'
|
51
|
+
# 'pg_lsn'
|
52
|
+
# 'point'
|
53
|
+
# 'polygon'
|
30
54
|
'real' => {type: 'float4'},
|
31
|
-
'
|
55
|
+
'float4' => {type: 'float4'},
|
32
56
|
'smallint' => {type: 'int2'},
|
57
|
+
'int2' => {type: 'int2'},
|
58
|
+
'smallserial' => {type: 'serial2'}, # TODO support
|
59
|
+
'serial2' => {type: 'serial2'}, # TODO support
|
60
|
+
'serial' => {type: 'serial4'}, # TODO support
|
61
|
+
'serial4' => {type: 'serial4'}, # TODO support
|
33
62
|
'text' => {type: 'text'},
|
34
|
-
'time
|
63
|
+
'time' => {type: 'time'},
|
35
64
|
'time without time zone' => {type: 'time'},
|
36
|
-
'
|
65
|
+
'time with time zone' => {type: 'timetz'},
|
66
|
+
'timetz' => {type: 'timetz'},
|
67
|
+
'timestamp' => {type: 'datetime'},
|
37
68
|
'timestamp without time zone' => {type: 'datetime'},
|
38
|
-
|
69
|
+
'timestamp with time zone' => {type: 'datetimetz'},
|
70
|
+
'timestamptz' => {type: 'datetimetz'},
|
71
|
+
:default => {type: '_unsupported'},
|
72
|
+
# 'tsquery'
|
73
|
+
# 'tsvector'
|
74
|
+
# 'txid_snapshot'
|
75
|
+
# 'uuid'
|
76
|
+
# 'xml'
|
77
|
+
#--- PosgreSQL automatically converts following data types to another data type ---
|
78
|
+
# 'decimal' (converted to 'numeric')
|
79
|
+
# 'bool' ('boolean')
|
80
|
+
# 'char' ('character')
|
39
81
|
}
|
40
82
|
|
41
83
|
def self.convert_to_flydata_type(information_schema_columns)
|
42
84
|
pg_type = information_schema_columns["data_type"]
|
85
|
+
raise "Unknown PostgreSQL type or internal error. type:#{pg_type}" unless pg_type
|
43
86
|
unless TYPE_MAP_P2F.has_key?(pg_type)
|
44
|
-
|
87
|
+
pg_type = :default
|
45
88
|
end
|
46
89
|
type_hash = TYPE_MAP_P2F[pg_type]
|
47
90
|
flydata_type = type_hash[:type]
|
@@ -73,7 +116,7 @@ class PostgresqlTableDef < Base
|
|
73
116
|
|
74
117
|
if type_hash.has_key?(:def_width)
|
75
118
|
if values.nil? || values.size != type_hash[:def_width].size
|
76
|
-
raise "The number of the default values must match the number of width attributes def_width:#{type_hash[:def_width].inspect} width_attrs:#{type_hash[:width_attrs].inspect}"
|
119
|
+
raise "The number of the default values must match the number of width attributes. column:#{information_schema_columns[:column_name]} type:#{type_hash[:type]} def_width:#{type_hash[:def_width].inspect} width_attrs:#{type_hash[:width_attrs].inspect}"
|
77
120
|
end
|
78
121
|
values = values.each_with_index.collect {|v, i| v ? v : type_hash[:def_width][i]}
|
79
122
|
end
|
@@ -118,9 +161,25 @@ class PostgresqlTableDef < Base
|
|
118
161
|
column[:type] = convert_to_flydata_type(information_schema_column)
|
119
162
|
column[:not_null] = true if information_schema_column["is_nullable"] == "NO"
|
120
163
|
column[:primary_key] = true if information_schema_column["is_primary"]
|
121
|
-
column[:default] =
|
164
|
+
column[:default] = case column[:type]
|
165
|
+
when 'boolean'
|
166
|
+
to_boolean(information_schema_column["column_default"])
|
167
|
+
else
|
168
|
+
information_schema_column["column_default"] # TODO nil handling
|
169
|
+
end
|
122
170
|
column
|
123
171
|
end
|
172
|
+
|
173
|
+
def self.to_boolean(col_value)
|
174
|
+
return nil if col_value.nil?
|
175
|
+
# Catch all possible literal for boolean type in PostgreSQL.
|
176
|
+
# Actual col_value coming in here is:
|
177
|
+
# 'true' or 'false' for default value
|
178
|
+
# 't' or 'f' for column value
|
179
|
+
return true if col_value.to_s =~ /^(t|true|y|yes|on|1)$/i
|
180
|
+
return false if col_value.to_s =~ /^(f|false|n|no|off|0)$/i
|
181
|
+
raise "Invalid default value for PostgreSQL boolean type:`#{col_value}`"
|
182
|
+
end
|
124
183
|
end
|
125
184
|
|
126
185
|
end
|
@@ -10,9 +10,13 @@ class RedshiftTableDef
|
|
10
10
|
TYPE_MAP_F2R = {
|
11
11
|
'binary' => {type: 'varchar', use_params: true, default_value: ''},
|
12
12
|
'bit' => {type: 'bigint', default_value: '0'},
|
13
|
+
'boolean' => {type: 'boolean', default_value: false},
|
14
|
+
'varbit' => {type: 'bigint', default_value: '0'},
|
15
|
+
'bytea' => {type: 'varchar(max)', default_value: ''},
|
13
16
|
'char' => {type: 'char', use_params: true, default_value: ''},
|
14
17
|
'date' => {type: 'date', default_value: '0000-01-01'},
|
15
18
|
'datetime' => {type: 'timestamp', default_value: '0000-01-01'},
|
19
|
+
'datetimetz' => {type: 'timestamp', default_value: '0000-01-01'},
|
16
20
|
'enum' => {type: 'varchar encode bytedict', default_value: ''},
|
17
21
|
'float4' => {type: 'float4', default_value: '0'},
|
18
22
|
'float4 unsigned' => {type: 'float4', default_value: '0'},
|
@@ -28,14 +32,17 @@ class RedshiftTableDef
|
|
28
32
|
'int4 unsigned' => {type: 'int8', unsigned: true, default_value: '0'},
|
29
33
|
'int8' => {type: 'int8', default_value: '0'},
|
30
34
|
'int8 unsigned' => {type: 'numeric(20,0)', unsigned: true, default_value: '0'},
|
31
|
-
'
|
32
|
-
'numeric
|
35
|
+
'money' => {type: 'numeric', use_params: true, max_size: [38,37], default_value: '0'},
|
36
|
+
'numeric' => {type: 'numeric', use_params: true, default_params: [18,8], max_size: [38,37], default_value: '0'},
|
37
|
+
'numeric unsigned' => {type: 'numeric', use_params: true, default_params: [18,8], max_size: [38,37], default_value: '0'},
|
33
38
|
'set' => {type: 'varchar encode bytedict', default_value: ''},
|
34
39
|
'text' => {type: 'varchar(max)', default_value: ''},
|
35
40
|
'time' => {type: 'timestamp', default_value: '0000-01-01'},
|
41
|
+
'timetz' => {type: 'timestamp', default_value: '0000-01-01'},
|
36
42
|
'varbinary' => {type: 'varchar', use_params: true, max_size: 65535, default_value: ''},
|
37
43
|
'varchar' => {type: 'varchar', use_params: true, max_size: 65535, default_value: ''},
|
38
44
|
'year' => {type: 'date', default_value: '0001-01-01'},
|
45
|
+
'_unsupported' => {type: 'varchar(max)', default_value: ''},
|
39
46
|
}
|
40
47
|
def self.from_flydata_tabledef(flydata_tabledef, options = {})
|
41
48
|
options[:flydata_ctl_table] = true unless options.has_key?(:flydata_ctl_table)
|
@@ -209,8 +216,14 @@ EOS
|
|
209
216
|
type_info = TYPE_MAP_F2R[type]
|
210
217
|
raise "Unsupported type '#{column[:type]}'" if type_info.nil?
|
211
218
|
|
212
|
-
rs_type = if type_info[:use_params]
|
213
|
-
|
219
|
+
rs_type = if type_info[:use_params]
|
220
|
+
if params
|
221
|
+
params = check_and_replace_max(params, Array(type_info[:max_size])) if type_info[:max_size]
|
222
|
+
else
|
223
|
+
#source data type has no parameter. use default parameters.
|
224
|
+
raise "No default pramameters for type:`#{column[:type]}`" unless type_info[:default_params]
|
225
|
+
params = type_info[:default_params].join(',')
|
226
|
+
end
|
214
227
|
type_info[:type] + "(#{params})"
|
215
228
|
else
|
216
229
|
type_info[:type]
|
@@ -246,6 +259,11 @@ EOS
|
|
246
259
|
|
247
260
|
def self.replace_default_value(flydata_type, redshift_type, default_value)
|
248
261
|
return NULL_STR if default_value.nil?
|
262
|
+
# strip type cast
|
263
|
+
if default_value.kind_of?(String) &&
|
264
|
+
/^(.+?)(::"?[a-z ]+"?)*$/.match(default_value)
|
265
|
+
default_value = $1
|
266
|
+
end
|
249
267
|
if flydata_type.start_with?('year')
|
250
268
|
value = convert_year_into_date(remove_single_quote(default_value))
|
251
269
|
begin
|
@@ -253,31 +271,36 @@ EOS
|
|
253
271
|
rescue
|
254
272
|
raise "default value of YEAR type must be 2 or 4-digit, value:'#{default_value}'"
|
255
273
|
end
|
274
|
+
elsif flydata_type.start_with?('money')
|
275
|
+
default_value = self.parse_money(remove_single_quote(default_value))
|
256
276
|
end
|
257
277
|
|
258
278
|
case redshift_type
|
259
279
|
when 'timestamp'
|
260
|
-
|
280
|
+
case default_value.upcase
|
281
|
+
when /^CURRENT_TIMESTAMP\b/, /^CURRENT_TIME\b/, /^NOW\(\)/
|
261
282
|
'SYSDATE'
|
262
283
|
else
|
263
284
|
"'#{self.parse_timestamp(remove_single_quote(default_value))}'"
|
264
285
|
end
|
265
286
|
when 'date'
|
266
287
|
"'#{self.parse_date(remove_single_quote(default_value))}'"
|
288
|
+
when 'boolean'
|
289
|
+
default_value.to_s.upcase
|
267
290
|
else
|
268
291
|
if !default_value.kind_of?(String)
|
269
292
|
"'#{default_value}'"
|
270
|
-
elsif /^
|
293
|
+
elsif /^[Bb]'.+'$/.match(default_value)
|
271
294
|
"0b#{default_value[2..-2]}".oct
|
272
295
|
elsif /^[xX]'.+'$/.match(default_value)
|
273
296
|
"0x#{default_value[2..-2]}".oct
|
274
297
|
elsif /^0[bx].+/.match(default_value)
|
275
298
|
default_value.oct
|
276
|
-
elsif /^'.*'$/.match(default_value)
|
277
|
-
default_value
|
278
299
|
elsif /\s*nextval\(/.match(default_value)
|
279
300
|
# Redshift does not support nextval() function. Set no default.
|
280
301
|
nil
|
302
|
+
elsif /^'.+'/.match(default_value)
|
303
|
+
default_value
|
281
304
|
else
|
282
305
|
"'#{default_value}'"
|
283
306
|
end
|
@@ -400,7 +423,7 @@ EOS
|
|
400
423
|
end
|
401
424
|
|
402
425
|
APACHE_TIMESTAMP_REGEXP = Regexp.new('^(?<apache_time_format>\[[0-3]\d\/\D{3}\/[1-2]\d{3}:[0-2]\d:[0-5]\d:[0-5]\d ?[\+\-]\d{2}:?\d{2}\])$')
|
403
|
-
TIME_REGEXP = Regexp.new('^(?<sign>-)?(?<hour>\d{2,3}):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9](\.\d+)?)
|
426
|
+
TIME_REGEXP = Regexp.new('^(?<sign>-)?(?<hour>\d{2,3}):(?<minute>[0-5][0-9]):(?<second>[0-5][0-9](\.\d+)?)\s*(?<tz>[+-][0-2][0-9](:?\d{2})?)?$')
|
404
427
|
|
405
428
|
def self.parse_timestamp(value)
|
406
429
|
value_str = value.to_s
|
@@ -412,7 +435,13 @@ EOS
|
|
412
435
|
# apache time format
|
413
436
|
t = DateTime.strptime(value, "[%d/%b/%Y:%H:%M:%S %Z]")
|
414
437
|
elsif time_match = TIME_REGEXP.match(value_str)
|
415
|
-
|
438
|
+
if time_match[:tz]
|
439
|
+
# let DateTime.parse handle timezone
|
440
|
+
t = DateTime.parse("0001-01-01 #{value_str}")
|
441
|
+
else
|
442
|
+
# use own converter which takes care of negative hours
|
443
|
+
t = convert_time_into_timestamp(time_match)
|
444
|
+
end
|
416
445
|
elsif /^(\d+)\.(\d+)$/ === value_str # epoch with fraction
|
417
446
|
epoch = $1
|
418
447
|
fraction = $2
|
@@ -477,6 +506,26 @@ EOS
|
|
477
506
|
value # Return the value as is
|
478
507
|
end
|
479
508
|
end
|
509
|
+
|
510
|
+
def self.parse_money(value)
|
511
|
+
# It's impossible to determine a decimal point of a currency sting without
|
512
|
+
# knowing its format. Here, we're making a best effort guess to support
|
513
|
+
# as many currencty formats as we can without knowing the format.
|
514
|
+
value = value.gsub(/[^0-9\.,]/, '') # remove all chars but numbers and possible decimal point chars
|
515
|
+
|
516
|
+
whole_part = value
|
517
|
+
fractional_part = nil
|
518
|
+
if idx = value.rindex(/[\.,]/)
|
519
|
+
if idx == value.size - 3
|
520
|
+
# it's the decimal point with two digit fraction
|
521
|
+
whole_part = value[0...idx]
|
522
|
+
fractional_part = value[-2..-1]
|
523
|
+
end
|
524
|
+
end
|
525
|
+
value = whole_part.gsub(/[^0-9]/, '')
|
526
|
+
value += ".#{fractional_part}" if fractional_part
|
527
|
+
value
|
528
|
+
end
|
480
529
|
end
|
481
530
|
|
482
531
|
end
|
@@ -8,7 +8,7 @@ module Mysql
|
|
8
8
|
let(:binlog_str) { "#{filename}\t#{pos}" }
|
9
9
|
let(:filename) { 'mysql-bin.000064' }
|
10
10
|
let(:pos) { 104 }
|
11
|
-
|
11
|
+
|
12
12
|
describe '#pos' do
|
13
13
|
subject { subject_object.pos }
|
14
14
|
it { is_expected.to eq(pos) }
|
@@ -35,7 +35,7 @@ module Mysql
|
|
35
35
|
let(:args) { [filename, pos] }
|
36
36
|
it_behaves_like "properly constructed"
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
let(:args) { [ arg1 ] }
|
40
40
|
context 'when given a String' do
|
41
41
|
let(:arg1) { binlog_str }
|
@@ -48,6 +48,14 @@ module Mysql
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
describe '.load' do
|
52
|
+
subject { described_class.load(binlog_str) }
|
53
|
+
it do
|
54
|
+
expect(subject.filename).to eq(filename)
|
55
|
+
expect(subject.pos).to eq(pos)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
51
59
|
let(:arg) { described_class.new (arg_str) }
|
52
60
|
let(:arg_str) { arg_binlog_str }
|
53
61
|
let(:arg_binlog_str) { "#{arg_filename}\t#{arg_pos}" }
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'flydata-core/postgresql/compatibility_checker'
|
3
|
+
|
4
|
+
module FlydataCore
|
5
|
+
module Postgresql
|
6
|
+
describe CompatibilityChecker do
|
7
|
+
let(:database) { 'test_db' }
|
8
|
+
let(:tables) { %w(table_1 table_2 table_3) }
|
9
|
+
let(:option) do
|
10
|
+
{ database: database, tables: tables }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe TableExistenceChecker do
|
14
|
+
let(:subject_object) { described_class.new(option) }
|
15
|
+
|
16
|
+
describe '#create_query' do
|
17
|
+
subject { subject_object.create_query }
|
18
|
+
|
19
|
+
context 'when schema is not set' do
|
20
|
+
it { is_expected.to eq <<EOT
|
21
|
+
SELECT
|
22
|
+
table_name
|
23
|
+
FROM
|
24
|
+
information_schema.tables
|
25
|
+
WHERE
|
26
|
+
table_schema in (select current_schema)
|
27
|
+
AND
|
28
|
+
table_name in ('table_1','table_2','table_3');
|
29
|
+
EOT
|
30
|
+
}
|
31
|
+
end
|
32
|
+
context 'when schema is set' do
|
33
|
+
before { option.merge!(schema: 'test_schema') }
|
34
|
+
it { is_expected.to eq <<EOT
|
35
|
+
SELECT
|
36
|
+
table_name
|
37
|
+
FROM
|
38
|
+
information_schema.tables
|
39
|
+
WHERE
|
40
|
+
table_schema in ('test_schema')
|
41
|
+
AND
|
42
|
+
table_name in ('table_1','table_2','table_3');
|
43
|
+
EOT
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#check_reesult' do
|
50
|
+
let(:result) { [] }
|
51
|
+
subject { subject_object.check_result(result) }
|
52
|
+
|
53
|
+
context 'when all tables exist' do
|
54
|
+
let(:result) do
|
55
|
+
[ {'table_name' => 'table_1'},
|
56
|
+
{'table_name' => 'table_2'},
|
57
|
+
{'table_name' => 'table_3'}, ]
|
58
|
+
end
|
59
|
+
it { expect{subject}.not_to raise_error }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when some tables does not exist' do
|
63
|
+
let(:result) do
|
64
|
+
[{"table_name" => "table_2"}]
|
65
|
+
end
|
66
|
+
it do
|
67
|
+
expect{subject}.to raise_error {|e|
|
68
|
+
expect(e).to be_kind_of(FlydataCore::PostgresqlCompatibilityError)
|
69
|
+
expect(e.to_s.include?("table_1")).to be_truthy
|
70
|
+
expect(e.to_s.include?("table_2")).to be_falsey
|
71
|
+
expect(e.to_s.include?("table_3")).to be_truthy
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe PrimaryKeyChecker do
|
79
|
+
let(:subject_object) { described_class.new(option) }
|
80
|
+
|
81
|
+
describe '#create_query' do
|
82
|
+
subject { subject_object.create_query }
|
83
|
+
|
84
|
+
context 'when schema is not set' do
|
85
|
+
it { is_expected.to eq <<EOT
|
86
|
+
SELECT
|
87
|
+
t.table_name
|
88
|
+
FROM
|
89
|
+
(select * from information_schema.tables where table_schema in (select current_schema) AND table_name in ('table_1','table_2','table_3')) t
|
90
|
+
LEFT OUTER JOIN
|
91
|
+
(select * from information_schema.table_constraints where table_schema in (select current_schema) AND table_name in ('table_1','table_2','table_3')) tc
|
92
|
+
USING (table_schema, table_name)
|
93
|
+
GROUP BY
|
94
|
+
t.table_schema, t.table_name
|
95
|
+
HAVING
|
96
|
+
SUM(CASE WHEN tc.constraint_type='PRIMARY KEY' THEN 1 ELSE 0 END) = 0;
|
97
|
+
EOT
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when schema is set' do
|
102
|
+
before { option.merge!(schema: 'test_schema') }
|
103
|
+
it { is_expected.to eq <<EOT
|
104
|
+
SELECT
|
105
|
+
t.table_name
|
106
|
+
FROM
|
107
|
+
(select * from information_schema.tables where table_schema in ('test_schema') AND table_name in ('table_1','table_2','table_3')) t
|
108
|
+
LEFT OUTER JOIN
|
109
|
+
(select * from information_schema.table_constraints where table_schema in ('test_schema') AND table_name in ('table_1','table_2','table_3')) tc
|
110
|
+
USING (table_schema, table_name)
|
111
|
+
GROUP BY
|
112
|
+
t.table_schema, t.table_name
|
113
|
+
HAVING
|
114
|
+
SUM(CASE WHEN tc.constraint_type='PRIMARY KEY' THEN 1 ELSE 0 END) = 0;
|
115
|
+
EOT
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#check_reesult' do
|
121
|
+
let(:result) { [] }
|
122
|
+
subject { subject_object.check_result(result) }
|
123
|
+
|
124
|
+
context 'when all tables have pk' do
|
125
|
+
let(:result) do
|
126
|
+
[]
|
127
|
+
end
|
128
|
+
it { expect{subject}.not_to raise_error }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when some tables does not have pk' do
|
132
|
+
let(:result) do
|
133
|
+
[{"table_name" => "table_1"}, {"table_name" => "table_3"}]
|
134
|
+
end
|
135
|
+
it do
|
136
|
+
expect{subject}.to raise_error {|e|
|
137
|
+
expect(e).to be_kind_of(FlydataCore::PostgresqlCompatibilityError)
|
138
|
+
expect(e.to_s.include?("table_1")).to be_truthy
|
139
|
+
expect(e.to_s.include?("table_2")).to be_falsey
|
140
|
+
expect(e.to_s.include?("table_3")).to be_truthy
|
141
|
+
}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|