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
@@ -0,0 +1,14 @@
1
+ require 'flydata/plugin_support/source_position_file'
2
+ require 'flydata-core/mysql/binlog_pos'
3
+
4
+ module Flydata
5
+ module SourceMysql
6
+
7
+ module PluginSupport
8
+ class SourcePositionFile < Flydata::PluginSupport::SourcePositionFile
9
+ SOURCE_POS_CLASS = FlydataCore::Mysql::BinlogPos
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -50,8 +50,8 @@ EOS
50
50
  if binlog_pos.nil?
51
51
  # get binlog position
52
52
  binlog_pos = FlydataCore::Mysql::BinlogPos.new(File.open(position_file){|f| f.read })
53
- original_binlog_file = context.current_binlog_file
54
- context.current_binlog_file = binlog_pos.filename
53
+ original_binlog_file = context.cur_src_pos_file
54
+ context.cur_src_pos_file = binlog_pos.filename
55
55
  end
56
56
  # get charset
57
57
  charset = mysql_tabledef.default_source_charset
@@ -77,7 +77,7 @@ EOS
77
77
  # update generated_ddl
78
78
  sync_fm.save_generated_ddl([table], V2_TARGET_VERSION.to_s)
79
79
  end
80
- context.current_binlog_file = original_binlog_file if binlog_pos
80
+ context.cur_src_pos_file = original_binlog_file if binlog_pos
81
81
  end
82
82
  end
83
83
 
@@ -1,3 +1,4 @@
1
+ require 'flydata/table_meta'
1
2
  require 'mysql2'
2
3
  require 'flydata-core/mysql/config'
3
4
  require 'flydata-core/table_def/mysql_table_def'
@@ -5,8 +6,7 @@ require 'flydata-core/table_def/mysql_table_def'
5
6
  module Flydata
6
7
  module SourceMysql
7
8
 
