Skip to content

Commit e4b0156

Browse files
committed
Support ROWID-based LOB writes for tables without primary keys
Add ability to write LOB data to tables that don't have a primary key by: 1. Exposing cursor.rowid method in OCI connection wrapper 2. Capturing ROWID after INSERT in exec_insert (@last_insert_rowid) 3. Using ROWID in write_lobs WHERE clause when no PK is available 4. Supporting composite primary keys (Array) in write_lobs The ROWID approach works because: - Ruby-oci8's cursor.rowid returns the ROWID of the last inserted row - ROWID uniquely identifies any row regardless of table structure - The after_create callback fires immediately after INSERT on same connection Also includes ORA-01741 diagnostic logging for empty column detection.
1 parent e834b46 commit e4b0156

File tree

2 files changed

+35
-5
lines changed

2 files changed

+35
-5
lines changed

lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, retu
155155
returning_id = cursor.get_returning_param(returning_id_index, Integer).to_i
156156
rows << [returning_id]
157157
end
158+
159+
# Capture ROWID for LOB writes on tables without primary keys
160+
# This must happen right after exec_update while the cursor still has the rowid
161+
if cursor.respond_to?(:rowid)
162+
@last_insert_rowid = cursor.rowid
163+
end
164+
158165
cursor.close unless cached
159166
build_result(columns: returning_id_col || [], rows: rows)
160167
end
@@ -280,7 +287,22 @@ def empty_insert_statement_value
280287

281288
# Writes LOB values from attributes for specified columns
282289
def write_lobs(table_name, klass, attributes, columns) # :nodoc:
283-
id = quote(attributes[klass.primary_key])
290+
pk = klass.primary_key
291+
292+
where_clause = if pk.nil? && @last_insert_rowid
293+
"ROWID = #{quote(@last_insert_rowid)}"
294+
elsif pk.nil?
295+
if columns.any? { |col| attributes[col.name].present? }
296+
@logger&.warn "Cannot write LOB columns for #{table_name} - table has no primary key " \
297+
"and ROWID is not available. LOB data may be truncated."
298+
end
299+
return
300+
elsif pk.is_a?(Array)
301+
pk.map { |col| "#{quote_column_name(col)} = #{quote(attributes[col])}" }.join(" AND ")
302+
else
303+
"#{quote_column_name(pk)} = #{quote(attributes[pk])}"
304+
end
305+
284306
columns.each do |col|
285307
value = attributes[col.name]
286308
# changed sequence of next two lines - should check if value is nil before converting to yaml
@@ -289,16 +311,18 @@ def write_lobs(table_name, klass, attributes, columns) # :nodoc:
289311
# value can be nil after serialization because ActiveRecord serializes [] and {} as nil
290312
next unless value
291313
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
314+
sql = "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} " \
315+
"WHERE #{where_clause} FOR UPDATE"
316+
unless lob_record = select_one(sql, "Writable Large Object")
296317
raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
297318
end
298319
lob = lob_record[col.name]
299320
_connection.write_lob(lob, value.to_s, col.type == :binary)
300321
end
301322
end
323+
324+
# Clear the stored ROWID after use to prevent it being used for wrong row
325+
@last_insert_rowid = nil
302326
end
303327

304328
private

lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ def get_returning_param(position, type)
193193
def close
194194
@raw_cursor.close
195195
end
196+
197+
# Returns the ROWID of the last inserted/updated/deleted row
198+
# This is useful for LOB writes on tables without primary keys
199+
def rowid
200+
@raw_cursor.rowid
201+
end
196202
end
197203

198204
def select(sql, name = nil, return_column_names = false)

0 commit comments

Comments
 (0)