flydata 0.6.14 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/flydata-core/Gemfile +1 -0
  4. data/flydata-core/Gemfile.lock +5 -0
  5. data/flydata-core/lib/flydata-core/errors.rb +4 -2
  6. data/flydata-core/lib/flydata-core/mysql/binlog_pos.rb +4 -0
  7. data/flydata-core/lib/flydata-core/postgresql/compatibility_checker.rb +119 -0
  8. data/flydata-core/lib/flydata-core/postgresql/config.rb +58 -0
  9. data/flydata-core/lib/flydata-core/postgresql/pg_client.rb +170 -0
  10. data/flydata-core/lib/flydata-core/postgresql/snapshot.rb +49 -0
  11. data/flydata-core/lib/flydata-core/postgresql/source_pos.rb +71 -10
  12. data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +1 -1
  13. data/flydata-core/lib/flydata-core/table_def/postgresql_table_def.rb +76 -17
  14. data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +59 -10
  15. data/flydata-core/spec/mysql/binlog_pos_spec.rb +10 -2
  16. data/flydata-core/spec/postgresql/compatibility_checker_spec.rb +148 -0
  17. data/flydata-core/spec/postgresql/config_spec.rb +85 -0
  18. data/flydata-core/spec/postgresql/pg_client_spec.rb +195 -0
  19. data/flydata-core/spec/postgresql/snapshot_spec.rb +55 -0
  20. data/flydata-core/spec/postgresql/source_pos_spec.rb +70 -8
  21. data/flydata-core/spec/table_def/postgresql_table_def_spec.rb +80 -19
  22. data/flydata-core/spec/table_def/redshift_table_def_spec.rb +211 -14
  23. data/flydata.gemspec +0 -0
  24. data/lib/flydata.rb +1 -0
  25. data/lib/flydata/command/sender.rb +10 -7
  26. data/lib/flydata/command/sync.rb +4 -1
  27. data/lib/flydata/fluent-plugins/flydata_plugin_ext/base.rb +1 -0
  28. data/lib/flydata/fluent-plugins/flydata_plugin_ext/fluent_log_ext.rb +73 -0
  29. data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync.rb +35 -10
  30. data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based.rb +29 -0
  31. data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based.rb +26 -0
  32. data/lib/flydata/fluent-plugins/flydata_plugin_ext/preference.rb +29 -13
  33. data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +10 -18
  34. data/lib/flydata/fluent-plugins/in_postgresql_query_based_flydata.rb +64 -0
  35. data/lib/flydata/helpers.rb +1 -3
  36. data/lib/flydata/plugin_support/context.rb +14 -2
  37. data/lib/flydata/plugin_support/source_position_file.rb +35 -0
  38. data/lib/flydata/plugin_support/sync_record_emittable.rb +2 -1
  39. data/lib/flydata/query_based_sync/client.rb +101 -0
  40. data/lib/flydata/query_based_sync/record_size_estimator.rb +39 -0
  41. data/lib/flydata/query_based_sync/resource_requester.rb +70 -0
  42. data/lib/flydata/query_based_sync/response.rb +122 -0
  43. data/lib/flydata/query_based_sync/response_handler.rb +30 -0
  44. data/lib/flydata/source/sync_generate_table_ddl.rb +1 -1
  45. data/lib/flydata/source_mysql/plugin_support/binlog_record_dispatcher.rb +2 -2
  46. data/lib/flydata/source_mysql/plugin_support/binlog_record_handler.rb +3 -9
  47. data/lib/flydata/source_mysql/plugin_support/context.rb +26 -2
  48. data/lib/flydata/source_mysql/plugin_support/source_position_file.rb +14 -0
  49. data/lib/flydata/source_mysql/table_ddl.rb +3 -3
  50. data/lib/flydata/source_mysql/{plugin_support/table_meta.rb → table_meta.rb} +3 -10
  51. data/lib/flydata/source_postgresql/generate_source_dump.rb +44 -63
  52. data/lib/flydata/source_postgresql/parse_dump_and_send.rb +2 -0
  53. data/lib/flydata/source_postgresql/plugin_support/context.rb +13 -0
  54. data/lib/flydata/source_postgresql/plugin_support/source_position_file.rb +14 -0
  55. data/lib/flydata/source_postgresql/query_based_sync/client.rb +16 -0
  56. data/lib/flydata/source_postgresql/query_based_sync/diff_query_generator.rb +135 -0
  57. data/lib/flydata/source_postgresql/query_based_sync/resource_requester.rb +86 -0
  58. data/lib/flydata/source_postgresql/query_based_sync/response.rb +12 -0
  59. data/lib/flydata/source_postgresql/query_based_sync/response_handler.rb +12 -0
  60. data/lib/flydata/source_postgresql/sync_generate_table_ddl.rb +25 -79
  61. data/lib/flydata/source_postgresql/table_meta.rb +168 -0
  62. data/lib/flydata/sync_file_manager.rb +5 -5
  63. data/lib/flydata/table_meta.rb +19 -0
  64. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_context.rb +85 -0
  65. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based_shared_examples.rb +36 -0
  66. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based_shared_examples.rb +37 -0
  67. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_shared_examples.rb +67 -0
  68. data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +119 -96
  69. data/spec/flydata/fluent-plugins/in_postgresql_query_based_flydata_spec.rb +82 -0
  70. data/spec/flydata/fluent-plugins/sync_source_plugin_context.rb +29 -0
  71. data/spec/flydata/plugin_support/context_spec.rb +37 -3
  72. data/spec/flydata/query_based_sync/client_spec.rb +79 -0
  73. data/spec/flydata/query_based_sync/query_based_sync_context.rb +116 -0
  74. data/spec/flydata/query_based_sync/record_size_estimator_spec.rb +54 -0
  75. data/spec/flydata/query_based_sync/resource_requester_spec.rb +58 -0
  76. data/spec/flydata/query_based_sync/response_handler_spec.rb +36 -0
  77. data/spec/flydata/query_based_sync/response_spec.rb +157 -0
  78. data/spec/flydata/source_mysql/plugin_support/context_spec.rb +7 -1
  79. data/spec/flydata/source_mysql/plugin_support/dml_record_handler_spec.rb +2 -15
  80. data/spec/flydata/source_mysql/plugin_support/drop_database_query_handler_spec.rb +1 -1
  81. data/spec/flydata/source_mysql/plugin_support/shared_query_handler_context.rb +12 -11
  82. data/spec/flydata/source_mysql/plugin_support/source_position_file_spec.rb +53 -0
  83. data/spec/flydata/source_mysql/plugin_support/truncate_query_handler_spec.rb +1 -1
  84. data/spec/flydata/source_mysql/table_ddl_spec.rb +5 -5
  85. data/spec/flydata/source_mysql/{plugin_support/table_meta_spec.rb → table_meta_spec.rb} +6 -7
  86. data/spec/flydata/source_postgresql/generate_source_dump_spec.rb +165 -77
  87. data/spec/flydata/source_postgresql/query_based_sync/diff_query_generator_spec.rb +213 -0
  88. data/spec/flydata/source_postgresql/query_based_sync/query_based_sync_postgresql_context.rb +76 -0
  89. data/spec/flydata/source_postgresql/query_based_sync/resource_requester_spec.rb +70 -0
  90. data/spec/flydata/source_postgresql/table_meta_spec.rb +77 -0
  91. metadata +49 -6
  92. data/lib/flydata/source_mysql/plugin_support/binlog_position_file.rb +0 -23
  93. 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: be893f658b13cbde2228307938f47f98d101f168
