-
Notifications
You must be signed in to change notification settings - Fork 328
Description
CLOB/BLOB Inserts Fail When prepared_statements: false
Environment
- Rails: 8.0.3
- activerecord-oracle_enhanced-adapter: 8.0.0
- ruby-oci8: 2.2.12
- Oracle Database: 19.3.0.0.0
- Ruby: 3.4.5
Issue Description
CLOB and BLOB columns are not properly inserted when prepared_statements: false
. This occurs in Rails development environments where config.active_record.query_log_tags_enabled = true
causes Rails to automatically disable prepared statements.
Reproduction Steps
- Enable query log tags in development (Rails 8 default):
# config/environments/development.rb
config.active_record.query_log_tags_enabled = true
-
This causes Rails to set
ActiveRecord.disable_prepared_statements = true
(seeactiverecord-8.0.3/lib/active_record/railtie.rb:377
) -
Create a record with a CLOB column:
Model.create!(clob_field: '<p>Test content</p>' * 100)
record = Model.last
record.clob_field # => "" (empty!)
Root Cause
When prepared_statements: false
, the adapter generates inline SQL:
INSERT INTO "TABLE" (..., "CLOB_COLUMN", ...)
VALUES ('val1', 'val2', empty_clob(), ...)
The CLOB is inserted as empty_clob()
placeholder, but the content is never written because the Lob
module only has after_update
callbacks, not after_create
:
# lib/active_record/connection_adapters/oracle_enhanced/lob.rb:14-15
included do
before_update :record_changed_lobs
after_update :enhanced_write_lobs
# Missing: after_create :enhanced_write_lobs_on_create
end
When prepared_statements: true
, the adapter correctly uses bind parameters with OCI8::CLOB
objects:
INSERT INTO "TABLE" (...) VALUES (:a1, :a2, :a3, ..., :a17, ...)
[["id", "ABC123"], ..., ["clob_column", #<OCI8::CLOB:0x000070dad5e26578>], ...]
Code Flow
quoting.rb:121-122 (when NOT using bind parameters):
when Type::OracleEnhanced::Text::Data then
"empty_clob()"
oci_quoting.rb:15-20 (when using bind parameters):
when Type::OracleEnhanced::Text::Data
lob_value = value.to_s == "" ? " " : value.to_s
bind_type = OCI8::CLOB
ora_value = bind_type.new(_connection.raw_oci_connection, lob_value)
ora_value.size = 0 if value.to_s == ""
ora_value
Proposed Fix
Add after_create
callback to the Lob
module to handle the two-step CLOB write when prepared_statements: false
:
# lib/active_record/connection_adapters/oracle_enhanced/lob.rb
included do
before_update :record_changed_lobs
after_update :enhanced_write_lobs
after_create :enhanced_write_lobs_on_create # ADD THIS
end
private
# New method to write LOBs after INSERT when prepared_statements = false
def enhanced_write_lobs_on_create
return unless self.class.respond_to?(:lob_columns)
return unless self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter)
return if self.class.custom_create_method
# Only needed when prepared_statements = false (inline empty_clob() was used)
return if self.class.connection.prepared_statements
# Ensure primary key is set for WHERE clause
return unless self.class.primary_key && attributes[self.class.primary_key]
lob_columns_with_values = self.class.lob_columns.select do |col|
value = attributes[col.name]
value.present? && !self.class.readonly_attributes.to_a.include?(col.name)
end
if lob_columns_with_values.any?
self.class.connection.write_lobs(
self.class.table_name,
self.class,
attributes,
lob_columns_with_values
)
end
end
Current Workaround
Users must either:
- Disable query log tags in development:
config.active_record.query_log_tags_enabled = false
- Or manually force prepared statements for oracle_enhanced connections (bypasses Rails' automatic disabling)
Impact
This affects any Rails 8 application using oracle_enhanced adapter with:
- Development environment (where query_log_tags_enabled defaults to true)
- Any environment where prepared_statements is explicitly disabled
- Tables containing CLOB or BLOB columns
Expected Behavior
CLOB/BLOB inserts should work regardless of the prepared_statements
setting. The adapter should handle both approaches:
- With prepared statements: Use
OCI8::CLOB
bind parameters (current behavior - works) - Without prepared statements: Use
empty_clob()
+ subsequentwrite_lobs
call (currently broken - needs fix)
Willingness to Submit PR
I'm willing to submit a PR with the proposed fix including:
- Implementation of
after_create
callback for LOB writes - Tests covering INSERT with
prepared_statements: false
- Documentation updates explaining the behavior
Please let me know if this approach is acceptable or if you'd prefer a different solution.