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.
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