Skip to content

Add option for primary keys generated as identity #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: release61
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,56 +200,64 @@ def columns(table_name)
# end

def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
create_sequence = id != false
td = create_table_definition(
table_name, **options.extract!(:temporary, :options, :as, :comment, :tablespace, :organization)
)
OracleEnhancedAdapter.using_identity(options[:primary_key_as_identity]) do
create_sequence = id != false && OracleEnhancedAdapter.use_identity_for_pk != true
td = create_table_definition(
table_name, **options.extract!(:temporary, :options, :as, :comment, :tablespace, :organization)
)

if id && !td.as
pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
if id && !td.as
pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)

if pk.is_a?(Array)
td.primary_keys pk
else
td.primary_key pk, id, **options
if pk.is_a?(Array)
td.primary_keys pk
else
td.primary_key pk, id, **options
end
end
end

# store that primary key was defined in create_table block
unless create_sequence
class << td
attr_accessor :create_sequence
def primary_key(*args)
self.create_sequence = true
super(*args)
# store that primary key was defined in create_table block
unless create_sequence
class << td
attr_accessor :create_sequence
def primary_key(*args)
if args.last.is_a?(Hash)
options = args.last

OracleEnhancedAdapter.use_identity_for_pk = options[:identity] unless options[:identity].nil?
end

self.create_sequence = !OracleEnhancedAdapter.use_identity_for_pk
super(*args)
end
end
end
end

yield td if block_given?
create_sequence = create_sequence || td.create_sequence
yield td if block_given?
create_sequence = create_sequence || td.create_sequence

if force && data_source_exists?(table_name)
drop_table(table_name, force: force, if_exists: true)
else
schema_cache.clear_data_source_cache!(table_name.to_s)
end
if force && data_source_exists?(table_name)
drop_table(table_name, force: force, if_exists: true)
else
schema_cache.clear_data_source_cache!(table_name.to_s)
end

execute schema_creation.accept td
execute schema_creation.accept td

create_sequence_and_trigger(table_name, options) if create_sequence
create_sequence_and_trigger(table_name, options) if create_sequence

if supports_comments? && !supports_comments_in_create?
if table_comment = td.comment.presence
change_table_comment(table_name, table_comment)
end
td.columns.each do |column|
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
if supports_comments? && !supports_comments_in_create?
if table_comment = td.comment.presence
change_table_comment(table_name, table_comment)
end
td.columns.each do |column|
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
end
end
end
td.indexes.each { |c, o| add_index table_name, c, **o }
td.indexes.each { |c, o| add_index table_name, c, **o }

rebuild_primary_key_index_to_default_tablespace(table_name, options)
rebuild_primary_key_index_to_default_tablespace(table_name, options)
end
end

def rename_table(table_name, new_name) #:nodoc:
Expand Down Expand Up @@ -413,14 +421,16 @@ def add_reference(table_name, ref_name, **options)
end

def add_column(table_name, column_name, type, **options) #:nodoc:
type = aliased_types(type.to_s, type)
at = create_alter_table table_name
at.add_column(column_name, type, **options)
add_column_sql = schema_creation.accept at
add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name)
execute add_column_sql
create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
OracleEnhancedAdapter.using_identity(options[:identity]) do
type = aliased_types(type.to_s, type)
at = create_alter_table table_name
at.add_column(column_name, type, **options)
add_column_sql = schema_creation.accept at
add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name)
execute add_column_sql
create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key && !OracleEnhancedAdapter.use_identity_for_pk
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
ensure
clear_table_columns_cache(table_name)
end
Expand Down
34 changes: 29 additions & 5 deletions lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ class OracleEnhancedAdapter < AbstractAdapter
cattr_accessor :emulate_booleans_from_strings
self.emulate_booleans_from_strings = false

##
# :singleton-method:
# Controls whether or not primary keys include the GENERATED AS IDENTITY option.
# To enable, you can add the following line to your initializer file:
#
# ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.use_identity_for_pk = true
cattr_accessor :use_identity_for_pk
self.use_identity_for_pk = false

##
# :singleton-method:
# By default, OracleEnhanced adapter will use Oracle12 visitor
Expand Down Expand Up @@ -405,13 +414,19 @@ def supports_longer_identifier?
bigint: { name: "NUMBER", limit: 19 }
}
# if emulate_booleans_from_strings then store booleans in VARCHAR2
NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = {
boolean: { name: "VARCHAR2", limit: 1 }
)
}
NATIVE_DATABASE_TYPES_IDENTITY_PK = {
primary_key: "NUMBER(38) GENERATED BY DEFAULT ON NULL AS IDENTITY NOT NULL PRIMARY KEY"
}
#:startdoc:

def native_database_types #:nodoc:
emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
NATIVE_DATABASE_TYPES.dup.tap do |types|
types.merge!(NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS) if emulate_booleans_from_strings
types.merge!(NATIVE_DATABASE_TYPES_IDENTITY_PK) if use_identity_for_pk
end
end

# CONNECTION MANAGEMENT ====================================
Expand Down Expand Up @@ -475,8 +490,8 @@ def discard!
# called directly; used by ActiveRecord to get the next primary key value
# when inserting a new database record (see #prefetch_primary_key?).
def next_sequence_value(sequence_name)
# if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
raise ArgumentError.new "Trigger based primary key is not supported" if sequence_name == AUTOGENERATED_SEQUENCE_NAME
# if sequence_name is set to :autogenerated it means that primary key will be populated by an identity sequence
return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
# call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
select_value(<<~SQL.squish, "SCHEMA")
SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual
Expand Down Expand Up @@ -689,6 +704,15 @@ def check_version
end
end

# Helper method for temporarily changing the value of OracleEnhancedAdapter.use_identity_for_pk (e.g., for a
# single create_table block)
def self.using_identity(value = nil, &block)
previous_value = self.use_identity_for_pk
self.use_identity_for_pk = value.nil? ? self.use_identity_for_pk : value
yield
self.use_identity_for_pk = previous_value
end

private
def initialize_type_map(m = type_map)
super
Expand Down