Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding table locking. #1076

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
15 changes: 15 additions & 0 deletions src/avram/database.cr
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ abstract class Avram::Database
end
end

# Creates a lock on the table in `mode`
# ```
# AppDatabase.with_lock_on(User, mode: :row_exclusive) do
# user = UserQuery.new.id(1).for_update.first
# SaveUser.update!(user, name: "New Name")
# end
# ```
def self.with_lock_on(model : Avram::Model.class, mode : Avram::TableLockMode, &)
exec("BEGIN")
exec("LOCK TABLE #{model.table_name} IN #{mode} MODE")
yield
ensure
exec("END")
end

# Methods without a block
{% for crystal_db_alias in [:exec, :scalar, :query, :query_all, :query_one, :query_one?] %}
# Same as crystal-db's `DB::QueryMethods#{{ crystal_db_alias.id }}` but with instrumentation
Expand Down
14 changes: 13 additions & 1 deletion src/avram/query_builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Avram::QueryBuilder
@prepared_statement_placeholder = 0
@distinct : Bool = false
@delete : Bool = false
@for_update : Bool = false

def initialize(@table)
end
Expand Down Expand Up @@ -116,7 +117,7 @@ class Avram::QueryBuilder
end

private def sql_condition_clauses
[joins_sql, wheres_sql, group_sql, order_sql, limit_sql, offset_sql]
[joins_sql, wheres_sql, group_sql, order_sql, limit_sql, offset_sql, locking_sql]
end

def delete : self
Expand Down Expand Up @@ -158,6 +159,11 @@ class Avram::QueryBuilder
self
end

def for_update : self
@for_update = true
Comment on lines +162 to +163
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Postgres supports more than just FOR UPDATE

FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE }

So this might need some sort of args, or maybe just different methods like

def for_no_key_update
end
def for_share
end
def for_key_share
end

# VS

def for_update(for_method)
end
for_update(:no_key_update)
for_update(:share)

self
end

def order_by(order : OrderByClause) : self
reset_order if ordered_randomly?
@orders << order
Expand Down Expand Up @@ -384,4 +390,10 @@ class Avram::QueryBuilder
private def delete_sql : String
"DELETE FROM #{table}"
end

private def locking_sql : String?
if @for_update
"FOR UPDATE"
end
end
end
4 changes: 4 additions & 0 deletions src/avram/queryable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ module Avram::Queryable(T)
clone.tap &.query.offset(amount)
end

def for_update : self
clone.tap &.query.for_update
end

def first? : T?
with_ordered_query
.limit(1)
Expand Down
16 changes: 16 additions & 0 deletions src/avram/table_lock_mode.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Avram
enum TableLockMode
ACCESS_SHARE
ROW_SHARE
ROW_EXCLUSIVE
SHARE_UPDATE_EXCLUSIVE
SHARE
SHARE_ROW_EXCLUSIVE
EXCLUSIVE
ACCESS_EXCLUSIVE

def to_s
member_name.to_s.gsub("_", " ")
end
end
end
Loading