@@ -122,6 +122,18 @@ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, retu
122122 sql , binds = sql_for_insert ( sql , pk , binds , returning )
123123 type_casted_binds = type_casted_binds ( binds )
124124
125+ # Check for empty column names in the SQL (diagnostic for ORA-01741)
126+ if sql =~ /INSERT INTO.*?\( ([^)]+)\) /i
127+ columns = $1. split ( "," ) . map ( &:strip )
128+ empty_columns = columns . select { |c | c . empty? || c == '""' || c == "''" }
129+ if empty_columns . any? || columns . any? { |c | c =~ /^\s *$/ }
130+ Rails . logger . error "ORA-01741 DEBUG: Empty column detected in INSERT!"
131+ Rails . logger . error " SQL: #{ sql [ 0 ..500 ] } "
132+ Rails . logger . error " Columns parsed: #{ columns . inspect } "
133+ Rails . logger . error " pk: #{ pk . inspect } , returning: #{ returning . inspect } "
134+ end
135+ end
136+
125137 log ( sql , name , binds , type_casted_binds ) do
126138 cached = false
127139 cursor = nil
@@ -155,6 +167,13 @@ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, retu
155167 returning_id = cursor . get_returning_param ( returning_id_index , Integer ) . to_i
156168 rows << [ returning_id ]
157169 end
170+
171+ # Capture ROWID for LOB writes on tables without primary keys
172+ # This must happen right after exec_update while the cursor still has the rowid
173+ if cursor . respond_to? ( :rowid )
174+ @last_insert_rowid = cursor . rowid
175+ end
176+
158177 cursor . close unless cached
159178 build_result ( columns : returning_id_col || [ ] , rows : rows )
160179 end
@@ -280,7 +299,28 @@ def empty_insert_statement_value
280299
281300 # Writes LOB values from attributes for specified columns
282301 def write_lobs ( table_name , klass , attributes , columns ) # :nodoc:
283- id = quote ( attributes [ klass . primary_key ] )
302+ pk = klass . primary_key
303+
304+ # Build WHERE clause based on what's available
305+ where_clause = if pk . nil? && @last_insert_rowid
306+ # Use ROWID when no primary key is defined but we have the rowid from INSERT
307+ "ROWID = #{ quote ( @last_insert_rowid ) } "
308+ elsif pk . nil?
309+ # No primary key and no ROWID - cannot locate the row
310+ if columns . any? { |col | attributes [ col . name ] . present? }
311+ @logger &.warn "Cannot write LOB columns for #{ table_name } - table has no primary key " \
312+ "and ROWID is not available. LOB data may be truncated. Consider adding " \
313+ "a primary key constraint or explicitly setting self.primary_key on the model."
314+ end
315+ return
316+ elsif pk . is_a? ( Array )
317+ # Composite primary key - build AND conditions for each column
318+ pk . map { |col | "#{ quote_column_name ( col ) } = #{ quote ( attributes [ col ] ) } " } . join ( " AND " )
319+ else
320+ # Single primary key
321+ "#{ quote_column_name ( pk ) } = #{ quote ( attributes [ pk ] ) } "
322+ end
323+
284324 columns . each do |col |
285325 value = attributes [ col . name ]
286326 # changed sequence of next two lines - should check if value is nil before converting to yaml
@@ -289,16 +329,18 @@ def write_lobs(table_name, klass, attributes, columns) # :nodoc:
289329 # value can be nil after serialization because ActiveRecord serializes [] and {} as nil
290330 next unless value
291331 uncached do
292- unless lob_record = select_one ( sql = <<~SQL . squish , "Writable Large Object" )
293- SELECT #{ quote_column_name ( col . name ) } FROM #{ quote_table_name ( table_name ) }
294- WHERE #{ quote_column_name ( klass . primary_key ) } = #{ id } FOR UPDATE
295- SQL
332+ sql = "SELECT #{ quote_column_name ( col . name ) } FROM #{ quote_table_name ( table_name ) } " \
333+ "WHERE #{ where_clause } FOR UPDATE"
334+ unless lob_record = select_one ( sql , "Writable Large Object" )
296335 raise ActiveRecord ::RecordNotFound , "statement #{ sql } returned no rows"
297336 end
298337 lob = lob_record [ col . name ]
299338 _connection . write_lob ( lob , value . to_s , col . type == :binary )
300339 end
301340 end
341+
342+ # Clear the stored ROWID after use to prevent it being used for wrong row
343+ @last_insert_rowid = nil
302344 end
303345
304346 private
0 commit comments