-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Bulk Upserting records
Adds an `upsert` overload for Arrays to create large numbers of records at the same time. Uses PG's UNNEST behavior to allow for a near-infinite (buyer beware) number of insertions rather than being limited by PG's bind parameter restrictions (64k total binds, which would prevent more than a few thousand upserts at a time depending on the number of column inserts). Co-authored-by: Alex Piechowski <[email protected]> Co-authored-by: robacarp <[email protected]>
- Loading branch information
1 parent
36e2f87
commit 17b0f97
Showing
7 changed files
with
292 additions
and
3 deletions.
There are no files selected for viewing
9 changes: 9 additions & 0 deletions
9
db/migrations/20220113043033_add_unique_constraint_to_users.cr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class AddUniqueConstraintToUsers::V20220113043033 < Avram::Migrator::Migration::V1 | ||
def migrate | ||
create_index :users, [:name, :nickname], unique: true | ||
end | ||
|
||
def rollback | ||
drop_index :users, [:name, :nickname] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
class Avram::BulkUpsert(T) | ||
@column_types : Hash(String, String) | ||
@permitted_fields : Array(Symbol) | ||
|
||
def initialize(@records : Array(T), | ||
@conflicts : Array(Symbol), | ||
permitted_fields : Array(Symbol)) | ||
set_timestamps | ||
@sample_record = @records.first.as(T) | ||
@permitted_fields = permitted_fields_for(permitted_fields) | ||
|
||
@column_types = T.database_table_info.columns.map do |col_info| | ||
{ | ||
col_info.column_name, | ||
col_info.data_type, | ||
} | ||
end.to_h | ||
end | ||
|
||
def statement | ||
<<-SQL | ||
INSERT INTO #{table}(#{fields}) | ||
(SELECT * FROM unnest(#{value_placeholders})) | ||
ON CONFLICT (#{conflicts}) DO UPDATE SET #{updates} | ||
RETURNING #{returning} | ||
SQL | ||
end | ||
|
||
def args | ||
@records.map do |record| | ||
permitted_attributes(record).map(&.value) | ||
end.transpose | ||
end | ||
|
||
private def permitted_fields_for(fields : Array(Symbol)) | ||
fields.push(:created_at) if @sample_record.responds_to?(:created_at) | ||
fields.push(:updated_at) if @sample_record.responds_to?(:updated_at) | ||
fields.uniq! | ||
end | ||
|
||
private def permitted_attributes(record) | ||
record | ||
.attributes | ||
.select { |attr| @permitted_fields.includes?(attr.name) } | ||
end | ||
|
||
private def permitted_attributes | ||
permitted_attributes(@sample_record) | ||
end | ||
|
||
private def conflicts | ||
@conflicts.join(", ") | ||
end | ||
|
||
private def set_timestamps | ||
@records.each do |record| | ||
record.created_at.value ||= Time.utc if record.responds_to?(:created_at) | ||
record.updated_at.value ||= Time.utc if record.responds_to?(:updated_at) | ||
end | ||
end | ||
|
||
private def table | ||
@sample_record.table_name | ||
end | ||
|
||
private def updates | ||
(permitted_attribute_column_names - [:created_at]).map do |column| | ||
"#{column}=EXCLUDED.#{column}" | ||
end.compact.join(", ") | ||
end | ||
|
||
private def returning | ||
T.column_names.join(", ") | ||
end | ||
|
||
private def permitted_attribute_column_names | ||
permitted_attributes.map(&.name) | ||
end | ||
|
||
private def fields | ||
permitted_attribute_column_names.map(&.to_s).join(", ") | ||
end | ||
|
||
private def value_placeholders | ||
permitted_attributes.map_with_index(1) do |column, index| | ||
"$#{index}::#{@column_types[column.name.to_s]}[]" | ||
end.join(", ") | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Can be removed once https://github.com/will/crystal-pg/pull/244 is merged. | ||
module PQ | ||
record Param, slice : Slice(UInt8), size : Int32, format : Int16 do | ||
def self.encode_array(io, value : Nil) | ||
io << "NULL" | ||
end | ||
end | ||
end |