activerecord-oracle_enhanced-adapter 7.2.1-java
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 +7 -0
- data/History.md +1977 -0
- data/License.txt +20 -0
- data/README.md +863 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +344 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +295 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +561 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +45 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +457 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +94 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +738 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +366 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +865 -0
- data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
- data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
- data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
- data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
- data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
- data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
- data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
- data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
- data/lib/arel/visitors/oracle.rb +216 -0
- data/lib/arel/visitors/oracle12.rb +121 -0
- data/lib/arel/visitors/oracle_common.rb +51 -0
- data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +36 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +574 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1289 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +474 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
- data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
- data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
- data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
- data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
- data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
- data/spec/active_record/oracle_enhanced/type/integer_spec.rb +99 -0
- data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +56 -0
- data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
- data/spec/active_record/oracle_enhanced/type/text_spec.rb +244 -0
- data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +77 -0
- data/spec/spec_config.yaml.template +11 -0
- data/spec/spec_helper.rb +214 -0
- data/spec/support/alter_system_set_open_cursors.sql +1 -0
- data/spec/support/alter_system_user_password.sql +2 -0
- data/spec/support/create_oracle_enhanced_users.sql +31 -0
- metadata +177 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module OracleEnhanced
|
|
6
|
+
module Quoting
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
# QUOTING ==================================================
|
|
9
|
+
#
|
|
10
|
+
# see: abstract/quoting.rb
|
|
11
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
|
12
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
|
13
|
+
|
|
14
|
+
module ClassMethods # :nodoc:
|
|
15
|
+
def column_name_matcher
|
|
16
|
+
/
|
|
17
|
+
\A
|
|
18
|
+
(
|
|
19
|
+
(?:
|
|
20
|
+
# "table_name"."column_name" | function(one or no argument)
|
|
21
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
|
22
|
+
)
|
|
23
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
|
24
|
+
)
|
|
25
|
+
(?:\s*,\s*\g<1>)*
|
|
26
|
+
\z
|
|
27
|
+
/ix
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def column_name_with_order_matcher
|
|
31
|
+
/
|
|
32
|
+
\A
|
|
33
|
+
(
|
|
34
|
+
(?:
|
|
35
|
+
# "table_name"."column_name" | function(one or no argument)
|
|
36
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
|
37
|
+
)
|
|
38
|
+
(?:\s+ASC|\s+DESC)?
|
|
39
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
|
40
|
+
)
|
|
41
|
+
(?:\s*,\s*\g<1>)*
|
|
42
|
+
\z
|
|
43
|
+
/ix
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def quote_column_name(name) # :nodoc:
|
|
47
|
+
name = name.to_s
|
|
48
|
+
QUOTED_COLUMN_NAMES[name] ||= if /\A[a-z][a-z_0-9$#]*\Z/.match?(name)
|
|
49
|
+
"\"#{name.upcase}\""
|
|
50
|
+
else
|
|
51
|
+
# remove double quotes which cannot be used inside quoted identifier
|
|
52
|
+
"\"#{name.delete('"')}\""
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def quote_table_name(name) # :nodoc:
|
|
57
|
+
name, _link = name.to_s.split("@")
|
|
58
|
+
QUOTED_TABLE_NAMES[name] ||= [name.split(".").map { |n| quote_column_name(n) }].join(".")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# This method is used in add_index to identify either column name (which is quoted)
|
|
63
|
+
# or function based index (in which case function expression is not quoted)
|
|
64
|
+
def quote_column_name_or_expression(name) # :nodoc:
|
|
65
|
+
name = name.to_s
|
|
66
|
+
case name
|
|
67
|
+
# if only valid lowercase column characters in name
|
|
68
|
+
when /^[a-z][a-z_0-9$#]*$/
|
|
69
|
+
"\"#{name.upcase}\""
|
|
70
|
+
when /^[a-z][a-z_0-9$#-]*$/i
|
|
71
|
+
"\"#{name}\""
|
|
72
|
+
# if other characters present then assume that it is expression
|
|
73
|
+
# which should not be quoted
|
|
74
|
+
else
|
|
75
|
+
name
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Names must be from 1 to 30 bytes long with these exceptions:
|
|
80
|
+
# * Names of databases are limited to 8 bytes.
|
|
81
|
+
# * Names of database links can be as long as 128 bytes.
|
|
82
|
+
#
|
|
83
|
+
# Nonquoted identifiers cannot be Oracle Database reserved words
|
|
84
|
+
#
|
|
85
|
+
# Nonquoted identifiers must begin with an alphabetic character from
|
|
86
|
+
# your database character set
|
|
87
|
+
#
|
|
88
|
+
# Nonquoted identifiers can contain only alphanumeric characters from
|
|
89
|
+
# your database character set and the underscore (_), dollar sign ($),
|
|
90
|
+
# and pound sign (#).
|
|
91
|
+
# Oracle strongly discourages you from using $ and # in nonquoted identifiers.
|
|
92
|
+
NONQUOTED_OBJECT_NAME = /[[:alpha:]][\w$#]{0,29}/
|
|
93
|
+
VALID_TABLE_NAME = /\A(?:#{NONQUOTED_OBJECT_NAME}\.)?#{NONQUOTED_OBJECT_NAME}?\Z/
|
|
94
|
+
|
|
95
|
+
# unescaped table name should start with letter and
|
|
96
|
+
# contain letters, digits, _, $ or #
|
|
97
|
+
# can be prefixed with schema name
|
|
98
|
+
# CamelCase table names should be quoted
|
|
99
|
+
def self.valid_table_name?(name) # :nodoc:
|
|
100
|
+
object_name = name.to_s
|
|
101
|
+
!!(object_name =~ VALID_TABLE_NAME && !mixed_case?(object_name))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.mixed_case?(name)
|
|
105
|
+
object_name = name.include?(".") ? name.split(".").second : name
|
|
106
|
+
!!(object_name =~ /[A-Z]/ && object_name =~ /[a-z]/)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def quote_string(s) # :nodoc:
|
|
110
|
+
s.gsub(/'/, "''")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def quote(value) # :nodoc:
|
|
114
|
+
case value
|
|
115
|
+
when Type::OracleEnhanced::CharacterString::Data then
|
|
116
|
+
"'#{quote_string(value.to_s)}'"
|
|
117
|
+
when Type::OracleEnhanced::NationalCharacterString::Data then
|
|
118
|
+
+"N" << "'#{quote_string(value.to_s)}'"
|
|
119
|
+
when ActiveModel::Type::Binary::Data then
|
|
120
|
+
"empty_blob()"
|
|
121
|
+
when Type::OracleEnhanced::Text::Data then
|
|
122
|
+
"empty_clob()"
|
|
123
|
+
when Type::OracleEnhanced::NationalCharacterText::Data then
|
|
124
|
+
"empty_nclob()"
|
|
125
|
+
else
|
|
126
|
+
super
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def quoted_true # :nodoc:
|
|
131
|
+
return "'Y'" if emulate_booleans_from_strings
|
|
132
|
+
"1"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def unquoted_true # :nodoc:
|
|
136
|
+
return "Y" if emulate_booleans_from_strings
|
|
137
|
+
"1"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def quoted_false # :nodoc:
|
|
141
|
+
return "'N'" if emulate_booleans_from_strings
|
|
142
|
+
"0"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def unquoted_false # :nodoc:
|
|
146
|
+
return "N" if emulate_booleans_from_strings
|
|
147
|
+
"0"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def type_cast(value)
|
|
151
|
+
case value
|
|
152
|
+
when Type::OracleEnhanced::TimestampTz::Data, Type::OracleEnhanced::TimestampLtz::Data
|
|
153
|
+
if value.acts_like?(:time)
|
|
154
|
+
zone_conversion_method = ActiveRecord.default_timezone == :utc ? :getutc : :getlocal
|
|
155
|
+
value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
|
|
156
|
+
else
|
|
157
|
+
value
|
|
158
|
+
end
|
|
159
|
+
when Type::OracleEnhanced::NationalCharacterString::Data
|
|
160
|
+
value.to_s
|
|
161
|
+
when Type::OracleEnhanced::CharacterString::Data
|
|
162
|
+
value
|
|
163
|
+
else
|
|
164
|
+
super
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
def oracle_downcase(column_name)
|
|
170
|
+
return nil if column_name.nil?
|
|
171
|
+
/[a-z]/.match?(column_name) ? column_name : column_name.downcase
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# if MRI or YARV or TruffleRuby
|
|
179
|
+
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby"
|
|
180
|
+
require "active_record/connection_adapters/oracle_enhanced/oci_quoting"
|
|
181
|
+
# if JRuby
|
|
182
|
+
elsif RUBY_ENGINE == "jruby"
|
|
183
|
+
require "active_record/connection_adapters/oracle_enhanced/jdbc_quoting"
|
|
184
|
+
else
|
|
185
|
+
raise "Unsupported Ruby engine #{RUBY_ENGINE}"
|
|
186
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module OracleEnhanced
|
|
6
|
+
class SchemaCreation < SchemaCreation
|
|
7
|
+
private
|
|
8
|
+
def visit_ColumnDefinition(o)
|
|
9
|
+
if [:blob, :clob, :nclob].include?(sql_type = type_to_sql(o.type, **o.options).downcase.to_sym)
|
|
10
|
+
if (tablespace = default_tablespace_for(sql_type))
|
|
11
|
+
@lob_tablespaces ||= {}
|
|
12
|
+
@lob_tablespaces[o.name] = tablespace
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def visit_TableDefinition(o)
|
|
19
|
+
create_sql = +"CREATE#{' GLOBAL TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
|
|
20
|
+
statements = o.columns.map { |c| accept c }
|
|
21
|
+
statements << accept(o.primary_keys) if o.primary_keys
|
|
22
|
+
|
|
23
|
+
if use_foreign_keys?
|
|
24
|
+
statements.concat(o.foreign_keys.map { |fk| accept fk })
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
create_sql << "(#{statements.join(', ')})" if statements.present?
|
|
28
|
+
|
|
29
|
+
unless o.temporary
|
|
30
|
+
@lob_tablespaces.each do |lob_column, tablespace|
|
|
31
|
+
create_sql << " LOB (#{quote_column_name(lob_column)}) STORE AS (TABLESPACE #{tablespace}) \n"
|
|
32
|
+
end if defined?(@lob_tablespaces)
|
|
33
|
+
create_sql << " ORGANIZATION #{o.organization}" if o.organization
|
|
34
|
+
if (tablespace = o.tablespace || default_tablespace_for(:table))
|
|
35
|
+
create_sql << " TABLESPACE #{tablespace}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
add_table_options!(create_sql, o)
|
|
39
|
+
create_sql << " AS #{to_sql(o.as)}" if o.as
|
|
40
|
+
create_sql
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def default_tablespace_for(type)
|
|
44
|
+
OracleEnhancedAdapter.default_tablespaces[type]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_column_options!(sql, options)
|
|
48
|
+
type = options[:type] || ((column = options[:column]) && column.type)
|
|
49
|
+
type = type && type.to_sym
|
|
50
|
+
# handle case of defaults for CLOB/NCLOB columns, which would otherwise get "quoted" incorrectly
|
|
51
|
+
if options_include_default?(options)
|
|
52
|
+
if type == :text
|
|
53
|
+
sql << " DEFAULT #{@conn.quote(options[:default])}"
|
|
54
|
+
elsif type == :ntext
|
|
55
|
+
sql << " DEFAULT #{@conn.quote(options[:default])}"
|
|
56
|
+
else
|
|
57
|
+
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
# must explicitly add NULL or NOT NULL to allow change_column to work on migrations
|
|
61
|
+
if options[:null] == false
|
|
62
|
+
sql << " NOT NULL"
|
|
63
|
+
elsif options[:null] == true
|
|
64
|
+
sql << " NULL" unless type == :primary_key
|
|
65
|
+
end
|
|
66
|
+
# add AS expression for virtual columns
|
|
67
|
+
if options[:as].present?
|
|
68
|
+
sql << " AS (#{options[:as]})"
|
|
69
|
+
end
|
|
70
|
+
if options[:primary_key] == true
|
|
71
|
+
sql << " PRIMARY KEY"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def action_sql(action, dependency)
|
|
76
|
+
if action == "UPDATE"
|
|
77
|
+
raise ArgumentError, <<~MSG
|
|
78
|
+
'#{action}' is not supported by Oracle
|
|
79
|
+
MSG
|
|
80
|
+
end
|
|
81
|
+
case dependency
|
|
82
|
+
when :nullify then "ON #{action} SET NULL"
|
|
83
|
+
when :cascade then "ON #{action} CASCADE"
|
|
84
|
+
else
|
|
85
|
+
raise ArgumentError, <<~MSG
|
|
86
|
+
'#{dependency}' is not supported for #{action}
|
|
87
|
+
Supported values are: :nullify, :cascade
|
|
88
|
+
MSG
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module OracleEnhanced
|
|
6
|
+
module ColumnMethods
|
|
7
|
+
def primary_key(name, type = :primary_key, **options)
|
|
8
|
+
# This is a placeholder for future :auto_increment support
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
[
|
|
13
|
+
:raw,
|
|
14
|
+
:timestamptz,
|
|
15
|
+
:timestampltz,
|
|
16
|
+
:ntext
|
|
17
|
+
].each do |column_type|
|
|
18
|
+
module_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
19
|
+
def #{column_type}(*args, **options)
|
|
20
|
+
args.each { |name| column(name, :#{column_type}, **options) }
|
|
21
|
+
end
|
|
22
|
+
CODE
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ReferenceDefinition < ActiveRecord::ConnectionAdapters::ReferenceDefinition # :nodoc:
|
|
27
|
+
def initialize(
|
|
28
|
+
name,
|
|
29
|
+
polymorphic: false,
|
|
30
|
+
index: true,
|
|
31
|
+
foreign_key: false,
|
|
32
|
+
type: :integer,
|
|
33
|
+
**options)
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class SynonymDefinition < Struct.new(:name, :table_owner, :table_name) # :nodoc:
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
|
|
42
|
+
attr_accessor :parameters, :statement_parameters, :tablespace
|
|
43
|
+
|
|
44
|
+
def initialize(table, name, unique, columns, orders, type, parameters, statement_parameters, tablespace)
|
|
45
|
+
@parameters = parameters
|
|
46
|
+
@statement_parameters = statement_parameters
|
|
47
|
+
@tablespace = tablespace
|
|
48
|
+
super(table, name, unique, columns, orders: orders, type: type)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
|
53
|
+
include OracleEnhanced::ColumnMethods
|
|
54
|
+
|
|
55
|
+
attr_accessor :tablespace, :organization
|
|
56
|
+
def initialize(
|
|
57
|
+
conn,
|
|
58
|
+
name,
|
|
59
|
+
temporary: false,
|
|
60
|
+
options: nil,
|
|
61
|
+
as: nil,
|
|
62
|
+
tablespace: nil,
|
|
63
|
+
organization: nil,
|
|
64
|
+
comment: nil,
|
|
65
|
+
**
|
|
66
|
+
)
|
|
67
|
+
@tablespace = tablespace
|
|
68
|
+
@organization = organization
|
|
69
|
+
super(conn, name, temporary: temporary, options: options, as: as, comment: comment)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def new_column_definition(name, type, **options) # :nodoc:
|
|
73
|
+
if type == :virtual
|
|
74
|
+
raise "No virtual column definition found." unless options[:as]
|
|
75
|
+
type = options[:type]
|
|
76
|
+
end
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def references(*args, **options)
|
|
81
|
+
super(*args, type: :integer, **options)
|
|
82
|
+
end
|
|
83
|
+
alias :belongs_to :references
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
def valid_column_definition_options
|
|
87
|
+
super + [ :as, :sequence_name, :sequence_start_value, :type ]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
|
95
|
+
include OracleEnhanced::ColumnMethods
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord # :nodoc:
|
|
4
|
+
module ConnectionAdapters # :nodoc:
|
|
5
|
+
module OracleEnhanced # :nodoc:
|
|
6
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
|
7
|
+
DEFAULT_PRIMARY_KEY_COLUMN_SPEC = { precision: "38", null: "false" }.freeze
|
|
8
|
+
private_constant :DEFAULT_PRIMARY_KEY_COLUMN_SPEC
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
def column_spec_for_primary_key(column)
|
|
12
|
+
spec = super
|
|
13
|
+
spec.except!(:precision) if prepare_column_options(column) == DEFAULT_PRIMARY_KEY_COLUMN_SPEC
|
|
14
|
+
spec
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def tables(stream)
|
|
18
|
+
# do not include materialized views in schema dump - they should be created separately after schema creation
|
|
19
|
+
sorted_tables = (@connection.tables - @connection.materialized_views).sort
|
|
20
|
+
sorted_tables.each do |tbl|
|
|
21
|
+
# add table prefix or suffix for schema_migrations
|
|
22
|
+
next if ignored? tbl
|
|
23
|
+
table(tbl, stream)
|
|
24
|
+
end
|
|
25
|
+
# following table definitions
|
|
26
|
+
# add foreign keys if table has them
|
|
27
|
+
sorted_tables.each do |tbl|
|
|
28
|
+
next if ignored? tbl
|
|
29
|
+
foreign_keys(tbl, stream)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# add synonyms in local schema
|
|
33
|
+
synonyms(stream)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def synonyms(stream)
|
|
37
|
+
syns = @connection.synonyms
|
|
38
|
+
syns.each do |syn|
|
|
39
|
+
next if ignored? syn.name
|
|
40
|
+
table_name = syn.table_name
|
|
41
|
+
table_name = "#{syn.table_owner}.#{table_name}" if syn.table_owner
|
|
42
|
+
stream.print " add_synonym #{syn.name.inspect}, #{table_name.inspect}, force: true"
|
|
43
|
+
stream.puts
|
|
44
|
+
end
|
|
45
|
+
stream.puts unless syns.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def _indexes(table, stream)
|
|
49
|
+
if (indexes = @connection.indexes(table)).any?
|
|
50
|
+
add_index_statements = indexes.filter_map do |index|
|
|
51
|
+
case index.type
|
|
52
|
+
when nil
|
|
53
|
+
# do nothing here. see indexes_in_create
|
|
54
|
+
statement_parts = []
|
|
55
|
+
when "CTXSYS.CONTEXT"
|
|
56
|
+
if index.statement_parameters
|
|
57
|
+
statement_parts = [ ("add_context_index " + remove_prefix_and_suffix(table).inspect) ]
|
|
58
|
+
statement_parts << index.statement_parameters
|
|
59
|
+
else
|
|
60
|
+
statement_parts = [ ("add_context_index " + remove_prefix_and_suffix(table).inspect) ]
|
|
61
|
+
statement_parts << index.columns.inspect
|
|
62
|
+
statement_parts << ("sync: " + $1.inspect) if index.parameters =~ /SYNC\((.*?)\)/
|
|
63
|
+
statement_parts << ("name: " + index.name.inspect)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
# unrecognized index type
|
|
67
|
+
statement_parts = ["# unrecognized index #{index.name.inspect} with type #{index.type.inspect}"]
|
|
68
|
+
end
|
|
69
|
+
" " + statement_parts.join(", ") unless statement_parts.empty?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
return if add_index_statements.empty?
|
|
73
|
+
|
|
74
|
+
stream.puts add_index_statements.sort.join("\n")
|
|
75
|
+
stream.puts
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def indexes_in_create(table, stream)
|
|
80
|
+
if (indexes = @connection.indexes(table)).any?
|
|
81
|
+
index_statements = indexes.map do |index|
|
|
82
|
+
" t.index #{index_parts(index).join(', ')}" unless index.type == "CTXSYS.CONTEXT"
|
|
83
|
+
end
|
|
84
|
+
stream.puts index_statements.compact.sort.join("\n")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def index_parts(index)
|
|
89
|
+
index_parts = super
|
|
90
|
+
index_parts << "tablespace: #{index.tablespace.inspect}" if index.tablespace
|
|
91
|
+
index_parts
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def table(table, stream)
|
|
95
|
+
columns = @connection.columns(table)
|
|
96
|
+
begin
|
|
97
|
+
self.table_name = table
|
|
98
|
+
|
|
99
|
+
tbl = StringIO.new
|
|
100
|
+
|
|
101
|
+
# first dump primary key column
|
|
102
|
+
if @connection.respond_to?(:primary_keys)
|
|
103
|
+
pk = @connection.primary_keys(table)
|
|
104
|
+
pk = pk.first unless pk.size > 1
|
|
105
|
+
else
|
|
106
|
+
pk = @connection.primary_key(table)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
110
|
+
|
|
111
|
+
# addition to make temporary option work
|
|
112
|
+
tbl.print ", temporary: true" if @connection.temporary_table?(table)
|
|
113
|
+
|
|
114
|
+
case pk
|
|
115
|
+
when String
|
|
116
|
+
tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
|
|
117
|
+
pkcol = columns.detect { |c| c.name == pk }
|
|
118
|
+
pkcolspec = column_spec_for_primary_key(pkcol)
|
|
119
|
+
unless pkcolspec.empty?
|
|
120
|
+
if pkcolspec != pkcolspec.slice(:id, :default)
|
|
121
|
+
pkcolspec = { id: { type: pkcolspec.delete(:id), **pkcolspec }.compact }
|
|
122
|
+
end
|
|
123
|
+
tbl.print ", #{format_colspec(pkcolspec)}"
|
|
124
|
+
end
|
|
125
|
+
when Array
|
|
126
|
+
tbl.print ", primary_key: #{pk.inspect}"
|
|
127
|
+
else
|
|
128
|
+
tbl.print ", id: false"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
table_options = @connection.table_options(table)
|
|
132
|
+
if table_options.present?
|
|
133
|
+
tbl.print ", #{format_options(table_options)}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
tbl.puts ", force: :cascade do |t|"
|
|
137
|
+
|
|
138
|
+
# then dump all non-primary key columns
|
|
139
|
+
columns.each do |column|
|
|
140
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
141
|
+
next if column.name == pk
|
|
142
|
+
type, colspec = column_spec(column)
|
|
143
|
+
tbl.print " t.#{type} #{column.name.inspect}"
|
|
144
|
+
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
|
|
145
|
+
tbl.puts
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
indexes_in_create(table, tbl)
|
|
149
|
+
|
|
150
|
+
tbl.puts " end"
|
|
151
|
+
tbl.puts
|
|
152
|
+
|
|
153
|
+
_indexes(table, tbl)
|
|
154
|
+
|
|
155
|
+
tbl.rewind
|
|
156
|
+
stream.print tbl.read
|
|
157
|
+
rescue => e
|
|
158
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
|
159
|
+
stream.puts "# #{e.message}"
|
|
160
|
+
stream.puts
|
|
161
|
+
ensure
|
|
162
|
+
self.table_name = nil
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def prepare_column_options(column)
|
|
167
|
+
spec = super
|
|
168
|
+
|
|
169
|
+
if @connection.supports_virtual_columns? && column.virtual?
|
|
170
|
+
spec[:as] = extract_expression_for_virtual_column(column)
|
|
171
|
+
spec = { type: schema_type(column).inspect }.merge!(spec) unless column.type == :decimal
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
spec
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def default_primary_key?(column)
|
|
178
|
+
schema_type(column) == :integer
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def extract_expression_for_virtual_column(column)
|
|
182
|
+
column_name = column.name
|
|
183
|
+
@connection.select_value(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase), bind_string("column_name", column_name.upcase)]).inspect
|
|
184
|
+
select data_default from all_tab_columns
|
|
185
|
+
where owner = SYS_CONTEXT('userenv', 'current_schema')
|
|
186
|
+
and table_name = :table_name
|
|
187
|
+
and column_name = :column_name
|
|
188
|
+
SQL
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def bind_string(name, value)
|
|
192
|
+
ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|