activerecord-oracle_enhanced-adapter 1.4.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1162 -2
  3. data/README.md +567 -155
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +3 -1
  6. data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +19 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +132 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +345 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +52 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +280 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +64 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +59 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +538 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +46 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +435 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +196 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +164 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +79 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +194 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +709 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +28 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +353 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +33 -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 +385 -1083
  29. data/lib/active_record/type/oracle_enhanced/boolean.rb +20 -0
  30. data/lib/active_record/type/oracle_enhanced/integer.rb +15 -0
  31. data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
  32. data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
  33. data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
  34. data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
  35. data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
  36. data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
  37. data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
  38. data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
  39. data/lib/activerecord-oracle_enhanced-adapter.rb +5 -13
  40. data/spec/active_record/connection_adapters/{oracle_enhanced_emulate_oracle_adapter_spec.rb → emulation/oracle_adapter_spec.rb} +5 -4
  41. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +469 -0
  42. data/spec/active_record/connection_adapters/{oracle_enhanced_context_index_spec.rb → oracle_enhanced/context_index_spec.rb} +140 -128
  43. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +112 -0
  44. data/spec/active_record/connection_adapters/{oracle_enhanced_dbms_output_spec.rb → oracle_enhanced/dbms_output_spec.rb} +13 -13
  45. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +365 -0
  46. data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +196 -0
  47. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
  48. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1433 -0
  49. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +478 -0
  50. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +385 -550
  51. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +92 -1249
  52. data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
  53. data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +208 -0
  54. data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +139 -0
  55. data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
  56. data/spec/active_record/oracle_enhanced/type/integer_spec.rb +91 -0
  57. data/spec/active_record/oracle_enhanced/type/json_spec.rb +57 -0
  58. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
  59. data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
  60. data/spec/active_record/oracle_enhanced/type/raw_spec.rb +122 -0
  61. data/spec/active_record/oracle_enhanced/type/text_spec.rb +229 -0
  62. data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +75 -0
  63. data/spec/spec_config.yaml.template +11 -0
  64. data/spec/spec_helper.rb +100 -93
  65. data/spec/support/alter_system_set_open_cursors.sql +1 -0
  66. data/spec/support/alter_system_user_password.sql +2 -0
  67. data/spec/support/create_oracle_enhanced_users.sql +31 -0
  68. metadata +105 -152
  69. data/.rspec +0 -2
  70. data/Gemfile +0 -52
  71. data/RUNNING_TESTS.md +0 -45
  72. data/Rakefile +0 -59
  73. data/activerecord-oracle_enhanced-adapter.gemspec +0 -130
  74. data/lib/active_record/connection_adapters/oracle_enhanced.rake +0 -105
  75. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +0 -41
  76. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +0 -121
  77. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +0 -151
  78. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +0 -119
  79. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -359
  80. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +0 -25
  81. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +0 -21
  82. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -46
  83. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +0 -572
  84. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +0 -497
  85. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +0 -260
  86. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -227
  87. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +0 -260
  88. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -428
  89. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  90. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +0 -294
  91. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +0 -17
  92. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
  93. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +0 -334
  94. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +0 -19
  95. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +0 -113
  96. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +0 -141
  97. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +0 -378
  98. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +0 -440
  99. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +0 -1400
  100. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +0 -339