4
- data.tar.gz: 4bdcaa201f75760393348f3910d333d91e9e7e6f
3
+ metadata.gz: b335ff2222169fc42a78d4dec2a5829aa0c94230
4
+ data.tar.gz: 15042a74de15a15b3784c6917973154d1ddd02c7
5
5
  SHA512:
6
- metadata.gz: 6205f32c0d7199f0f285c7d6e61de96ee72e44498f1973bc37349af00564012c23640ef5d45a4e47bbd87464ef45155b144b93dd58ebf6d8860f48eb16478a7b
7
- data.tar.gz: d90c9ee2fc5e362df5da45995ea3287b7845df17e46330d51115e0fcb5f506c3e3ca50d1d2c24f3203bac2de77412367dc5bd66184424affbb7634182e3b61be
6
+ metadata.gz: 86fb1aa9fe1449e11f0d8755773cd7a083054bb668df7dbf489973828b018f9ebe2e9a10850bcf8ca7afff7682f157cd7d3a3082907b458ad1b5cbba99cf73b5
7
+ data.tar.gz: d36c033143bdd9a1b5931d891406ec6dfbaeaeba70cdae08d78c3282ccf55bebbe3125921bec021bbbb9405907a52431ac0f220722fa10f771db421134c7f348
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.14
1
+ 0.7.0
data/flydata-core/Gemfile CHANGED
@@ -9,6 +9,7 @@ group :development, :test do
9
9
  gem 'ruby-prof'
10
10
  gem 'rubocop'
11
11
  gem 'mysql2'
12
+ gem 'pg'
12
13
  gem 'rspec', '~> 3.1'
13
14
  gem 'simplecov', :require => false
14
15
  end
@@ -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 < StandardError
320
+ class AgentCompatibilityError < CompatibilityError
321
321
  end
322
322
 
323
- class MysqlCompatibilityError < StandardError
323
+ class MysqlCompatibilityError < CompatibilityError
324
324
  end
325
325
 
326
+ class PostgresqlCompatibilityError < CompatibilityError
327
+ end
326
328
 
327
329
  ## Error container
328
330
 
@@ -29,6 +29,10 @@ class BinlogPos
29
29
  @pos = @pos.to_i
30
30
  end
31
31
 
32
+ def self.load(binlog_pos_str)
33
+ self.new(binlog_pos_str)
34
+ end
35
+
32
36
  attr_reader :filename, :pos
33
37
 
34
38
  def empty?
@@ -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