8
- module PluginSupport
9
- class TableMeta
9
+ class TableMeta < Flydata::TableMeta
10
10
  MANDATORY_OPTS = [
11
11
  :host, :port, :username, :password,
12
12
  :database, :tables,
@@ -38,7 +38,7 @@ EOT
38
38
  @table_meta = Hash.new{|h, k| h[k] = {}}
39
39
  end
40
40
 
41
- def update
41
+ def reload
42
42
  conn = Mysql2::Client.new(@db_opts)
43
43
  sql = GET_TABLE_META_SQL % {
44
44
  database: @database, tables: @tables.collect{|t| "'#{t}'"}.join(',') }
@@ -51,14 +51,7 @@ EOT
51
51
  ensure
52
52
  conn.close rescue nil if conn
53
53
  end
54
-
55
- # Return table meta
56
- # :character_set_name
57
- def [](table_name)
58
- @table_meta[table_name.to_sym]
59
- end
60
54
  end
61
55
  end
62
56
 
63
57
  end
64
- end
@@ -1,8 +1,10 @@
1
1
  require 'flydata/source/generate_source_dump'
2
2
  require 'flydata/preference/data_entry_preference'
3
3
  require 'flydata/source_postgresql/postgresql_component'
4
- require 'flydata/source_postgresql/pg_client'
4
+ require 'flydata/source_postgresql/query_based_sync/diff_query_generator'
5
+ require 'flydata/source_postgresql/table_meta'
5
6
  require 'flydata-core/postgresql/source_pos'
7
+ require 'flydata-core/postgresql/pg_client'
6
8
  require 'msgpack'
7
9
 
8
10
  module Flydata
@@ -41,42 +43,49 @@ EOS
41
43
  TABLE_PLACEHOLDER_START_NUM = 2 # because $1 is used by table_schema
42
44
 
43
45
  def dump_size(tables)
44
- cli = PGClient.new(de_prefs)
46
+ cli = FlydataCore::Postgresql::PGClient.new(de_prefs)
45
47
 
46
- res = cli.query(DUMP_SIZE_QUERY, [de_prefs['schema']] + tables, placeholder_size: tables.size, placeholder_start_num: TABLE_PLACEHOLDER_START_NUM)
48
+ query = FlydataCore::Postgresql::PGQuery.new(DUMP_SIZE_QUERY,
49
+ placeholder_size: tables.size,
50
+ placeholder_start_num: TABLE_PLACEHOLDER_START_NUM)
51
+ res = cli.query(query, [de_prefs['schema']] + tables)
47
52
 
48
53
  res.first['total_size'].to_i
54
+
55
+ ensure
56
+ cli.close if cli
49
57
  end
50
58
 
51
59
  def dump(tables, file_path = nil, &src_pos_callback)
60
+ io = nil
52
61
  if file_path
53
62
  io = File.open(file_path, "w")
54
63
  else
55
64
  raise "dump via pipe has not been implemented yet"
56
65
  end
57
66
 
58
- cli = PGClient.new(de_prefs)
59
-
60
- source_pos = get_source_pos(cli, &src_pos_callback)
67
+ table_meta = Flydata::SourcePostgresql::TableMeta.new(de_prefs, tables)
68
+ cli = FlydataCore::Postgresql::PGClient.new(de_prefs)
69
+ table_meta.reload(cli)
61
70
 
62
71
  context = source.sync_generate_table_ddl(dp, nil)
63
- missing_tables = context.each_source_tabledef(tables, de_prefs) do |tabledef, error|
72
+ source_pos = get_source_pos(table_meta.current_snapshot, &src_pos_callback)
73
+
74
+ options = de_prefs.merge(table_meta: table_meta)
75
+ missing_tables = context.each_source_tabledef(tables, options) do |tabledef, error|
64
76
  dump_table(tabledef, source_pos, io, cli) if tabledef
65
77
  end
78
+
66
79
  nil
80
+ ensure
81
+ cli.close if cli
82
+ io.close if io
67
83
  end
68
84
 
69
85
  private
70
86
 
71
- CURRENT_SNAPSHOT_QUERY = <<EOS
72
- SELECT txid_current_snapshot() AS current_snapshot;
73
- EOS
74
-
75
- def get_source_pos(cli, &src_pos_callback)
76
- res = cli.query(CURRENT_SNAPSHOT_QUERY)
77
-
78
- current_snapshot = res.first['current_snapshot']
79
- src_pos = FlydataCore::Postgresql::SourcePos.new(current_snapshot)
87
+ def get_source_pos(snapshot, &src_pos_callback)
88
+ src_pos = FlydataCore::Postgresql::SourcePos.new(snapshot)
80
89
  src_pos_callback.call(nil, src_pos)
81
90
 
82
91
  src_pos
@@ -85,16 +94,13 @@ EOS
85
94
  NUM_ROWS = 500000
86
95
 
87
96
  def dump_table(tabledef, source_pos, io, cli)
88
- fqtn = fq_table_name(de_prefs['schema'], tabledef.table_name)
89
- pk_columns = tabledef.pk_columns
90
- columns = tabledef.column_names
91
-
92
97
  dump_source_table(tabledef, io)
93
98
 
94
99
  last_pks = nil
95
100
  loop do
96
- num_rows, last_pks = dump_table_chunk(fqtn, source_pos, NUM_ROWS,
97
- columns, pk_columns, last_pks, io, cli)
101
+ num_rows, last_pks =
102
+ dump_table_chunk(tabledef.table_name, de_prefs['schema'], source_pos, NUM_ROWS,
103
+ tabledef, tabledef.pk_columns, last_pks, io, cli)
98
104
  break if num_rows < NUM_ROWS
99
105
  end
100
106
  end
@@ -112,24 +118,29 @@ EOS
112
118
  io.write(source_table_hash.to_msgpack)
113
119
  end
114
120
 
115
- SELECT_QUERY = <<EOS
116
- SELECT * FROM %s%s%s LIMIT %s;
117
- EOS
118
-
119
- def dump_table_chunk(fqtn, source_pos, num_rows, columns, pk_columns, last_pks, io, cli)
120
- where_clause = build_where_clause(source_pos, pk_columns, last_pks)
121
- params = last_pks || []
122
- order_by_clause = build_order_by_clause(pk_columns)
123
- query = SELECT_QUERY % [fqtn, where_clause, order_by_clause, num_rows]
121
+ def dump_table_chunk(table, schema, source_pos, num_rows,
122
+ tabledef, pk_columns, last_pks, io, cli)
123
+ query = Flydata::SourcePostgresql::QueryBasedSync::DiffQueryGenerator.new(
124
+ table, schema,
125
+ columns: tabledef.columns,
126
+ to_sid: source_pos.snapshot,
127
+ pk_columns: pk_columns,
128
+ last_pks: last_pks,
129
+ limit: num_rows).build_query
130
+
131
+ column_names = tabledef.columns.inject([]) do |ary, h|
132
+ ary << h[:column]
133
+ end
124
134
 
125
- res = cli.query(query, params)
135
+ res = cli.query(query)
126
136
  count = 0
127
137
  last_row = nil
128
138
  res.each do |row|
129
139
  count += 1
130
140
  last_row = row
131
- dump_row(row, columns, io)
141
+ dump_row(row, column_names, io)
132
142
  end
143
+
133
144
  last_pks = last_row ? pk_columns.collect{|col| last_row[col]} : nil
134
145
 
135
146
  [count, last_pks]
@@ -139,36 +150,6 @@ EOS
139
150
  data = columns.collect{|col| row[col] }.to_msgpack
140
151
  io.write(data)
141
152
  end
142
-
143
- def build_where_clause(source_pos, pk_columns, last_pks)
144
- clause = " WHERE txid_visible_in_snapshot(xmin::TEXT::BIGINT, '#{source_pos.snapshot_id}'::TXID_SNAPSHOT)"
145
- if last_pks
146
- clause += pk_conditions(pk_columns)
147
- end
148
- end
149
-
150
- def build_order_by_clause(pk_columns)
151
- " ORDER BY #{pk_columns.collect{|col| %Q|"#{col}"| }.join(",")}"
152
- end
153
-
154
- def pk_conditions(pk_columns)
155
- i = pk_columns.size - 1
156
- str = nil
157
- while i >= 0
158
- colname = pk_columns[i]
159
- if str
160
- str = %Q|"#{colname}" > $#{i + 1} OR ("#{colname}" = $#{i + 1} AND (#{str}))|
161
- else
162
- str = %Q|"#{colname}" > $#{i + 1}|
163
- end
164
- i -= 1
165
- end
166
- " AND (#{str})"
167
- end
168
-
169
- def fq_table_name(schema, table)
170
- schema ? %Q|"#{schema}"."#{table}"| : %Q|"#{table}"|
171
- end
172
153
  end
173
154
 
174
155
  end
@@ -87,6 +87,7 @@ class DumpParser
87
87
 
88
88
  # {"table_name"=>"users", "columns"=>{"id"=>{"column_name"=>"id", "format_type"=>"bigint"}, "name"=>{"column_name"=>"name", "format_type"=>"character varying"}, "another_id"=>{"column_name"=>"another_id", "format_type"=>"integer"}}}
89
89
  def handle_table_info(table_info)
90
+ call_insert_record_block # flush rows if any
90
91
  call_check_point_block(Parser::State::CREATE_TABLE)
91
92
  columns = table_info["columns"].inject({}) do |h, (k, v)|
92
93
  h[k] = %w(column_name format_type).inject({}) do |hh, kk|
@@ -114,6 +115,7 @@ class DumpParser
114
115
  end
115
116
 
116
117
  def call_insert_record_block
118
+ return if @rows.empty?
117
119
  if @insert_record_block.call(@current_table, @rows)
118
120
  call_check_point_block(Parser::State::INSERT_RECORD)
119
121
  end
@@ -0,0 +1,13 @@
1
+ require 'flydata/plugin_support/context'
2
+ require 'flydata-core/postgresql/source_pos'
3
+
4
+ module Flydata
5
+ module SourcePostgresql
6
+ module PluginSupport
7
+ class Context < ::Flydata::PluginSupport::Context
8
+ register_mandatory_opts :database, :dbconf
9
+ SOURCE_POS_CLASS = ::FlydataCore::Postgresql::SourcePos
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ require 'flydata/plugin_support/source_position_file'
2
+ require 'flydata-core/postgresql/source_pos'
3
+
4
+ module Flydata
5
+ module SourcePostgresql
6
+
7
+ module PluginSupport
8
+ class SourcePositionFile < Flydata::PluginSupport::SourcePositionFile
9
+ SOURCE_POS_CLASS = FlydataCore::Postgresql::SourcePos
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'flydata/query_based_sync/client.rb'
2
+ require 'flydata/source_postgresql/query_based_sync/resource_requester.rb'
3
+ require 'flydata/source_postgresql/query_based_sync/response_handler.rb'
4
+
5
+ module Flydata
6
+ module SourcePostgresql
7
+ module QueryBasedSync
8
+
9
+ class Client < Flydata::QueryBasedSync::Client
10
+ RESOURCE_REQUESTER_CLASS = Flydata::SourcePostgresql::QueryBasedSync::ResourceRequester
11
+ RESPONSE_HANDLER_CLASS = Flydata::SourcePostgresql::QueryBasedSync::ResponseHandler
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,135 @@
1
+ require 'flydata-core/postgresql/pg_client'
2
+
3
+ module Flydata
4
+ module SourcePostgresql
5
+ module QueryBasedSync
6
+
7
+ class DiffQueryGenerator
8
+
9
+ DEFAULT_LIMIT = 10000
10
+
11
+ FETCH_RECORDS_SQL = <<EOS
12
+ SELECT %{columns} FROM %{table_name} WHERE %{where_clause} ORDER BY %{order_by_clause} LIMIT %{max_rows};
13
+ EOS
14
+
15
+ TO_SID_WHERE_CLAUSE = "txid_visible_in_snapshot(xmin::TEXT::BIGINT, '%{to_sid}')"
16
+ FROM_SID_WHERE_CLAUSE = "AND NOT txid_visible_in_snapshot(xmin::TEXT::BIGINT, '%{from_sid}')"
17
+
18
+ def initialize(table, schema, query_cond = {})
19
+ @table = table
20
+ @schema = schema
21
+
22
+ @columns = query_cond[:columns] or raise ArgumentError, "columns is required"
23
+ @from_sid = query_cond[:from_sid]
24
+ @to_sid = query_cond[:to_sid] or raise ArgumentError, "to_sid is required"
25
+ @pk_columns = query_cond[:pk_columns] or raise ArgumentError, "pk_columns is required"
26
+ @last_pks = query_cond[:last_pks]
27
+ @limit = query_cond[:limit] || DEFAULT_LIMIT
28
+ end
29
+
30
+ def build_query(binding_param_enabled = true)
31
+ from_sid_where_clause = if @from_sid
32
+ FROM_SID_WHERE_CLAUSE % {from_sid: @from_sid}
33
+ else
34
+ nil
35
+ end
36
+
37
+ column_names, types = @columns.inject([[],[]]) do |arys, h|
38
+ arys[0] << h[:column]; arys[1] << h[:type].gsub(/\(.+?\)/,''); arys
39
+ end
40
+ columns = column_list(column_names, types)
41
+
42
+ query = FETCH_RECORDS_SQL % {
43
+ columns: columns,
44
+ table_name: fq_table_name(@table, @schema),
45
+ where_clause: build_where_clause(@from_sid, @to_sid, @pk_columns, @last_pks, binding_param_enabled),
46
+ order_by_clause: build_order_by_clause(@pk_columns),
47
+ max_rows: @limit,
48
+ }
49
+
50
+ opts = {
51
+ value_overriders: build_value_overriders(column_names, types)
52
+ }
53
+
54
+ if binding_param_enabled
55
+ opts[:binding_params] = @last_pks
56
+ end
57
+
58
+ FlydataCore::Postgresql::PGQuery.new(query, opts)
59
+ end
60
+
61
+ private
62
+
63
+ def build_where_clause(from_sid, to_sid, pk_columns, last_pks, binding_param_enabled)
64
+ clauses = []
65
+ clauses << TO_SID_WHERE_CLAUSE % { to_sid: to_sid }
66
+ clauses << FROM_SID_WHERE_CLAUSE % { from_sid: from_sid } if from_sid
67
+ if last_pks && !last_pks.empty?
68
+ clauses << pk_conditions(pk_columns, last_pks, binding_param_enabled)
69
+ end
70
+ clauses.join(" ")
71
+ end
72
+
73
+ def build_order_by_clause(pk_columns)
74
+ "#{pk_columns.collect{|col| %Q|"#{col}"| }.join(",")}"
75
+ end
76
+
77
+ def pk_conditions(pk_columns, last_pks, binding_param_enabled)
78
+ i = pk_columns.size - 1
79
+ str = nil
80
+ while i >= 0
81
+ colname = pk_columns[i]
82
+ val = value_in_where(i, last_pks, binding_param_enabled)
83
+ if str
84
+ str = %Q|"#{colname}" > #{val} OR ("#{colname}" = #{val} AND (#{str}))|
85
+ else
86
+ str = %Q|"#{colname}" > #{val}|
87
+ end
88
+ i -= 1
89
+ end
90
+ " AND (#{str})"
91
+ end
92
+
93
+ def value_in_where(index, last_pks, binding_param_enabled)
94
+ if binding_param_enabled
95
+ "$#{index + 1}"
96
+ else
97
+ %Q|'#{last_pks[index]}'|
98
+ end
99
+ end
100
+
101
+ def fq_table_name(table, schema)
102
+ !schema.to_s.empty? ? %Q|"#{schema}"."#{table}"| : %Q|"#{table}"|
103
+ end
104
+
105
+ # return a list of columns with necessary conversion as a string
106
+ QUERY_VAL_CONV_RULES = Hash.new do |h, k|
107
+ h[k] =
108
+ case k
109
+ when "bytea"; %Q['0x' || encode("%<column>s", 'hex') AS "%<column>s"]
110
+ when "money"; %Q["%<column>s"::numeric]
111
+ else %Q["%<column>s"]
112
+ end
113
+ end
114
+
115
+ VALUE_OVERRIDERS = Hash.new do |h, k|
116
+ h[k] =
117
+ case k
118
+ when "bit", "varbit"; -> (v) { v ? '%d' % v.to_i(2) : nil }
119
+ else
120
+ nil
121
+ end
122
+ end
123
+
124
+ def column_list(columns, types)
125
+ columns.each_with_index.collect{|c, i| QUERY_VAL_CONV_RULES[types[i]] % {column:c}}.join(", ")
126
+ end
127
+
128
+ def build_value_overriders(columns, types)
129
+ columns.each_with_index.inject({}) {|h, (c, i)| overrider = VALUE_OVERRIDERS[types[i]]; h[c] = overrider if overrider; h}
130
+ end
131
+ end
132
+
133
+ end
134
+ end
135
+ end