flydata 0.7.12 → 0.7.13
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/Gemfile +1 -0
- data/VERSION +1 -1
- data/flydata-core/lib/flydata-core/oracle/config.rb +25 -0
- data/flydata-core/lib/flydata-core/oracle/oracle_client.rb +48 -0
- data/flydata-core/lib/flydata-core/oracle/query_helper.rb +20 -0
- data/flydata-core/lib/flydata-core/oracle/source_pos.rb +63 -0
- data/flydata-core/lib/flydata-core/table_def/oracle_table_def.rb +167 -0
- data/flydata-core/spec/oracle/config_spec.rb +45 -0
- data/flydata-core/spec/oracle/source_pos_spec.rb +101 -0
- data/flydata.gemspec +0 -0
- data/lib/flydata/command/sync.rb +14 -4
- data/lib/flydata/source.rb +1 -0
- data/lib/flydata/source/sync_repair.rb +25 -0
- data/lib/flydata/source_mysql/generate_source_dump.rb +2 -1
- data/lib/flydata/source_mysql/mysql_accessible.rb +30 -0
- data/lib/flydata/source_mysql/parser/dump_parser.rb +0 -40
- data/lib/flydata/source_mysql/sync_database_size_check.rb +29 -0
- data/lib/flydata/source_mysql/sync_repair.rb +26 -0
- data/lib/flydata/source_oracle/data_entry.rb +24 -0
- data/lib/flydata/source_oracle/generate_source_dump.rb +184 -0
- data/lib/flydata/source_oracle/oracle_component.rb +12 -0
- data/lib/flydata/source_oracle/parse_dump_and_send.rb +128 -0
- data/lib/flydata/source_oracle/plugin_support/context.rb +13 -0
- data/lib/flydata/source_oracle/plugin_support/source_position_file.rb +14 -0
- data/lib/flydata/source_oracle/query_based_sync/diff_query_generator.rb +122 -0
- data/lib/flydata/source_oracle/setup.rb +24 -0
- data/lib/flydata/source_oracle/source_pos.rb +18 -0
- data/lib/flydata/source_oracle/sync.rb +15 -0
- data/lib/flydata/source_oracle/sync_generate_table_ddl.rb +64 -0
- data/lib/flydata/source_oracle/table_meta.rb +220 -0
- data/lib/flydata/source_postgresql/sync_repair.rb +13 -0
- data/spec/flydata/source_mysql/generate_source_dump_spec.rb +2 -2
- metadata +27 -3
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'flydata/source/parse_dump_and_send'
|
2
|
+
require 'flydata/source_oracle/oracle_component'
|
3
|
+
require 'flydata-core/table_def/oracle_table_def'
|
4
|
+
|
5
|
+
module Flydata
|
6
|
+
module SourceOracle
|
7
|
+
|
8
|
+
class ParseDumpAndSend < Source::ParseDumpAndSend
|
9
|
+
include OracleComponent
|
10
|
+
|
11
|
+
def value_converters
|
12
|
+
FlydataCore::TableDef::OracleTableDef::VALUE_CONVERTERS
|
13
|
+
end
|
14
|
+
|
15
|
+
# dump format
|
16
|
+
# dump file is in msgpack. Each table data starts with a source table hash
|
17
|
+
# followed by row arrays.
|
18
|
+
#
|
19
|
+
# {"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"}}}
|
20
|
+
# ["2", "hay", "1"]
|
21
|
+
# ["3", "hoe", "2"]
|
22
|
+
def parse_dump(dump_pos_info, dmpio, create_table_block, insert_record_block,
|
23
|
+
check_point_block)
|
24
|
+
parser = DumpParser.new(dump_pos_info, dmpio, create_table_block,
|
25
|
+
insert_record_block, check_point_block)
|
26
|
+
parser.parse_all
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DumpParser
|
31
|
+
MAX_ROW_BYTES = 1 * 1024 * 1024 # Parser holds rows until the total byte size
|
32
|
+
# reaches this number
|
33
|
+
|
34
|
+
def initialize(dump_pos_info, dmpio, create_table_block, insert_record_block,
|
35
|
+
check_point_block)
|
36
|
+
@source_pos = dump_pos_info[:source_pos]
|
37
|
+
raise ArgumentError.new("source position is required") unless @source_pos
|
38
|
+
@current_table = nil
|
39
|
+
@last_pos = 0
|
40
|
+
@row_head_pos = nil
|
41
|
+
@rows = []
|
42
|
+
@dmpio = dmpio
|
43
|
+
@create_table_block = create_table_block
|
44
|
+
@insert_record_block = insert_record_block
|
45
|
+
@check_point_block = check_point_block
|
46
|
+
|
47
|
+
resume(dump_pos_info)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_all
|
51
|
+
u = MessagePack::Unpacker.new(@dmpio)
|
52
|
+
u.each do |obj|
|
53
|
+
@last_pos = @dmpio.pos - u.buffer.size
|
54
|
+
parse(obj)
|
55
|
+
end
|
56
|
+
close
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse(obj)
|
60
|
+
obj.kind_of?(Hash) ? handle_table_info(obj) : handle_data_row(obj)
|
61
|
+
end
|
62
|
+
|
63
|
+
def close
|
64
|
+
unless @rows.empty?
|
65
|
+
call_insert_record_block
|
66
|
+
# Core logic expects a check point callback with CREATE_TABLE at the
|
67
|
+
# end of table data insertion of each table. #handle_table_info takes
|
68
|
+
# care of all tables but the last table. This is for the last table.
|
69
|
+
call_check_point_block(Parser::State::CREATE_TABLE)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def resume(dump_pos_info)
|
77
|
+
last_pos = dump_pos_info[:last_pos] ? dump_pos_info[:last_pos].to_i : -1
|
78
|
+
if last_pos == -1
|
79
|
+
# no resume point
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
@last_pos = last_pos
|
84
|
+
@dmpio.pos = last_pos
|
85
|
+
@current_table = dump_pos_info[:source_table]
|
86
|
+
end
|
87
|
+
|
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
|
+
def handle_table_info(table_info)
|
90
|
+
call_insert_record_block # flush rows if any
|
91
|
+
call_check_point_block(Parser::State::CREATE_TABLE)
|
92
|
+
columns = table_info["columns"].inject({}) do |h, (k, v)|
|
93
|
+
h[k] = %w(column_name format_type).inject({}) do |hh, kk|
|
94
|
+
hh[kk.to_sym] = v[kk]
|
95
|
+
hh
|
96
|
+
end
|
97
|
+
h
|
98
|
+
end
|
99
|
+
@current_table = Parser::SourceTable.new(table_info["table_name"], columns)
|
100
|
+
@create_table_block.call(@current_table)
|
101
|
+
call_check_point_block(Parser::State::INSERT_RECORD)
|
102
|
+
end
|
103
|
+
|
104
|
+
def handle_data_row(row)
|
105
|
+
@row_head_pos = @last_pos unless @row_head_pos
|
106
|
+
@rows << row
|
107
|
+
if @last_pos - @row_head_pos > MAX_ROW_BYTES
|
108
|
+
call_insert_record_block
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def call_check_point_block(state)
|
113
|
+
@check_point_block.call(@current_table, @last_pos, @last_pos, @source_pos,
|
114
|
+
state)
|
115
|
+
end
|
116
|
+
|
117
|
+
def call_insert_record_block
|
118
|
+
return if @rows.empty?
|
119
|
+
if @insert_record_block.call(@current_table, @rows)
|
120
|
+
call_check_point_block(Parser::State::INSERT_RECORD)
|
121
|
+
end
|
122
|
+
@rows = []
|
123
|
+
@row_head_pos = nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'flydata/plugin_support/context'
|
2
|
+
require 'flydata-core/oracle/source_pos'
|
3
|
+
|
4
|
+
module Flydata
|
5
|
+
module SourceOracle
|
6
|
+
module PluginSupport
|
7
|
+
class Context < ::Flydata::PluginSupport::Context
|
8
|
+
register_mandatory_opts :database, :dbconf
|
9
|
+
SOURCE_POS_CLASS = ::FlydataCore::Oracle::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/oracle/source_pos'
|
3
|
+
|
4
|
+
module Flydata
|
5
|
+
module SourceOracle
|
6
|
+
|
7
|
+
module PluginSupport
|
8
|
+
class SourcePositionFile < Flydata::PluginSupport::SourcePositionFile
|
9
|
+
SOURCE_POS_CLASS = FlydataCore::Oracle::SourcePos
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'flydata-core/oracle/oracle_client'
|
2
|
+
require 'flydata-core/table_def/oracle_table_def'
|
3
|
+
require 'flydata-core/table_def/value_conv'
|
4
|
+
|
5
|
+
module Flydata
|
6
|
+
module SourceOracle
|
7
|
+
module QueryBasedSync
|
8
|
+
|
9
|
+
class DiffQueryGenerator
|
10
|
+
|
11
|
+
DEFAULT_LIMIT = 10000
|
12
|
+
|
13
|
+
FETCH_RECORDS_SQL = <<EOS
|
14
|
+
SELECT %{columns} FROM %{table_name} AS OF SCN %{scn} WHERE %{where_clause} ORDER BY %{order_by_clause}
|
15
|
+
EOS
|
16
|
+
|
17
|
+
def initialize(table, schema, query_cond = {})
|
18
|
+
@table = table
|
19
|
+
@schema = schema
|
20
|
+
|
21
|
+
@columns = query_cond[:columns] or raise ArgumentError, "columns is required"
|
22
|
+
@to_scn = query_cond[:to_scn] or raise ArgumentError, "to_scn is required"
|
23
|
+
@pk_columns = query_cond[:pk_columns] or raise ArgumentError, "pk_columns is required"
|
24
|
+
@last_pks = query_cond[:last_pks]
|
25
|
+
@limit = query_cond[:limit] || DEFAULT_LIMIT
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_query(binding_param_enabled = true)
|
29
|
+
column_names, types = @columns.inject([[],[]]) do |arys, h|
|
30
|
+
arys[0] << h[:column]; arys[1] << h[:type].gsub(/\(.+?\)/,''); arys
|
31
|
+
end
|
32
|
+
columns = column_list(column_names, types)
|
33
|
+
|
34
|
+
query = FETCH_RECORDS_SQL % {
|
35
|
+
columns: columns,
|
36
|
+
table_name: fq_table_name(@table, @schema),
|
37
|
+
scn: @to_scn,
|
38
|
+
where_clause: build_where_clause(@pk_columns, @last_pks, @limit),
|
39
|
+
order_by_clause: build_order_by_clause(@pk_columns),
|
40
|
+
}
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def build_where_clause(pk_columns, last_pks, limit, binding_param_enabled = false)
|
47
|
+
clauses = []
|
48
|
+
if last_pks && !last_pks.empty?
|
49
|
+
clauses << pk_conditions(pk_columns, last_pks, binding_param_enabled)
|
50
|
+
end
|
51
|
+
clauses << "ROWNUM <= #{limit}"
|
52
|
+
clauses.join(" AND ")
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_order_by_clause(pk_columns)
|
56
|
+
"#{pk_columns.collect{|col| %Q|"#{col}"| }.join(",")}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def pk_conditions(pk_columns, last_pks, binding_param_enabled)
|
60
|
+
i = pk_columns.size - 1
|
61
|
+
str = nil
|
62
|
+
while i >= 0
|
63
|
+
colname = pk_columns[i]
|
64
|
+
val = value_in_where(i, last_pks, binding_param_enabled)
|
65
|
+
if str
|
66
|
+
str = %Q|"#{colname}" > #{val} OR ("#{colname}" = #{val} AND (#{str}))|
|
67
|
+
else
|
68
|
+
str = %Q|"#{colname}" > #{val}|
|
69
|
+
end
|
70
|
+
i -= 1
|
71
|
+
end
|
72
|
+
"(#{str})"
|
73
|
+
end
|
74
|
+
|
75
|
+
def value_in_where(index, last_pks, binding_param_enabled)
|
76
|
+
if binding_param_enabled
|
77
|
+
"$#{index + 1}"
|
78
|
+
else
|
79
|
+
%Q|'#{last_pks[index]}'|
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def fq_table_name(table, schema)
|
84
|
+
!schema.to_s.empty? ? %Q|"#{schema.upcase}"."#{table}"| : %Q|"#{table}"|
|
85
|
+
end
|
86
|
+
|
87
|
+
# return a list of columns with necessary conversion as a string
|
88
|
+
QUERY_VAL_CONV_RULES = Hash.new do |h, k|
|
89
|
+
h[k] =
|
90
|
+
case k
|
91
|
+
when "bytea"; %Q['0x' || encode("%<column>s", 'hex') AS "%<column>s"]
|
92
|
+
else %Q["%<column>s"]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
VALUE_OVERRIDERS = Hash.new do |h, k|
|
97
|
+
h[k] =
|
98
|
+
case k
|
99
|
+
when "bit", "varbit"; -> (v) { v ? '%d' % v.to_i(2) : nil }
|
100
|
+
when "boolean"; -> (v) { FlydataCore::TableDef::OracleTableDef::to_boolean(v) }
|
101
|
+
when "money"; -> (v) { v ? FlydataCore::TableDef::ValueConv.strip_currency_format(v) : nil }
|
102
|
+
else
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def column_list(columns, types)
|
108
|
+
columns.each_with_index.collect{|c, i| QUERY_VAL_CONV_RULES[types[i]] % {column:c}}.join(", ")
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_value_overriders(columns, types)
|
112
|
+
columns.each_with_index.inject({}) do |h, (c, i)|
|
113
|
+
overrider = VALUE_OVERRIDERS[types[i]]
|
114
|
+
h[c] = overrider if overrider
|
115
|
+
h
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'flydata/source/setup'
|
2
|
+
require 'flydata/sync_file_manager'
|
3
|
+
|
4
|
+
module Flydata
|
5
|
+
module SourceOracle
|
6
|
+
|
7
|
+
class Setup < Source::Setup
|
8
|
+
def initial_run_need_restart?
|
9
|
+
overinstall?
|
10
|
+
end
|
11
|
+
|
12
|
+
def initial_run_complete_message
|
13
|
+
overinstall? ? :all_done : :initial_sync
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def overinstall?
|
19
|
+
File.exists?(Flydata::SyncFileManager.new(de).source_pos_path)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'flydata/source/source_pos'
|
2
|
+
require 'flydata-core/oracle/source_pos'
|
3
|
+
|
4
|
+
module Flydata
|
5
|
+
module SourceOracle
|
6
|
+
|
7
|
+
class SourcePos < Source::SourcePos
|
8
|
+
def create_source_pos(source_pos_str)
|
9
|
+
FlydataCore::Oracle::SourcePos.load(source_pos_str)
|
10
|
+
end
|
11
|
+
|
12
|
+
def resume_pos(source_pos)
|
13
|
+
source_pos
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'flydata/source/sync_generate_table_ddl'
|
2
|
+
require 'flydata/source_oracle/table_meta'
|
3
|
+
|
4
|
+
module Flydata
|
5
|
+
module SourceOracle
|
6
|
+
|
7
|
+
class SyncGenerateTableDdl < Source::SyncGenerateTableDdl
|
8
|
+
def run_compatibility_check
|
9
|
+
# do nothing for now
|
10
|
+
end
|
11
|
+
|
12
|
+
def data_source_type_display_name
|
13
|
+
"Oracle database"
|
14
|
+
end
|
15
|
+
|
16
|
+
def data_entry_prefs
|
17
|
+
de['oracle_data_entry_preference']
|
18
|
+
end
|
19
|
+
|
20
|
+
def each_source_tabledef(tables, options, &block)
|
21
|
+
# Oracle options.
|
22
|
+
tables = tables.clone
|
23
|
+
if tables.to_s == '' || tables.to_s == '[]'
|
24
|
+
raise ArgumentError, "tables is nil or empty"
|
25
|
+
end
|
26
|
+
_each_tabledef(tables, options, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def table_meta(tables, options)
|
32
|
+
Flydata::SourceOracle::TableMeta.new(options, tables)
|
33
|
+
end
|
34
|
+
|
35
|
+
def _each_tabledef(tables, options, &block)
|
36
|
+
all_table_meta = options[:table_meta]
|
37
|
+
unless all_table_meta
|
38
|
+
all_table_meta = table_meta(tables, options)
|
39
|
+
all_table_meta.reload
|
40
|
+
end
|
41
|
+
|
42
|
+
missing_tables = []
|
43
|
+
|
44
|
+
tables.each do |table|
|
45
|
+
t_meta = all_table_meta[table]
|
46
|
+
unless t_meta && !t_meta.empty?
|
47
|
+
missing_tables << table
|
48
|
+
next
|
49
|
+
end
|
50
|
+
|
51
|
+
table_def = t_meta[:table_def]
|
52
|
+
if table_def
|
53
|
+
yield(table_def, nil)
|
54
|
+
else
|
55
|
+
yield(nil, t_meta[:table_def_err])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
missing_tables
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'flydata/table_meta'
|
2
|
+
require 'flydata-core/oracle/oracle_client'
|
3
|
+
require 'flydata-core/table_def/oracle_table_def'
|
4
|
+
|
5
|
+
|
6
|
+
module Flydata
|
7
|
+
module SourceOracle
|
8
|
+
|
9
|
+
# Fetch and keep table meta information
|
10
|
+
#
|
11
|
+
# <table-name(Symbol)>:
|
12
|
+
# table_name: <String> # Table name
|
13
|
+
# table_schema: <String> or nil # Schema name
|
14
|
+
# primary_keys: <Array of String> # Set primary key names. ex: ['group_id', 'category_id']
|
15
|
+
# pk_positions: <Array of Integer> # Set the ordinal position of primary keys. ex: [1, 3]
|
16
|
+
# max_row_size: <Integer> # byte, calculated based on column size
|
17
|
+
# max_num_rows_per_query>: <Integer> # max number of rows per query
|
18
|
+
# raw_columns: <Hash> # raw information schema data
|
19
|
+
# columns: table_def.columns
|
20
|
+
# table_def:
|
21
|
+
# columns:
|
22
|
+
# table:
|
23
|
+
# column:
|
24
|
+
# type:
|
25
|
+
# not_null:
|
26
|
+
# primary_key:
|
27
|
+
# default:
|
28
|
+
# column_size: (new) # Set in `OracleTableDef.parse_one_column_def`
|
29
|
+
#
|
30
|
+
class TableMeta < Flydata::TableMeta
|
31
|
+
|
32
|
+
GET_TABLE_META_SQL = <<EOS
|
33
|
+
SELECT
|
34
|
+
tbls.OWNER AS schema
|
35
|
+
,cols.TABLE_NAME AS table_name
|
36
|
+
,cols.COLUMN_NAME AS column_name
|
37
|
+
,cols.COLUMN_ID AS ordinal_position
|
38
|
+
,(CASE
|
39
|
+
WHEN pk_cols.CONSTRAINT_NAME IS NULL THEN
|
40
|
+
'f'
|
41
|
+
ELSE
|
42
|
+
't'
|
43
|
+
END) AS is_primary
|
44
|
+
,pk_cols.CONSTRAINT_NAME AS pk_constraint_name
|
45
|
+
,pk_cols.KEY_ORDINAL AS pk_key_ordinal
|
46
|
+
,(CASE
|
47
|
+
WHEN uq_cols.CONSTRAINT_NAME IS NULL THEN
|
48
|
+
'f'
|
49
|
+
ELSE
|
50
|
+
't'
|
51
|
+
END) AS is_unique
|
52
|
+
,uq_cols.CONSTRAINT_NAME AS uq_constraint_name
|
53
|
+
,uq_cols.KEY_ORDINAL AS uq_key_ordinal
|
54
|
+
,cols.DATA_TYPE AS column_data_type
|
55
|
+
,cols.DATA_DEFAULT AS column_default
|
56
|
+
,cols.NULLABLE AS is_nullable
|
57
|
+
,cols.DATA_LENGTH AS max_length
|
58
|
+
,cols.DATA_PRECISION AS precision
|
59
|
+
,cols.DATA_SCALE AS scale
|
60
|
+
FROM
|
61
|
+
(SELECT * FROM ALL_TABLES WHERE OWNER = %{schema}) tbls
|
62
|
+
INNER JOIN ALL_TAB_COLUMNS cols
|
63
|
+
ON cols.OWNER = tbls.OWNER
|
64
|
+
AND cols.TABLE_NAME = tbls.TABLE_NAME
|
65
|
+
LEFT OUTER JOIN (
|
66
|
+
SELECT
|
67
|
+
cons_inner.CONSTRAINT_NAME AS constraint_name
|
68
|
+
,cons_columns_inner.POSITION AS key_ordinal
|
69
|
+
,tbls_inner.TABLE_NAME AS table_name
|
70
|
+
,cols_inner.COLUMN_NAME AS col_name
|
71
|
+
,cols_inner.COLUMN_ID AS col_id
|
72
|
+
FROM
|
73
|
+
(SELECT * FROM ALL_TABLES WHERE OWNER = %{schema}) tbls_inner
|
74
|
+
INNER JOIN ALL_TAB_COLUMNS cols_inner
|
75
|
+
ON cols_inner.OWNER = tbls_inner.OWNER
|
76
|
+
AND cols_inner.TABLE_NAME = tbls_inner.TABLE_NAME
|
77
|
+
INNER JOIN ALL_CONSTRAINTS cons_inner
|
78
|
+
ON cons_inner.OWNER = cols_inner.OWNER
|
79
|
+
AND cons_inner.CONSTRAINT_TYPE = 'P'
|
80
|
+
AND cons_inner.TABLE_NAME = tbls_inner.TABLE_NAME
|
81
|
+
INNER JOIN ALL_CONS_COLUMNS cons_columns_inner
|
82
|
+
ON cons_columns_inner.OWNER = cons_inner.OWNER
|
83
|
+
AND cons_columns_inner.CONSTRAINT_NAME = cons_inner.CONSTRAINT_NAME
|
84
|
+
AND cons_columns_inner.TABLE_NAME = cons_inner.TABLE_NAME
|
85
|
+
AND cons_columns_inner.COLUMN_NAME = cols_inner.COLUMN_NAME
|
86
|
+
) pk_cols
|
87
|
+
ON pk_cols.TABLE_NAME = cols.TABLE_NAME
|
88
|
+
AND pk_cols.COL_NAME = cols.COLUMN_NAME
|
89
|
+
AND pk_cols.COL_ID = cols.COLUMN_ID
|
90
|
+
LEFT OUTER JOIN (
|
91
|
+
SELECT
|
92
|
+
cons_inner.CONSTRAINT_NAME AS constraint_name
|
93
|
+
,cons_columns_inner.POSITION AS key_ordinal
|
94
|
+
,tbls_inner.TABLE_NAME AS table_name
|
95
|
+
,cols_inner.COLUMN_NAME AS col_name
|
96
|
+
,cols_inner.COLUMN_ID AS col_id
|
97
|
+
FROM
|
98
|
+
(SELECT * FROM ALL_TABLES WHERE OWNER = %{schema}) tbls_inner
|
99
|
+
INNER JOIN ALL_TAB_COLUMNS cols_inner
|
100
|
+
ON cols_inner.OWNER = tbls_inner.OWNER
|
101
|
+
AND cols_inner.TABLE_NAME = tbls_inner.TABLE_NAME
|
102
|
+
INNER JOIN ALL_CONSTRAINTS cons_inner
|
103
|
+
ON cons_inner.OWNER = cols_inner.OWNER
|
104
|
+
AND cons_inner.CONSTRAINT_TYPE = 'U'
|
105
|
+
AND cons_inner.TABLE_NAME = tbls_inner.TABLE_NAME
|
106
|
+
INNER JOIN ALL_CONS_COLUMNS cons_columns_inner
|
107
|
+
ON cons_columns_inner.OWNER = cons_inner.OWNER
|
108
|
+
AND cons_columns_inner.CONSTRAINT_NAME = cons_inner.CONSTRAINT_NAME
|
109
|
+
AND cons_columns_inner.TABLE_NAME = cons_inner.TABLE_NAME
|
110
|
+
AND cons_columns_inner.COLUMN_NAME = cols_inner.COLUMN_NAME
|
111
|
+
) uq_cols
|
112
|
+
ON uq_cols.TABLE_NAME = cols.TABLE_NAME
|
113
|
+
AND uq_cols.COL_NAME = cols.COLUMN_NAME
|
114
|
+
AND uq_cols.COL_ID = cols.COLUMN_ID
|
115
|
+
ORDER BY
|
116
|
+
table_name, ordinal_position
|
117
|
+
EOS
|
118
|
+
|
119
|
+
|
120
|
+
GET_CURRENT_SCN_SQL = "SELECT current_scn from v$database"
|
121
|
+
|
122
|
+
DEFAULT_MAX_FETCH_RECORD_SIZE = 50000
|
123
|
+
#DEFAULT_MAX_FETCH_RECORD_SIZE = 8
|
124
|
+
|
125
|
+
def initialize(options, tables, schema = nil)
|
126
|
+
@options = options
|
127
|
+
@database = options[:dbname] || options[:database] || options['database']
|
128
|
+
@tables = tables
|
129
|
+
username = options[:username] || options['username']
|
130
|
+
@schema = (schema || options[:schema] || options['schema'] || username).to_s.strip.upcase
|
131
|
+
end
|
132
|
+
|
133
|
+
attr_reader :current_scn
|
134
|
+
|
135
|
+
def reload(oracle_client = nil)
|
136
|
+
schema = {
|
137
|
+
database: "'#{@database}'",
|
138
|
+
schema: "'#{@schema}'",
|
139
|
+
tables: @tables.collect{|t| "'#{t}'"}.join(','),
|
140
|
+
}
|
141
|
+
|
142
|
+
conn = oracle_client
|
143
|
+
if conn.nil?
|
144
|
+
local_conn = conn = FlydataCore::Oracle::OracleClient.new(@options)
|
145
|
+
end
|
146
|
+
|
147
|
+
sql = GET_TABLE_META_SQL % schema
|
148
|
+
|
149
|
+
# Set table_meta
|
150
|
+
columns = conn.query(sql)
|
151
|
+
@table_meta = build_table_meta(columns)
|
152
|
+
|
153
|
+
# Set current scn
|
154
|
+
current_scn_str = conn.query(GET_CURRENT_SCN_SQL).fetch.first
|
155
|
+
|
156
|
+
raise "Invalid scn is retrieved" if current_scn_str.nil?
|
157
|
+
@current_scn = current_scn_str.to_i
|
158
|
+
|
159
|
+
self
|
160
|
+
ensure
|
161
|
+
local_conn.close rescue nil if local_conn
|
162
|
+
end
|
163
|
+
|
164
|
+
def build_table_meta(columns)
|
165
|
+
ret = Hash.new{|h,k| h[k]={} }
|
166
|
+
|
167
|
+
# Put ret[<table-name-sym>][:raw_columns]
|
168
|
+
while (col = columns.fetch_hash)
|
169
|
+
column_name = col['COLUMN_NAME'].to_sym
|
170
|
+
table_name = col['TABLE_NAME'].to_sym
|
171
|
+
t_meta = ret[table_name]
|
172
|
+
t_meta[:raw_columns] = Hash.new {|h,k| h[k] = []} unless t_meta[:raw_columns]
|
173
|
+
t_meta[:raw_columns][column_name] << col
|
174
|
+
end
|
175
|
+
|
176
|
+
ret.each do |table_name, t_meta|
|
177
|
+
begin
|
178
|
+
table_def = FlydataCore::TableDef::OracleTableDef.create(
|
179
|
+
t_meta[:raw_columns].values, @options)
|
180
|
+
rescue FlydataCore::TableDefError => e
|
181
|
+
t_meta.merge!(
|
182
|
+
table_name: table_name,
|
183
|
+
table_def_err: e,
|
184
|
+
)
|
185
|
+
# Skip when getting an error when parsing the columns
|
186
|
+
next
|
187
|
+
end
|
188
|
+
|
189
|
+
primary_keys = []
|
190
|
+
pk_positions = []
|
191
|
+
table_def.columns.each.with_index(1) do |col, index|
|
192
|
+
col_name = col[:column]
|
193
|
+
if col["CONSTRAINT_TYPE"] == 'P'
|
194
|
+
primary_keys << col_name
|
195
|
+
pk_positions << index.to_s
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
t_meta.merge!(
|
200
|
+
table_name: table_name.to_s,
|
201
|
+
table_schema: @schema,
|
202
|
+
primary_keys: primary_keys,
|
203
|
+
pk_positions: pk_positions,
|
204
|
+
max_num_rows_per_query: DEFAULT_MAX_FETCH_RECORD_SIZE,
|
205
|
+
columns: table_def.columns,
|
206
|
+
table_def: table_def,
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
ret
|
211
|
+
end
|
212
|
+
|
213
|
+
def calc_column_size(column)
|
214
|
+
#TODO: Implement the check logic based on column type
|
215
|
+
124
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|