|
| 1 | +class Avram::BulkUpsert(T) |
| 2 | + def initialize(@records : Array(T), @column_names : Array(Symbol)) |
| 3 | + @records = set_timestamps(records) |
| 4 | + end |
| 5 | + |
| 6 | + def statement |
| 7 | + <<-SQL |
| 8 | + INSERT INTO #{table}(#{fields}) |
| 9 | + (select * from unnest(#{value_placeholders})) |
| 10 | + ON CONFLICT (#{conflicts}) DO UPDATE SET #{updates} |
| 11 | + RETURNING #{returning} |
| 12 | + SQL |
| 13 | + end |
| 14 | + |
| 15 | + def args |
| 16 | + @records.map do |record| |
| 17 | + record.changed_attributes.map(&.value) |
| 18 | + end.transpose |
| 19 | + end |
| 20 | + |
| 21 | + private def conflicts |
| 22 | + @column_names.join(", ") |
| 23 | + end |
| 24 | + |
| 25 | + private def set_timestamps(collection) |
| 26 | + collection.map do |record| |
| 27 | + record.created_at.value ||= Time.utc if record.responds_to?(:created_at) |
| 28 | + record.updated_at.value = Time.utc if record.responds_to?(:updated_at) |
| 29 | + record |
| 30 | + end |
| 31 | + end |
| 32 | + |
| 33 | + private def table |
| 34 | + @records.first.table_name |
| 35 | + end |
| 36 | + |
| 37 | + private def updates |
| 38 | + update_keys = changed_attributes.flat_map(&.name) |
| 39 | + (update_keys - [:created_at]).map do |column| |
| 40 | + "#{column}=EXCLUDED.#{column}" |
| 41 | + end.join(", ") |
| 42 | + end |
| 43 | + |
| 44 | + private def returning |
| 45 | + T.column_names.join(", ") |
| 46 | + end |
| 47 | + |
| 48 | + private def changed_attributes |
| 49 | + @records.first.changed_attributes |
| 50 | + end |
| 51 | + |
| 52 | + private def fields |
| 53 | + changed_attributes.map do |key| |
| 54 | + <<-TEXT |
| 55 | + "#{key.name.to_s}" |
| 56 | + TEXT |
| 57 | + end.join(", ") |
| 58 | + end |
| 59 | + |
| 60 | + private def column_types |
| 61 | + T.database_table_info.not_nil!.columns.map do |column_info| |
| 62 | + [ |
| 63 | + column_info.column_name, |
| 64 | + column_info.data_type, |
| 65 | + ] |
| 66 | + end.to_h |
| 67 | + end |
| 68 | + |
| 69 | + private def cast(column) |
| 70 | + "#{column_types[column.name.to_s]}[]" |
| 71 | + end |
| 72 | + |
| 73 | + private def cast(column : Avram::Attribute(Time)) |
| 74 | + "timestamptz[]" |
| 75 | + end |
| 76 | + |
| 77 | + private def value_placeholders |
| 78 | + changed_attributes.map_with_index(1) do |k, index| |
| 79 | + "$#{index}::#{cast(k)}" |
| 80 | + end.join(", ") |
| 81 | + end |
| 82 | +end |
0 commit comments