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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1977 -0
  3. data/License.txt +20 -0
  4. data/README.md +863 -0
  5. data/VERSION +1 -0
  6. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +7 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +24 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +137 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +344 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +47 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +295 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +63 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +71 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +561 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +45 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +457 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +195 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +186 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +94 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +99 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +197 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +738 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +366 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +34 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +865 -0
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +19 -0
  30. data/lib/active_record/type/oracle_enhanced/character_string.rb +36 -0
  31. data/lib/active_record/type/oracle_enhanced/integer.rb +14 -0
  32. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  34. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  35. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  36. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  37. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  38. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  39. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  40. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  41. data/lib/arel/visitors/oracle.rb +216 -0
  42. data/lib/arel/visitors/oracle12.rb +121 -0
  43. data/lib/arel/visitors/oracle_common.rb +51 -0
  44. data/spec/active_record/connection_adapters/emulation/oracle_adapter_spec.rb +24 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +36 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +574 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/context_index_spec.rb +431 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +122 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/dbms_output_spec.rb +69 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +362 -0
  51. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +181 -0
  52. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  53. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1289 -0
  54. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +474 -0
  55. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +815 -0
  56. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +230 -0
  57. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  58. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +206 -0
  59. data/spec/active_record/oracle_enhanced/type/character_string_spec.rb +67 -0
  60. data/spec/active_record/oracle_enhanced/type/custom_spec.rb +90 -0
  61. data/spec/active_record/oracle_enhanced/type/decimal_spec.rb +56 -0
  62. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +141 -0
  63. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  64. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +99 -0
  65. data/spec/active_record/oracle_enhanced/type/json_spec.rb +56 -0
  66. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +56 -0
  67. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  68. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +137 -0
  69. data/spec/active_record/oracle_enhanced/type/text_spec.rb +244 -0
  70. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +77 -0
  71. data/spec/spec_config.yaml.template +11 -0
  72. data/spec/spec_helper.rb +214 -0
  73. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  74. data/spec/support/alter_system_user_password.sql +2 -0
  75. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  76. 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