@@ -0,0 +1,79 @@
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, :db_link) #: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(name, temporary = false, options = nil, as = nil, tablespace = nil, organization = nil, comment: nil)
57
+ @tablespace = tablespace
58
+ @organization = organization
59
+ super(name, temporary, options, as, comment: comment)
60
+ end
61
+
62
+ def new_column_definition(name, type, **options) # :nodoc:
63
+ if type == :virtual
64
+ raise "No virtual column definition found." unless options[:as]
65
+ type = options[:type]
66
+ end
67
+ super
68
+ end
69
+ end
70
+
71
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
72
+ end
73
+
74
+ class Table < ActiveRecord::ConnectionAdapters::Table
75
+ include OracleEnhanced::ColumnMethods
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,194 @@
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
+ private
8
+
9
+ def tables(stream)
10
+ # do not include materialized views in schema dump - they should be created separately after schema creation
11
+ sorted_tables = (@connection.tables - @connection.materialized_views).sort
12
+ sorted_tables.each do |tbl|
13
+ # add table prefix or suffix for schema_migrations
14
+ next if ignored? tbl
15
+ table(tbl, stream)
16
+ # add primary key trigger if table has it
17
+ primary_key_trigger(tbl, stream)
18
+ end
19
+ # following table definitions
20
+ # add foreign keys if table has them
21
+ sorted_tables.each do |tbl|
22
+ next if ignored? tbl
23
+ foreign_keys(tbl, stream)
24
+ end
25
+
26
+ # add synonyms in local schema
27
+ synonyms(stream)
28
+ end
29
+
30
+ def primary_key_trigger(table_name, stream)
31
+ if @connection.has_primary_key_trigger?(table_name)
32
+ pk, _pk_seq = @connection.pk_and_sequence_for(table_name)
33
+ stream.print " add_primary_key_trigger #{table_name.inspect}"
34
+ stream.print ", primary_key: \"#{pk}\"" if pk != "id"
35
+ stream.print "\n\n"
36
+ end
37
+ end
38
+
39
+ def synonyms(stream)
40
+ syns = @connection.synonyms
41
+ syns.each do |syn|
42
+ next if ignored? syn.name
43
+ table_name = syn.table_name
44
+ table_name = "#{syn.table_owner}.#{table_name}" if syn.table_owner
45
+ table_name = "#{table_name}@#{syn.db_link}" if syn.db_link
46
+ stream.print " add_synonym #{syn.name.inspect}, #{table_name.inspect}, force: true"
47
+ stream.puts
48
+ end
49
+ stream.puts unless syns.empty?
50
+ end
51
+
52
+ def _indexes(table, stream)
53
+ if (indexes = @connection.indexes(table)).any?
54
+ add_index_statements = indexes.map do |index|
55
+ case index.type
56
+ when nil
57
+ # do nothing here. see indexes_in_create
58
+ statement_parts = []
59
+ when "CTXSYS.CONTEXT"
60
+ if index.statement_parameters
61
+ statement_parts = [ ("add_context_index " + remove_prefix_and_suffix(table).inspect) ]
62
+ statement_parts << index.statement_parameters
63
+ else
64
+ statement_parts = [ ("add_context_index " + remove_prefix_and_suffix(table).inspect) ]
65
+ statement_parts << index.columns.inspect
66
+ statement_parts << ("name: " + index.name.inspect)
67
+ end
68
+ else
69
+ # unrecognized index type
70
+ statement_parts = ["# unrecognized index #{index.name.inspect} with type #{index.type.inspect}"]
71
+ end
72
+ " " + statement_parts.join(", ") unless statement_parts.empty?
73
+ end.compact
74
+
75
+ return if add_index_statements.empty?
76
+
77
+ stream.puts add_index_statements.sort.join("\n")
78
+ stream.puts
79
+ end
80
+ end
81
+
82
+ def indexes_in_create(table, stream)
83
+ if (indexes = @connection.indexes(table)).any?
84
+ index_statements = indexes.map do |index|
85
+ " t.index #{index_parts(index).join(', ')}" unless index.type == "CTXSYS.CONTEXT"
86
+ end
87
+ stream.puts index_statements.sort.join("\n")
88
+ end
89
+ end
90
+
91
+ def index_parts(index)
92
+ index_parts = super
93
+ index_parts << "tablespace: #{index.tablespace.inspect}" if index.tablespace
94
+ index_parts
95
+ end
96
+
97
+ def table(table, stream)
98
+ columns = @connection.columns(table)
99
+ begin
100
+ tbl = StringIO.new
101
+
102
+ # first dump primary key column
103
+ if @connection.respond_to?(:primary_keys)
104
+ pk = @connection.primary_keys(table)
105
+ pk = pk.first unless pk.size > 1
106
+ else
107
+ pk = @connection.primary_key(table)
108
+ end
109
+
110
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
111
+
112
+ # addition to make temporary option work
113
+ tbl.print ", temporary: true" if @connection.temporary_table?(table)
114
+
115
+ case pk
116
+ when String
117
+ tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
118
+ pkcol = columns.detect { |c| c.name == pk }
119
+ pkcolspec = column_spec_for_primary_key(pkcol)
120
+ if pkcolspec.present?
121
+ tbl.print ", #{format_colspec(pkcolspec)}"
122
+ end
123
+ when Array
124
+ tbl.print ", primary_key: #{pk.inspect}"
125
+ else
126
+ tbl.print ", id: false"
127
+ end
128
+
129
+ table_options = @connection.table_options(table)
130
+ if table_options.present?
131
+ tbl.print ", #{format_options(table_options)}"
132
+ end
133
+
134
+ tbl.puts ", force: :cascade do |t|"
135
+
136
+ # then dump all non-primary key columns
137
+ columns.each do |column|
138
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
139
+ next if column.name == pk
140
+ type, colspec = column_spec(column)
141
+ tbl.print " t.#{type} #{column.name.inspect}"
142
+ tbl.print ", #{format_colspec(colspec)}" if colspec.present?
143
+ tbl.puts
144
+ end
145
+
146
+ indexes_in_create(table, tbl)
147
+
148
+ tbl.puts " end"
149
+ tbl.puts
150
+
151
+ _indexes(table, tbl)
152
+
153
+ tbl.rewind
154
+ stream.print tbl.read
155
+ rescue => e
156
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
157
+ stream.puts "# #{e.message}"
158
+ stream.puts
159
+ end
160
+ end
161
+
162
+ def prepare_column_options(column)
163
+ spec = super
164
+
165
+ if @connection.supports_virtual_columns? && column.virtual?
166
+ spec[:as] = extract_expression_for_virtual_column(column)
167
+ spec = { type: schema_type(column).inspect }.merge!(spec) unless column.type == :decimal
168
+ end
169
+
170
+ spec
171
+ end
172
+
173
+ def default_primary_key?(column)
174
+ schema_type(column) == :integer
175
+ end
176
+
177
+ def extract_expression_for_virtual_column(column)
178
+ column_name = column.name
179
+ table_name = column.table_name
180
+ @connection.select_value(<<-SQL.strip.gsub(/\s+/, " "), "Table comment", [bind_string("table_name", table_name.upcase), bind_string("column_name", column_name.upcase)]).inspect
181
+ select data_default from all_tab_columns
182
+ where owner = SYS_CONTEXT('userenv', 'session_user')
183
+ and table_name = :table_name
184
+ and column_name = :column_name
185
+ SQL
186
+ end
187
+
188
+ def bind_string(name, value)
189
+ ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,709 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module OracleEnhanced
8
+ module SchemaStatements
9
+ # SCHEMA STATEMENTS ========================================
10
+ #
11
+ # see: abstract/schema_statements.rb
12
+
13
+ def tables #:nodoc:
14
+ select_values(<<-SQL.strip.gsub(/\s+/, " "), "tables")
15
+ SELECT DECODE(table_name, UPPER(table_name), LOWER(table_name), table_name)
16
+ FROM all_tables
17
+ WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
18
+ AND secondary = 'N'
19
+ SQL
20
+ end
21
+
22
+ def data_sources
23
+ super | synonyms.map(&:name)
24
+ end
25
+
26
+ def table_exists?(table_name)
27
+ table_name = table_name.to_s
28
+ if table_name.include?("@")
29
+ # db link is not table
30
+ false
31
+ else
32
+ default_owner = current_schema
33
+ end
34
+ real_name = OracleEnhanced::Quoting.valid_table_name?(table_name) ?
35
+ table_name.upcase : table_name
36
+ if real_name.include?(".")
37
+ table_owner, table_name = real_name.split(".")
38
+ else
39
+ table_owner, table_name = default_owner, real_name
40
+ end
41
+
42
+ select_values(<<-SQL.strip.gsub(/\s+/, " "), "table exists", [bind_string("owner", table_owner), bind_string("table_name", table_name)]).any?
43
+ SELECT owner, table_name
44
+ FROM all_tables
45
+ WHERE owner = :owner
46
+ AND table_name = :table_name
47
+ SQL
48
+ end
49
+
50
+ def data_source_exists?(table_name)
51
+ (_owner, table_name, _db_link) = @connection.describe(table_name)
52
+ true
53
+ rescue
54
+ false
55
+ end
56
+
57
+ def views # :nodoc:
58
+ select_values(<<-SQL.strip.gsub(/\s+/, " "), "views")
59
+ SELECT LOWER(view_name) FROM all_views WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
60
+ SQL
61
+ end
62
+
63
+ def materialized_views #:nodoc:
64
+ select_values(<<-SQL.strip.gsub(/\s+/, " "), "materialized views")
65
+ SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
66
+ SQL
67
+ end
68
+
69
+ # get synonyms for schema dump
70
+ def synonyms
71
+ result = select_all(<<-SQL.strip.gsub(/\s+/, " "), "synonyms")
72
+ SELECT synonym_name, table_owner, table_name, db_link
73
+ FROM all_synonyms where owner = SYS_CONTEXT('userenv', 'session_user')
74
+ SQL
75
+
76
+ result.collect do |row|
77
+ OracleEnhanced::SynonymDefinition.new(oracle_downcase(row["synonym_name"]),
78
+ oracle_downcase(row["table_owner"]), oracle_downcase(row["table_name"]), oracle_downcase(row["db_link"]))
79
+ end
80
+ end
81
+
82
+ def indexes(table_name) #:nodoc:
83
+ (owner, table_name, db_link) = @connection.describe(table_name)
84
+ default_tablespace_name = default_tablespace
85
+
86
+ result = select_all(<<-SQL.strip.gsub(/\s+/, " "), "indexes", [bind_string("owner", owner), bind_string("owner", owner)])
87
+ SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
88
+ i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
89
+ LOWER(i.tablespace_name) AS tablespace_name,
90
+ LOWER(c.column_name) AS column_name, e.column_expression,
91
+ atc.virtual_column
92
+ FROM all_indexes#{db_link} i
93
+ JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
94
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
95
+ e.index_owner = i.owner AND e.column_position = c.column_position
96
+ LEFT OUTER JOIN all_tab_cols#{db_link} atc ON i.table_name = atc.table_name AND
97
+ c.column_name = atc.column_name AND i.owner = atc.owner AND atc.hidden_column = 'NO'
98
+ WHERE i.owner = :owner
99
+ AND i.table_owner = :owner
100
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
101
+ WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
102
+ ORDER BY i.index_name, c.column_position
103
+ SQL
104
+
105
+ current_index = nil
106
+ all_schema_indexes = []
107
+
108
+ result.each do |row|
109
+ # have to keep track of indexes because above query returns dups
110
+ # there is probably a better query we could figure out
111
+ if current_index != row["index_name"]
112
+ statement_parameters = nil
113
+ if row["index_type"] == "DOMAIN" && row["ityp_owner"] == "CTXSYS" && row["ityp_name"] == "CONTEXT"
114
+ procedure_name = default_datastore_procedure(row["index_name"])
115
+ source = select_values(<<-SQL.strip.gsub(/\s+/, " "), "procedure", [bind_string("owner", owner), bind_string("procedure_name", procedure_name.upcase)]).join
116
+ SELECT text
117
+ FROM all_source#{db_link}
118
+ WHERE owner = :owner
119
+ AND name = :procedure_name
120
+ ORDER BY line
121
+ SQL
122
+ if source =~ /-- add_context_index_parameters (.+)\n/
123
+ statement_parameters = $1
124
+ end
125
+ end
126
+ all_schema_indexes << OracleEnhanced::IndexDefinition.new(
127
+ row["table_name"],
128
+ row["index_name"],
129
+ row["uniqueness"] == "UNIQUE",
130
+ [],
131
+ {},
132
+ row["index_type"] == "DOMAIN" ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
133
+ row["parameters"],
134
+ statement_parameters,
135
+ row["tablespace_name"] == default_tablespace_name ? nil : row["tablespace_name"])
136
+ current_index = row["index_name"]
137
+ end
138
+
139
+ # Functional index columns and virtual columns both get stored as column expressions,
140
+ # but re-creating a virtual column index as an expression (instead of using the virtual column's name)
141
+ # results in a ORA-54018 error. Thus, we only want the column expression value returned
142
+ # when the column is not virtual.
143
+ if row["column_expression"] && row["virtual_column"] != "YES"
144
+ all_schema_indexes.last.columns << row["column_expression"]
145
+ else
146
+ all_schema_indexes.last.columns << row["column_name"].downcase
147
+ end
148
+ end
149
+
150
+ # Return the indexes just for the requested table, since AR is structured that way
151
+ table_name = table_name.downcase
152
+ all_schema_indexes.select { |i| i.table == table_name }
153
+ end
154
+
155
+ # Additional options for +create_table+ method in migration files.
156
+ #
157
+ # You can specify individual starting value in table creation migration file, e.g.:
158
+ #
159
+ # create_table :users, :sequence_start_value => 100 do |t|
160
+ # # ...
161
+ # end
162
+ #
163
+ # You can also specify other sequence definition additional parameters, e.g.:
164
+ #
165
+ # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
166
+ # # ...
167
+ # end
168
+ #
169
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
170
+ # By default trigger name will be "table_name_pkt", you can override the name with
171
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
172
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
173
+ # Example:
174
+ #
175
+ # create_table :users, :primary_key_trigger => true do |t|
176
+ # # ...
177
+ # end
178
+ #
179
+ # It is possible to add table and column comments in table creation migration files:
180
+ #
181
+ # create_table :employees, :comment => “Employees and contractors” do |t|
182
+ # t.string :first_name, :comment => “Given name”
183
+ # t.string :last_name, :comment => “Surname”
184
+ # end
185
+
186
+ def create_table(table_name, comment: nil, **options)
187
+ create_sequence = options[:id] != false
188
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], options[:tablespace], options[:organization], comment: comment
189
+
190
+ if options[:id] != false && !options[:as]
191
+ pk = options.fetch(:primary_key) do
192
+ Base.get_primary_key table_name.to_s.singularize
193
+ end
194
+
195
+ if pk.is_a?(Array)
196
+ td.primary_keys pk
197
+ else
198
+ td.primary_key pk, options.fetch(:id, :primary_key), options
199
+ end
200
+ end
201
+
202
+ # store that primary key was defined in create_table block
203
+ unless create_sequence
204
+ class << td
205
+ attr_accessor :create_sequence
206
+ def primary_key(*args)
207
+ self.create_sequence = true
208
+ super(*args)
209
+ end
210
+ end
211
+ end
212
+
213
+ yield td if block_given?
214
+ create_sequence = create_sequence || td.create_sequence
215
+
216
+ if options[:force] && data_source_exists?(table_name)
217
+ drop_table(table_name, options)
218
+ end
219
+
220
+ execute schema_creation.accept td
221
+
222
+ create_sequence_and_trigger(table_name, options) if create_sequence
223
+
224
+ if supports_comments? && !supports_comments_in_create?
225
+ change_table_comment(table_name, comment) if comment
226
+ td.columns.each do |column|
227
+ change_column_comment(table_name, column.name, column.comment) if column.comment.present?
228
+ end
229
+ end
230
+ td.indexes.each { |c, o| add_index table_name, c, o }
231
+
232
+ rebuild_primary_key_index_to_default_tablespace(table_name, options)
233
+ end
234
+
235
+ def rename_table(table_name, new_name) #:nodoc:
236
+ if new_name.to_s.length > table_name_length
237
+ raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters"
238
+ end
239
+ execute "RENAME #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
240
+ execute "RENAME #{quote_table_name("#{table_name}_seq")} TO #{default_sequence_name(new_name)}" rescue nil
241
+
242
+ rename_table_indexes(table_name, new_name)
243
+ end
244
+
245
+ def drop_table(table_name, options = {}) #:nodoc:
246
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE CONSTRAINTS' if options[:force] == :cascade}"
247
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
248
+ execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
249
+ rescue ActiveRecord::StatementInvalid => e
250
+ raise e unless options[:if_exists]
251
+ end
252
+
253
+ def insert_versions_sql(versions) # :nodoc:
254
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
255
+
256
+ if supports_multi_insert?
257
+ versions.inject("INSERT ALL\n".dup) { |sql, version|
258
+ sql << "INTO #{sm_table} (version) VALUES (#{quote(version)})\n"
259
+ } << "SELECT * FROM DUAL\n"
260
+ else
261
+ if versions.is_a?(Array)
262
+ # called from ActiveRecord::Base.connection#dump_schema_information
263
+ versions.map { |version|
264
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
265
+ }.join("\n\n/\n\n")
266
+ else
267
+ # called from ActiveRecord::Base.connection#assume_migrated_upto_version
268
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)})"
269
+ end
270
+ end
271
+ end
272
+
273
+ def add_index(table_name, column_name, options = {}) #:nodoc:
274
+ index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options)
275
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}"
276
+ if index_type == "UNIQUE"
277
+ unless quoted_column_names =~ /\(.*\)/
278
+ execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})"
279
+ end
280
+ end
281
+ end
282
+
283
+ def add_index_options(table_name, column_name, comment: nil, **options) #:nodoc:
284
+ column_names = Array(column_name)
285
+ index_name = index_name(table_name, column: column_names)
286
+
287
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using)
288
+
289
+ index_type = options[:unique] ? "UNIQUE" : ""
290
+ index_name = options[:name].to_s if options.key?(:name)
291
+ tablespace = tablespace_for(:index, options[:tablespace])
292
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
293
+ # TODO: This option is used for NOLOGGING, needs better argumetn name
294
+ index_options = options[:options]
295
+
296
+ if index_name.to_s.length > max_index_length
297
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
298
+ end
299
+ if table_exists?(table_name) && index_name_exists?(table_name, index_name)
300
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
301
+ end
302
+
303
+ quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
304
+ [index_name, index_type, quoted_column_names, tablespace, index_options]
305
+ end
306
+
307
+ # Remove the given index from the table.
308
+ # Gives warning if index does not exist
309
+ def remove_index(table_name, options = {}) #:nodoc:
310
+ index_name = index_name_for_remove(table_name, options)
311
+ # TODO: It should execute only when index_type == "UNIQUE"
312
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil
313
+ execute "DROP INDEX #{quote_column_name(index_name)}"
314
+ end
315
+
316
+ # returned shortened index name if default is too large
317
+ def index_name(table_name, options) #:nodoc:
318
+ default_name = super(table_name, options).to_s
319
+ # sometimes options can be String or Array with column names
320
+ options = {} unless options.is_a?(Hash)
321
+ identifier_max_length = options[:identifier_max_length] || index_name_length
322
+ return default_name if default_name.length <= identifier_max_length
323
+
324
+ # remove 'index', 'on' and 'and' keywords
325
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
326
+
327
+ # leave just first three letters from each word
328
+ if shortened_name.length > identifier_max_length
329
+ shortened_name = shortened_name.split("_").map { |w| w[0, 3] }.join("_")
330
+ end
331
+ # generate unique name using hash function
332
+ if shortened_name.length > identifier_max_length
333
+ shortened_name = "i" + Digest::SHA1.hexdigest(default_name)[0, identifier_max_length - 1]
334
+ end
335
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
336
+ shortened_name
337
+ end
338
+
339
+ # Verify the existence of an index with a given name.
340
+ #
341
+ # The default argument is returned if the underlying implementation does not define the indexes method,
342
+ # as there's no way to determine the correct answer in that case.
343
+ #
344
+ # Will always query database and not index cache.
345
+ def index_name_exists?(table_name, index_name)
346
+ (owner, table_name, db_link) = @connection.describe(table_name)
347
+ result = select_value(<<-SQL.strip.gsub(/\s+/, " "), "index name exists")
348
+ SELECT 1 FROM all_indexes#{db_link} i
349
+ WHERE i.owner = '#{owner}'
350
+ AND i.table_owner = '#{owner}'
351
+ AND i.table_name = '#{table_name}'
352
+ AND i.index_name = '#{index_name.to_s.upcase}'
353
+ SQL
354
+ result == 1
355
+ end
356
+
357
+ def rename_index(table_name, old_name, new_name) #:nodoc:
358
+ validate_index_length!(table_name, new_name)
359
+ execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}"
360
+ end
361
+
362
+ # Add synonym to existing table or view or sequence. Can be used to create local synonym to
363
+ # remote table in other schema or in other database
364
+ # Examples:
365
+ #
366
+ # add_synonym :posts, "blog.posts"
367
+ # add_synonym :posts_seq, "blog.posts_seq"
368
+ # add_synonym :employees, "hr.employees@dblink", :force => true
369
+ #
370
+ def add_synonym(name, table_name, options = {})
371
+ sql = "CREATE".dup
372
+ if options[:force] == true
373
+ sql << " OR REPLACE"
374
+ end
375
+ sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
376
+ execute sql
377
+ end
378
+
379
+ # Remove existing synonym to table or view or sequence
380
+ # Example:
381
+ #
382
+ # remove_synonym :posts, "blog.posts"
383
+ #
384
+ def remove_synonym(name)
385
+ execute "DROP SYNONYM #{quote_table_name(name)}"
386
+ end
387
+
388
+ def add_reference(table_name, *args)
389
+ OracleEnhanced::ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
390
+ end
391
+
392
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
393
+ type = aliased_types(type.to_s, type)
394
+ at = create_alter_table table_name
395
+ at.add_column(column_name, type, options)
396
+ add_column_sql = schema_creation.accept at
397
+ add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name)
398
+ execute add_column_sql
399
+ create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key
400
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
401
+ end
402
+
403
+ def aliased_types(name, fallback)
404
+ fallback
405
+ end
406
+
407
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
408
+ default = extract_new_default_value(default_or_changes)
409
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
410
+ end
411
+
412
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
413
+ column = column_for(table_name, column_name)
414
+
415
+ unless null || default.nil?
416
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
417
+ end
418
+
419
+ change_column table_name, column_name, column.sql_type, null: null
420
+ end
421
+
422
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
423
+ column = column_for(table_name, column_name)
424
+
425
+ # remove :null option if its value is the same as current column definition
426
+ # otherwise Oracle will raise error
427
+ if options.has_key?(:null) && options[:null] == column.null
428
+ options[:null] = nil
429
+ end
430
+ if type.to_sym == :virtual
431
+ type = options[:type]
432
+ end
433
+
434
+ td = create_table_definition(table_name)
435
+ cd = td.new_column_definition(column.name, type, options)
436
+ change_column_stmt = schema_creation.accept cd
437
+ change_column_stmt << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
438
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{change_column_stmt}"
439
+ execute(change_column_sql)
440
+
441
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
442
+ end
443
+
444
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
445
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
446
+ rename_column_indexes(table_name, column_name, new_column_name)
447
+ end
448
+
449
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
450
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)} CASCADE CONSTRAINTS"
451
+ end
452
+
453
+ def change_table_comment(table_name, comment)
454
+ clear_cache!
455
+ if comment.nil?
456
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS ''"
457
+ else
458
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
459
+ end
460
+ end
461
+
462
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
463
+ clear_cache!
464
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS '#{comment}'"
465
+ end
466
+
467
+ def table_comment(table_name) #:nodoc:
468
+ (owner, table_name, db_link) = @connection.describe(table_name)
469
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "Table comment", [bind_string("owner", owner), bind_string("table_name", table_name)])
470
+ SELECT comments FROM all_tab_comments#{db_link}
471
+ WHERE owner = :owner
472
+ AND table_name = :table_name
473
+ SQL
474
+ end
475
+
476
+ def table_options(table_name) # :nodoc:
477
+ if comment = table_comment(table_name)
478
+ { comment: comment }
479
+ end
480
+ end
481
+
482
+ def column_comment(table_name, column_name) #:nodoc:
483
+ # TODO: it does not exist in Abstract adapter
484
+ (owner, table_name, db_link) = @connection.describe(table_name)
485
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "Column comment", [bind_string("owner", owner), bind_string("table_name", table_name), bind_string("column_name", column_name.upcase)])
486
+ SELECT comments FROM all_col_comments#{db_link}
487
+ WHERE owner = :owner
488
+ AND table_name = :table_name
489
+ AND column_name = :column_name
490
+ SQL
491
+ end
492
+
493
+ # Maps logical Rails types to Oracle-specific data types.
494
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) #:nodoc:
495
+ # Ignore options for :text, :ntext and :binary columns
496
+ return super(type) if ["text", "ntext", "binary"].include?(type.to_s)
497
+
498
+ super
499
+ end
500
+
501
+ def tablespace(table_name)
502
+ select_value(<<-SQL.strip.gsub(/\s+/, " "), "tablespace")
503
+ SELECT tablespace_name
504
+ FROM all_tables
505
+ WHERE table_name='#{table_name.to_s.upcase}'
506
+ AND owner = SYS_CONTEXT('userenv', 'session_user')
507
+ SQL
508
+ end
509
+
510
+ # get table foreign keys for schema dump
511
+ def foreign_keys(table_name) #:nodoc:
512
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
513
+
514
+ fk_info = select_all(<<-SQL.strip.gsub(/\s+/, " "), "Foreign Keys", [bind_string("owner", owner), bind_string("desc_table_name", desc_table_name)])
515
+ SELECT r.table_name to_table
516
+ ,rc.column_name references_column
517
+ ,cc.column_name
518
+ ,c.constraint_name name
519
+ ,c.delete_rule
520
+ FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc,
521
+ all_constraints#{db_link} r, all_cons_columns#{db_link} rc
522
+ WHERE c.owner = :owner
523
+ AND c.table_name = :desc_table_name
524
+ AND c.constraint_type = 'R'
525
+ AND cc.owner = c.owner
526
+ AND cc.constraint_name = c.constraint_name
527
+ AND r.constraint_name = c.r_constraint_name
528
+ AND r.owner = c.owner
529
+ AND rc.owner = r.owner
530
+ AND rc.constraint_name = r.constraint_name
531
+ AND rc.position = cc.position
532
+ ORDER BY name, to_table, column_name, references_column
533
+ SQL
534
+
535
+ fk_info.map do |row|
536
+ options = {
537
+ column: oracle_downcase(row["column_name"]),
538
+ name: oracle_downcase(row["name"]),
539
+ primary_key: oracle_downcase(row["references_column"])
540
+ }
541
+ options[:on_delete] = extract_foreign_key_action(row["delete_rule"])
542
+ ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(oracle_downcase(table_name), oracle_downcase(row["to_table"]), options)
543
+ end
544
+ end
545
+
546
+ def extract_foreign_key_action(specifier) # :nodoc:
547
+ case specifier
548
+ when "CASCADE"; :cascade
549
+ when "SET NULL"; :nullify
550
+ end
551
+ end
552
+
553
+ # REFERENTIAL INTEGRITY ====================================
554
+
555
+ def disable_referential_integrity(&block) #:nodoc:
556
+ old_constraints = select_all(<<-SQL.strip.gsub(/\s+/, " "), "Foreign Keys to disable and enable")
557
+ SELECT constraint_name, owner, table_name
558
+ FROM all_constraints
559
+ WHERE constraint_type = 'R'
560
+ AND status = 'ENABLED'
561
+ AND owner = SYS_CONTEXT('userenv', 'session_user')
562
+ SQL
563
+ begin
564
+ old_constraints.each do |constraint|
565
+ execute "ALTER TABLE #{quote_table_name(constraint["table_name"])} DISABLE CONSTRAINT #{quote_table_name(constraint["constraint_name"])}"
566
+ end
567
+ yield
568
+ ensure
569
+ old_constraints.each do |constraint|
570
+ execute "ALTER TABLE #{quote_table_name(constraint["table_name"])} ENABLE CONSTRAINT #{quote_table_name(constraint["constraint_name"])}"
571
+ end
572
+ end
573
+ end
574
+
575
+ def create_alter_table(name)
576
+ OracleEnhanced::AlterTable.new create_table_definition(name, false, {})
577
+ end
578
+
579
+ def update_table_definition(table_name, base)
580
+ OracleEnhanced::Table.new(table_name, base)
581
+ end
582
+
583
+ def create_schema_dumper(options)
584
+ OracleEnhanced::SchemaDumper.create(self, options)
585
+ end
586
+
587
+ private
588
+
589
+ def schema_creation
590
+ OracleEnhanced::SchemaCreation.new self
591
+ end
592
+
593
+ def create_table_definition(*args)
594
+ OracleEnhanced::TableDefinition.new(*args)
595
+ end
596
+
597
+ def new_column_from_field(table_name, field)
598
+ limit, scale = field["limit"], field["scale"]
599
+ if limit || scale
600
+ field["sql_type"] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
601
+ end
602
+
603
+ if field["sql_type_owner"]
604
+ field["sql_type"] = field["sql_type_owner"] + "." + field["sql_type"]
605
+ end
606
+
607
+ is_virtual = field["virtual_column"] == "YES"
608
+
609
+ # clean up odd default spacing from Oracle
610
+ if field["data_default"] && !is_virtual
611
+ field["data_default"].sub!(/^(.*?)\s*$/, '\1')
612
+
613
+ # If a default contains a newline these cleanup regexes need to
614
+ # match newlines.
615
+ field["data_default"].sub!(/^'(.*)'$/m, '\1')
616
+ field["data_default"] = nil if field["data_default"] =~ /^(null|empty_[bc]lob\(\))$/i
617
+ # TODO: Needs better fix to fallback "N" to false
618
+ field["data_default"] = false if (field["data_default"] == "N" && OracleEnhancedAdapter.emulate_booleans_from_strings)
619
+ end
620
+
621
+ type_metadata = fetch_type_metadata(field["sql_type"], is_virtual)
622
+ default_value = extract_value_from_default(field["data_default"])
623
+ default_value = nil if is_virtual
624
+ OracleEnhanced::Column.new(oracle_downcase(field["name"]),
625
+ default_value,
626
+ type_metadata,
627
+ field["nullable"] == "Y",
628
+ table_name,
629
+ field["column_comment"]
630
+ )
631
+ end
632
+
633
+ def fetch_type_metadata(sql_type, virtual = nil)
634
+ OracleEnhanced::TypeMetadata.new(super(sql_type), virtual: virtual)
635
+ end
636
+
637
+ def tablespace_for(obj_type, tablespace_option, table_name = nil, column_name = nil)
638
+ tablespace_sql = "".dup
639
+ if tablespace = (tablespace_option || default_tablespace_for(obj_type))
640
+ if [:blob, :clob, :nclob].include?(obj_type.to_sym)
641
+ tablespace_sql << " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
642
+ else
643
+ tablespace_sql << " TABLESPACE #{tablespace}"
644
+ end
645
+ end
646
+ tablespace_sql
647
+ end
648
+
649
+ def default_tablespace_for(type)
650
+ (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
651
+ end
652
+
653
+ def column_for(table_name, column_name)
654
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
655
+ raise "No such column: #{table_name}.#{column_name}"
656
+ end
657
+ column
658
+ end
659
+
660
+ def create_sequence_and_trigger(table_name, options)
661
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
662
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
663
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
664
+
665
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
666
+ end
667
+
668
+ def create_primary_key_trigger(table_name, options)
669
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
670
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
671
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
672
+ execute <<-SQL
673
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
674
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
675
+ BEGIN
676
+ IF inserting THEN
677
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
678
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
679
+ END IF;
680
+ END IF;
681
+ END;
682
+ SQL
683
+ end
684
+
685
+ def default_trigger_name(table_name)
686
+ # truncate table name if necessary to fit in max length of identifier
687
+ "#{table_name.to_s[0, table_name_length - 4]}_pkt"
688
+ end
689
+
690
+ def rebuild_primary_key_index_to_default_tablespace(table_name, options)
691
+ tablespace = default_tablespace_for(:index)
692
+
693
+ return unless tablespace
694
+
695
+ index_name = select_value(<<-SQL.strip.gsub(/\s+/, " "), "Index name for primary key", [bind_string("table_name", table_name.upcase)])
696
+ SELECT index_name FROM all_constraints
697
+ WHERE table_name = :table_name
698
+ AND constraint_type = 'P'
699
+ AND owner = SYS_CONTEXT('userenv', 'current_schema')
700
+ SQL
701
+
702
+ return unless index_name
703
+
704
+ execute("ALTER INDEX #{quote_column_name(index_name)} REBUILD TABLESPACE #{tablespace}")
705
+ end
706
+ end
707
+ end
708
+ end
709
+ end