Skip to content

CLOB/BLOB Inserts Fail When prepared_statements: false (which happens when config.active_record.query_log_tags_enabled = true) #2477

@andynu

Description

@andynu

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

  1. Enable query log tags in development (Rails 8 default):
# config/environments/development.rb
config.active_record.query_log_tags_enabled = true
  1. This causes Rails to set ActiveRecord.disable_prepared_statements = true (see activerecord-8.0.3/lib/active_record/railtie.rb:377)

  2. 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:

  1. Disable query log tags in development:
config.active_record.query_log_tags_enabled = false
  1. 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() + subsequent write_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions