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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b335ff2222169fc42a78d4dec2a5829aa0c94230
|
|
4
|
+
data.tar.gz: 15042a74de15a15b3784c6917973154d1ddd02c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86fb1aa9fe1449e11f0d8755773cd7a083054bb668df7dbf489973828b018f9ebe2e9a10850bcf8ca7afff7682f157cd7d3a3082907b458ad1b5cbba99cf73b5
|
|
7
|
+
data.tar.gz: d36c033143bdd9a1b5931d891406ec6dfbaeaeba70cdae08d78c3282ccf55bebbe3125921bec021bbbb9405907a52431ac0f220722fa10f771db421134c7f348
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.7.0
|
data/flydata-core/Gemfile
CHANGED
data/flydata-core/Gemfile.lock
CHANGED
|
@@ -17,6 +17,7 @@ GEM
|
|
|
17
17
|
parser (2.2.0.pre.8)
|
|
18
18
|
ast (>= 1.1, < 3.0)
|
|
19
19
|
slop (~> 3.4, >= 3.4.5)
|
|
20
|
+
pg (0.18.4)
|
|
20
21
|
powerpack (0.0.9)
|
|
21
22
|
rainbow (2.0.0)
|
|
22
23
|
rspec (3.1.0)
|
|
@@ -54,8 +55,12 @@ PLATFORMS
|
|
|
54
55
|
DEPENDENCIES
|
|
55
56
|
aws-sdk (= 1.18.0)
|
|
56
57
|
mysql2
|
|
58
|
+
pg
|
|
57
59
|
rspec (~> 3.1)
|
|
58
60
|
rubocop
|
|
59
61
|
ruby-prof
|
|
60
62
|
simplecov
|
|
61
63
|
timecop
|
|
64
|
+
|
|
65
|
+
BUNDLED WITH
|
|
66
|
+
1.10.6
|
|
@@ -317,12 +317,14 @@ end
|
|
|
317
317
|
class CompatibilityError < StandardError
|
|
318
318
|
end
|
|
319
319
|
|
|
320
|
-
class AgentCompatibilityError <
|
|
320
|
+
class AgentCompatibilityError < CompatibilityError
|
|
321
321
|
end
|
|
322
322
|
|
|
323
|
-
class MysqlCompatibilityError <
|
|
323
|
+
class MysqlCompatibilityError < CompatibilityError
|
|
324
324
|
end
|
|
325
325
|
|
|
326
|
+
class PostgresqlCompatibilityError < CompatibilityError
|
|
327
|
+
end
|
|
326
328
|
|
|
327
329
|
## Error container
|
|
328
330
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'flydata-core/errors'
|
|
2
|
+
require 'flydata-core/postgresql/config'
|
|
3
|
+
require 'pg'
|
|
4
|
+
|
|
5
|
+
module FlydataCore
|
|
6
|
+
module Postgresql
|
|
7
|
+
class CompatibilityChecker
|
|
8
|
+
def initialize(option = {})
|
|
9
|
+
option ||= {}
|
|
10
|
+
@option = option.merge FlydataCore::Postgresql::Config.build_db_opts(option)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def do_check(option = @option, &block)
|
|
14
|
+
result = block.call create_query(option)
|
|
15
|
+
check_result(result, option)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Override
|
|
19
|
+
#def create_query(option = @option)
|
|
20
|
+
#end
|
|
21
|
+
|
|
22
|
+
# Override
|
|
23
|
+
#def validate_result(result, option = @option)
|
|
24
|
+
#end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class PostgresqlCompatibilityChecker < CompatibilityChecker
|
|
28
|
+
def do_check(option = @option, &block)
|
|
29
|
+
query = create_query(option)
|
|
30
|
+
result = if block
|
|
31
|
+
block.call query
|
|
32
|
+
else
|
|
33
|
+
exec_query(query)
|
|
34
|
+
end
|
|
35
|
+
check_result(result, option)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def exec_query(query)
|
|
39
|
+
begin
|
|
40
|
+
client = PGconn.connect(FlydataCore::Postgresql::Config.opts_for_pg(@option))
|
|
41
|
+
client.exec(query)
|
|
42
|
+
ensure
|
|
43
|
+
client.close rescue nil if client
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def schema_and_table_in_query(option = @option)
|
|
48
|
+
schema = if option[:schema].to_s.strip.empty?
|
|
49
|
+
"select current_schema"
|
|
50
|
+
else
|
|
51
|
+
"'#{option[:schema]}'"
|
|
52
|
+
end
|
|
53
|
+
{
|
|
54
|
+
schema_name: schema,
|
|
55
|
+
table_names: option[:tables].collect{|tn| "'#{tn}'"}.join(',')
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class TableExistenceChecker < PostgresqlCompatibilityChecker
|
|
61
|
+
TABLE_EXISTENCE_CHECK_QUERY_TMPLT = <<EOT
|
|
62
|
+
SELECT
|
|
63
|
+
table_name
|
|
64
|
+
FROM
|
|
65
|
+
information_schema.tables
|
|
66
|
+
WHERE
|
|
67
|
+
table_schema in (%{schema_name})
|
|
68
|
+
AND
|
|
69
|
+
table_name in (%{table_names});
|
|
70
|
+
EOT
|
|
71
|
+
|
|
72
|
+
def create_query(option = @option)
|
|
73
|
+
TABLE_EXISTENCE_CHECK_QUERY_TMPLT % schema_and_table_in_query(option)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_result(result, option = @option)
|
|
77
|
+
existing_tables = []
|
|
78
|
+
result.each {|r| existing_tables << r['table_name']}
|
|
79
|
+
missing_tables = option[:tables] - existing_tables
|
|
80
|
+
|
|
81
|
+
unless missing_tables.empty?
|
|
82
|
+
raise FlydataCore::PostgresqlCompatibilityError,
|
|
83
|
+
"These tables are missing. Create these tables on your database or remove them from the data entry : #{missing_tables.join(", ")}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class PrimaryKeyChecker < PostgresqlCompatibilityChecker
|
|
89
|
+
PK_CHECK_QUERY_TMPLT = <<EOT
|
|
90
|
+
SELECT
|
|
91
|
+
t.table_name
|
|
92
|
+
FROM
|
|
93
|
+
(select * from information_schema.tables where table_schema in (%{schema_name}) AND table_name in (%{table_names})) t
|
|
94
|
+
LEFT OUTER JOIN
|
|
95
|
+
(select * from information_schema.table_constraints where table_schema in (%{schema_name}) AND table_name in (%{table_names})) tc
|
|
96
|
+
USING (table_schema, table_name)
|
|
97
|
+
GROUP BY
|
|
98
|
+
t.table_schema, t.table_name
|
|
99
|
+
HAVING
|
|
100
|
+
SUM(CASE WHEN tc.constraint_type='PRIMARY KEY' THEN 1 ELSE 0 END) = 0;
|
|
101
|
+
EOT
|
|
102
|
+
|
|
103
|
+
def create_query(option = @option)
|
|
104
|
+
PK_CHECK_QUERY_TMPLT % schema_and_table_in_query(option)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def check_result(result, option = @option)
|
|
108
|
+
missing_pk_tables = []
|
|
109
|
+
result.each {|r| missing_pk_tables << r['table_name']}
|
|
110
|
+
|
|
111
|
+
unless missing_pk_tables.empty?
|
|
112
|
+
raise FlydataCore::PostgresqlCompatibilityError,
|
|
113
|
+
"Primary key is required for tables to sync. " +
|
|
114
|
+
"Add primary key or remove following tables from data entry: #{missing_pk_tables.join(", ")}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module FlydataCore
|
|
2
|
+
module Postgresql
|
|
3
|
+
class Config
|
|
4
|
+
def self.build_db_opts(db_conf)
|
|
5
|
+
db_opts = [:host,
|
|
6
|
+
:port,
|
|
7
|
+
:username,
|
|
8
|
+
:password,
|
|
9
|
+
:database,
|
|
10
|
+
:dbname,
|
|
11
|
+
:user,
|
|
12
|
+
:connect_timeout,
|
|
13
|
+
:sslmode,
|
|
14
|
+
:options,
|
|
15
|
+
].inject({}) { |h, sym|
|
|
16
|
+
if db_conf.has_key?(sym)
|
|
17
|
+
h[sym] = db_conf[sym]
|
|
18
|
+
elsif db_conf[sym.to_s]
|
|
19
|
+
h[sym] = db_conf[sym.to_s]
|
|
20
|
+
end
|
|
21
|
+
h
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# for pg gem
|
|
25
|
+
update_opts(db_opts, :user, :username)
|
|
26
|
+
update_opts(db_opts, :dbname, :database)
|
|
27
|
+
|
|
28
|
+
db_opts
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns options for making pg connection
|
|
32
|
+
# pg does not accept unsupported keys
|
|
33
|
+
# http://www.rubydoc.info/gems/pg/PG%2FConnection%3Ainitialize
|
|
34
|
+
def self.opts_for_pg(db_conf)
|
|
35
|
+
db_opts = build_db_opts(db_conf)
|
|
36
|
+
|
|
37
|
+
[:host,
|
|
38
|
+
:port,
|
|
39
|
+
:user,
|
|
40
|
+
:password,
|
|
41
|
+
:dbname,
|
|
42
|
+
:connect_timeout,
|
|
43
|
+
:sslmode,
|
|
44
|
+
:options,
|
|
45
|
+
].inject({}) do |ret, k|
|
|
46
|
+
ret[k] = db_opts[k] if db_opts[k]
|
|
47
|
+
ret
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.update_opts(opts, dst_key, src_key)
|
|
52
|
+
if opts[dst_key].nil? && opts[src_key]
|
|
53
|
+
opts[dst_key] = opts[src_key]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
require 'pg'
|
|
2
|
+
require 'socket'
|
|
3
|
+
require 'delegate'
|
|
4
|
+
require 'flydata-core/postgresql/config'
|
|
5
|
+
|
|
6
|
+
module FlydataCore
|
|
7
|
+
module Postgresql
|
|
8
|
+
|
|
9
|
+
class PGClient
|
|
10
|
+
PG_CONNECT_TIMEOUT = 10.0
|
|
11
|
+
|
|
12
|
+
def initialize(dbconf)
|
|
13
|
+
@dbconf = FlydataCore::Postgresql::Config.opts_for_pg(dbconf)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :dbconf
|
|
17
|
+
|
|
18
|
+
def establish_connection
|
|
19
|
+
@conn = create_connection if @conn.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def query(query, params = [])
|
|
23
|
+
establish_connection
|
|
24
|
+
|
|
25
|
+
if query.respond_to?(:placeholder_start_num) && query.placeholder_start_num
|
|
26
|
+
placeholders = placeholder_string(query.placeholder_size,
|
|
27
|
+
query.placeholder_start_num)
|
|
28
|
+
q = query % [placeholders]
|
|
29
|
+
else
|
|
30
|
+
q = query
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if (params.nil? || params.empty?) && query.respond_to?(:binding_params)
|
|
34
|
+
params = query.binding_params || []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
result = @conn.query(q, params)
|
|
38
|
+
|
|
39
|
+
query.respond_to?(:value_overriders) && query.value_overriders ?
|
|
40
|
+
EnumerableDelegator.new(result, HashValueOverrider, query.value_overriders)
|
|
41
|
+
: result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def close
|
|
45
|
+
if @conn
|
|
46
|
+
@conn.finish
|
|
47
|
+
@conn = nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def create_connection
|
|
54
|
+
conn = nil
|
|
55
|
+
retry_on(Errno::EBADF, 3) do
|
|
56
|
+
hostaddr = IPSocket.getaddress(dbconf[:host])
|
|
57
|
+
dbconf[:hostaddr] = hostaddr
|
|
58
|
+
conn = PG::Connection.connect_start(dbconf)
|
|
59
|
+
raise PG::Error.new("Unable to create a new connection.") unless conn
|
|
60
|
+
raise PG::Error.new("Connection failed: %s" % [ conn.error_message ]) if conn.status == PG::CONNECTION_BAD
|
|
61
|
+
|
|
62
|
+
socket = conn.socket_io
|
|
63
|
+
poll_status = PG::PGRES_POLLING_WRITING
|
|
64
|
+
until poll_status == PG::PGRES_POLLING_OK || poll_status == PG::PGRES_POLLING_FAILED
|
|
65
|
+
case poll_status
|
|
66
|
+
when PG::PGRES_POLLING_READING
|
|
67
|
+
IO.select([socket], nil, nil, PG_CONNECT_TIMEOUT) or raise PG::Error.new("Asynchronous connection timed out!(READING)")
|
|
68
|
+
when PG::PGRES_POLLING_WRITING
|
|
69
|
+
IO.select(nil, [socket], nil, PG_CONNECT_TIMEOUT) or raise PG::Error.new("Asynchronous connection timed out!(WRITING)")
|
|
70
|
+
end
|
|
71
|
+
poll_status = conn.connect_poll
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
unless conn.status == PG::CONNECTION_OK
|
|
75
|
+
raise PG::Error.new("Connect failed: %s" % [ conn.error_message.to_s.lines.uniq.join(" ") ])
|
|
76
|
+
end
|
|
77
|
+
conn
|
|
78
|
+
rescue Errno::EBADF => e
|
|
79
|
+
raise PG::Error.new("Failed to connect redshift due to Errno::EBADF. #{e.to_s}")
|
|
80
|
+
rescue SocketError => e
|
|
81
|
+
if e.to_s == 'getaddrinfo: nodename nor servname provided, or not known'
|
|
82
|
+
raise PG::Error.new("Connection failed: FATAL: unknown host(#{dbconf[:host]}).")
|
|
83
|
+
end
|
|
84
|
+
raise e
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Retry the given block if +exception+ happens
|
|
88
|
+
def retry_on(exception = StandardError, try_count = 3, interval = 1.0)
|
|
89
|
+
count = 0
|
|
90
|
+
begin
|
|
91
|
+
count += 1
|
|
92
|
+
yield
|
|
93
|
+
rescue exception
|
|
94
|
+
if count < try_count
|
|
95
|
+
sleep interval
|
|
96
|
+
interval *= 2
|
|
97
|
+
retry
|
|
98
|
+
else
|
|
99
|
+
raise
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def placeholder_string(num_items, start_num)
|
|
105
|
+
num_items.times.collect{|i| "$#{i + start_num}"}.join(",")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class EnumerableDelegator
|
|
109
|
+
include Enumerable
|
|
110
|
+
|
|
111
|
+
def initialize(delegate, item_delegator_class, *args)
|
|
112
|
+
@delegate = delegate
|
|
113
|
+
@item_delegator_class = item_delegator_class
|
|
114
|
+
@args = args
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def each(&block)
|
|
118
|
+
@delegate.each do |item|
|
|
119
|
+
block.call(@item_delegator_class.new(item, *@args))
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class HashValueOverrider < SimpleDelegator
|
|
125
|
+
def initialize(delegate, overriders)
|
|
126
|
+
super(delegate)
|
|
127
|
+
@overriders = overriders
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def [](key)
|
|
131
|
+
val = __getobj__[key]
|
|
132
|
+
override(key, val)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# TODO: all methods returning hash value(s) must be overridden
|
|
136
|
+
|
|
137
|
+
def first
|
|
138
|
+
key, val = __getobj__.first
|
|
139
|
+
[key, override(key, val)]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def values
|
|
143
|
+
__getobj__.keys.collect{|k| override(k, __getobj__[k]) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def kind_of?(klass)
|
|
147
|
+
__getobj__.kind_of?(klass)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def override(key, val)
|
|
153
|
+
@overriders.has_key?(key) ? @overriders[key].call(val) : val
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class PGQuery < SimpleDelegator
|
|
159
|
+
def initialize(query_text, opts)
|
|
160
|
+
super(query_text)
|
|
161
|
+
@value_overriders = opts[:value_overriders]
|
|
162
|
+
@placeholder_start_num = opts[:placeholder_start_num]
|
|
163
|
+
@placeholder_size = opts[:placeholder_size]
|
|
164
|
+
@binding_params = opts[:binding_params]
|
|
165
|
+
end
|
|
166
|
+
attr_reader :value_overriders, :placeholder_start_num, :placeholder_size, :binding_params
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Snapshot components
|
|
2
|
+
# http://www.postgresql.org/docs/8.3/static/functions-info.html#FUNCTIONS-TXID-SNAPSHOT-PARTS
|
|
3
|
+
#
|
|
4
|
+
# Example) 10:20:10,14,15 means xmin=10, xmax=20, xip_list=10, 14, 15.
|
|
5
|
+
|
|
6
|
+
module FlydataCore
|
|
7
|
+
module Postgresql
|
|
8
|
+
|
|
9
|
+
class Snapshot
|
|
10
|
+
include Comparable
|
|
11
|
+
|
|
12
|
+
def initialize(txid_snapshot)
|
|
13
|
+
@txid_snapshot_str = txid_snapshot.to_s
|
|
14
|
+
xmin_str, xmax_str, xip_list_str = @txid_snapshot_str.split(':')
|
|
15
|
+
|
|
16
|
+
raise ArgumentError, "Invalid snapshot - xmin is empty." if xmin_str.to_s.empty?
|
|
17
|
+
raise ArgumentError, "Invalid snapshot - xmax is empty." if xmax_str.to_s.empty?
|
|
18
|
+
|
|
19
|
+
@xmin = xmin_str.to_i
|
|
20
|
+
@xmax = xmax_str.to_i
|
|
21
|
+
@xip_list = xip_list_str.to_s.split(',').collect(&:to_i)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :xmin, :xmax, :xip_list
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
@txid_snapshot_str
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def <=>(other)
|
|
31
|
+
if @xmin == other.xmin
|
|
32
|
+
if @xmax == other.xmax
|
|
33
|
+
# items xip_list will disappear after the transaction is completed,
|
|
34
|
+
# because xip_list is an active transaction list.
|
|
35
|
+
#
|
|
36
|
+
# The following comparison needs to be true
|
|
37
|
+
# "10:18:10,11,12" < "10:18:11,12"
|
|
38
|
+
(other.xip_list.size <=> @xip_list.size)
|
|
39
|
+
else
|
|
40
|
+
@xmax <=> other.xmax
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
@xmin <=> other.xmin
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|