Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions spec/interro_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ struct UserQuery < Interro::QueryBuilder(User)
insert! email: email, name: name
end

def create(user_templates : Array(Template))
insert user_templates.map(&.to_named_tuple)
end

def create!(user_templates : Array(Template))
insert! user_templates.map(&.to_named_tuple)
end
Expand All @@ -117,6 +121,12 @@ struct UserQuery < Interro::QueryBuilder(User)
do: Interro::Update.new(set: {name: name, updated_at: Time.utc})
end

def upsert(user_templates : Array(Template), name_on_conflict : String) : Array(User)
insert user_templates.map(&.to_named_tuple),
on_conflict: Interro::ConflictHandler.new("email",
do: Interro::Update.new(set: {name: name_on_conflict}))
end

def upsert!(user_templates : Array(Template), name_on_conflict : String) : Int32
insert! user_templates.map(&.to_named_tuple),
on_conflict: Interro::ConflictHandler.new("email",
Expand Down Expand Up @@ -445,6 +455,17 @@ describe Interro do
user.email.should eq email
user.name.should eq "Foo"
end

it "can insert many rows" do
templates = Array.new(10) { |i|
UserQuery::Template.new(email: "one-of-many.#{UUID.v7}", name: "User #{i}")
}

users = UserQuery.new.create(templates)

users.map(&.email).should eq templates.map(&.email)
users.map(&.name).should eq templates.map(&.name)
end

it "can insert many rows without returning them" do
result = UserQuery.new.create!(Array.new(10) { |i|
Expand All @@ -464,6 +485,22 @@ describe Interro do
end

it "can upsert multiple rows" do
templates = [
UserQuery::Template.new(email: "pip-#{UUID.random}@example.com", name: "Pip"),
UserQuery::Template.new(email: "pat-#{UUID.random}@example.com", name: "Pat"),
]

# No rows exist yet, so this only inserts; the conflict update does not run.
inserted = query.upsert(templates, name_on_conflict: "ignored")
inserted.map(&.name).should eq %w[Pip Pat]

# Every row now conflicts on email, so the handler updates each name.
upserted = query.upsert(templates, name_on_conflict: "Updated")
upserted.map(&.id).should eq inserted.map(&.id)
upserted.map(&.name).should eq %w[Updated Updated]
end

it "can upsert multiple rows without returning them" do
templates = [
UserQuery::Template.new(email: "ivy-#{UUID.random}@example.com", name: "Ivy"),
UserQuery::Template.new(email: "jack-#{UUID.random}@example.com", name: "Jack"),
Expand Down
14 changes: 10 additions & 4 deletions src/create_many_operation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ module Interro
def initialize(@queryable : DB::Database | DB::Connection)
end

def call(query : QueryBuilder(T), params, on_conflict conflict_handler : ConflictHandler? = nil) : T
def call(query : QueryBuilder(T), params : Array(NamedTuple), on_conflict conflict_handler : ConflictHandler? = nil) : Array(T)
table_name = query.sql_table_name
args = params
.values
.flat_map(&.values.to_a)
.map { |value| Interro::Any.new(value) }
.to_a
sql = generate_query query.sql_table_name, params, args,
on_conflict: conflict_handler,
returning: ->(io : IO) { query.select_columns io }

@queryable.query_one sql, args: args, as: T
@queryable.query_all sql, args: args, as: T
end

def call!(query : QueryBuilder(T), params : Array(NamedTuple), on_conflict conflict_handler : ConflictHandler? = nil) : Int32
Expand All @@ -28,7 +28,8 @@ module Interro
.map { |value| Interro::Any.new(value) }
.to_a
sql = generate_query query.sql_table_name, params, args,
on_conflict: conflict_handler
on_conflict: conflict_handler,
returning: nil

@queryable.exec(sql, args: args)
.rows_affected
Expand All @@ -42,6 +43,7 @@ module Interro
params : Array(NamedTuple),
args,
on_conflict conflict_handler : ConflictHandler?,
returning returning_clause,
)
String.build do |str|
str << "INSERT INTO " << table_name << " ("
Expand Down Expand Up @@ -75,6 +77,10 @@ module Interro
end
conflict_handler.to_sql str, start_at: start || 1
end
if returning_clause
str << " RETURNING "
returning_clause.call str
end
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions src/query_builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,11 @@ module Interro
create_operation.call!(self, values, on_conflict: on_conflict)
end

# :doc:
protected def insert(records : Array(NamedTuple), on_conflict : ConflictHandler? = nil) : Array(T)
create_many_operation.call(self, records, on_conflict: on_conflict)
end

# :doc:
protected def insert!(records : Array(NamedTuple), on_conflict : ConflictHandler? = nil) : Int32
create_many_operation.call!(self, records, on_conflict: on_conflict)
Expand Down
Loading