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