|
1 | 1 | class Avram::BulkUpsert(T) |
2 | | - alias Params = Hash(Symbol, String) | Hash(Symbol, String?) | Hash(Symbol, Nil) |
3 | | - |
4 | | - def initialize(@records : Array(Params)) |
| 2 | + def initialize(@records : Array(T), @column_names : Array(Symbol)) |
| 3 | + @records = set_timestamps(records) |
5 | 4 | end |
6 | 5 |
|
7 | 6 | def statement |
8 | 7 | [ |
9 | | - "insert into #{table}(#{fields})", |
10 | | - "values #{value_placeholders}", |
11 | | - "ON CONFLICT DO UPDATE SET #{updates}", |
12 | | - "returning #{returning}", |
| 8 | + "INSERT INTO #{table}(#{fields})", |
| 9 | + "VALUES #{value_placeholders}", |
| 10 | + "ON CONFLICT (#{conflicts}) DO UPDATE SET #{updates}", |
| 11 | + "RETURNING #{returning}", |
13 | 12 | ].join(" ") |
14 | 13 | end |
15 | 14 |
|
| 15 | + private def conflicts |
| 16 | + @column_names.join(", ") |
| 17 | + end |
| 18 | + |
| 19 | + private def set_timestamps(collection) |
| 20 | + collection.map do |record| |
| 21 | + record.created_at.value ||= Time.utc if record.responds_to?(:created_at) |
| 22 | + record.updated_at.value = Time.utc if record.responds_to?(:updated_at) |
| 23 | + record |
| 24 | + end |
| 25 | + end |
| 26 | + |
16 | 27 | private def table |
17 | | - T.table_name |
| 28 | + @records.first.table_name |
18 | 29 | end |
19 | 30 |
|
20 | 31 | private def updates |
21 | | - conflict_updates = T.column_names.uniq.map do |column| |
22 | | - "SET #{column}=EXCLUDED.#{column}" |
23 | | - end |
| 32 | + update_keys = @records.first.insert_values.keys |
24 | 33 |
|
25 | | - if T.column_names.includes?(:updated_at) |
26 | | - conflict_updates.push("SET updated_at=NOW()").join(", ") |
27 | | - else |
28 | | - conflict_updates.join(", ") |
29 | | - end |
| 34 | + (update_keys - [:created_at]).map do |column| |
| 35 | + "#{column}=EXCLUDED.#{column}" |
| 36 | + end.join(", ") |
30 | 37 | end |
31 | 38 |
|
32 | 39 | private def returning |
33 | | - "id" |
| 40 | + T.column_names.join(", ") |
34 | 41 | end |
35 | 42 |
|
36 | 43 | private def fields |
37 | | - T.column_names.join(", ") |
| 44 | + @records.first.insert_values.keys.map do |key| |
| 45 | + <<-TEXT |
| 46 | + "#{key}" |
| 47 | + TEXT |
| 48 | + end.join(", ") |
38 | 49 | end |
39 | 50 |
|
40 | 51 | def args |
41 | | - @records.map &.values |
| 52 | + @records.flat_map do |record| |
| 53 | + record.insert_values.values |
| 54 | + end |
42 | 55 | end |
43 | 56 |
|
44 | | - private def placeholder_values(record) |
45 | | - values = record.values.map_with_index(1) do |_value, index| |
| 57 | + private def value_placeholders(record) |
| 58 | + record.insert_values.map_with_index(1) do |_value, index| |
46 | 59 | "$#{index}" |
47 | 60 | end.join(", ") |
48 | | - |
49 | | - "(#{values})" |
50 | 61 | end |
51 | 62 |
|
52 | 63 | private def value_placeholders |
| 64 | + i = 0 |
| 65 | + |
53 | 66 | @records.map do |record| |
54 | | - placeholder_values(record) |
| 67 | + "($#{i += 1}, $#{i += 1}, $#{i += 1}, $#{i += 1})" |
55 | 68 | end.join(", ") |
56 | 69 | end |
57 | 70 | end |
0 commit comments