diff --git a/spec/adapters/mysql_adapter_spec.cr b/spec/adapters/mysql_adapter_spec.cr index 4a3162b..9e83e32 100644 --- a/spec/adapters/mysql_adapter_spec.cr +++ b/spec/adapters/mysql_adapter_spec.cr @@ -114,5 +114,20 @@ if Repo.config.adapter == Crecto::Adapters::Mysql sql.should eq(["SELECT posts.* FROM posts INNER JOIN users ON users.id = posts.user_id"]) end end + + it "should generate sql for query syntax with lock" do + query = Query + .where(name: "fridge") + .where("users.things < ?", [124]) + .order_by("users.name ASC") + .order_by("users.things DESC") + .limit(1) + Repo.config.get_connection.transaction do |tx| + Repo.lock(tx, User, query) + end + check_sql do |sql| + sql.should eq(["SELECT users.* FROM users WHERE (users.name=?) AND (users.things < ?) ORDER BY users.name ASC, users.things DESC LIMIT 1 FOR UPDATE"]) + end + end end end diff --git a/spec/adapters/postgres_adapter_spec.cr b/spec/adapters/postgres_adapter_spec.cr index b0e2ea5..0d002fb 100644 --- a/spec/adapters/postgres_adapter_spec.cr +++ b/spec/adapters/postgres_adapter_spec.cr @@ -103,5 +103,20 @@ if Repo.config.adapter == Crecto::Adapters::Postgres sql.should eq(["SELECT posts.* FROM posts INNER JOIN users ON users.id = posts.user_id"]) end end + + it "should generate sql for query syntax with lock" do + query = Query + .where(name: "fridge") + .where("users.things < ?", [124]) + .order_by("users.name ASC") + .order_by("users.things DESC") + .limit(1) + Repo.config.get_connection.transaction do |tx| + Repo.lock(tx, User, query) + end + check_sql do |sql| + sql.should eq(["SELECT users.* FROM users WHERE (users.name=$1) AND (users.things < $2) ORDER BY users.name ASC, users.things DESC LIMIT 1 FOR UPDATE"]) + end + end end end diff --git a/spec/repo_spec.cr b/spec/repo_spec.cr index bdf85b2..d140175 100644 --- a/spec/repo_spec.cr +++ b/spec/repo_spec.cr @@ -1301,6 +1301,34 @@ describe Crecto do end end + describe "#lock" do + it "should return rows" do + unless Repo.config.adapter == Crecto::Adapters::SQLite3 + users = [] of User + Repo.config.get_connection.transaction do |tx| + users = Repo.lock(tx, User) + end + users.size.should be > 0 + end + end + + it "should return rows with Query" do + unless Repo.config.adapter == Crecto::Adapters::SQLite3 + query = Query + .where(name: "fridge") + .where("users.things < ?", [124]) + .order_by("users.name ASC") + .order_by("users.things DESC") + .limit(1) + users = [] of User + Repo.config.get_connection.transaction do |tx| + users = Repo.lock(tx, User, query) + end + users.size.should be > 0 + end + end + end + # keep this at the end describe "#delete_all" do it "should delete destroy dependents" do diff --git a/src/crecto/adapters/base_adapter.cr b/src/crecto/adapters/base_adapter.cr index 7d3540d..8bf03a5 100644 --- a/src/crecto/adapters/base_adapter.cr +++ b/src/crecto/adapters/base_adapter.cr @@ -13,6 +13,8 @@ module Crecto all(conn, queryable, query) when :delete_all delete(conn, queryable, query) + when :lock + all(conn, queryable, query, true) end end @@ -153,8 +155,7 @@ module Crecto builder << " SELECT " << ag << '(' << queryable.table_name << '.' << field << ") FROM " << queryable.table_name end - - private def all(conn, queryable, query) + private def all(conn, queryable, query, for_update = false) params = [] of DbValue | Array(DbValue) q = String.build do |builder| @@ -177,6 +178,7 @@ module Crecto limit(builder, query) offset(builder, query) group_by(builder, query) + builder << " FOR UPDATE" if for_update end execute(conn, position_args(q), params) diff --git a/src/crecto/live_transaction.cr b/src/crecto/live_transaction.cr index f40aff3..d81e358 100644 --- a/src/crecto/live_transaction.cr +++ b/src/crecto/live_transaction.cr @@ -26,5 +26,9 @@ module Crecto def update_all(queryable, query, update_tuple : NamedTuple) update_all(queryable, query, update_tuple.to_h) end + + def lock(queryable, query = Crecto::Repo::Query.new) + @repo.lock(@tx, queryable, query) + end end end diff --git a/src/crecto/repo.cr b/src/crecto/repo.cr index 2a610da..9bd6c96 100644 --- a/src/crecto/repo.cr +++ b/src/crecto/repo.cr @@ -533,6 +533,20 @@ module Crecto end end + # Returns a list of locked *queryable* instances. Accepts an optional `query` + # + # ``` + # Crecto::Repo.config.get_connection.transaction do |tx| + # users = Crecto::Repo.lock(tx, User) + # end + # ``` + def lock(tx : DB::Transaction, queryable, query = Query.new) : Array + raise Crecto::InvalidAdapter.new "SQLite3 cannot use lock feature." if config.adapter == Crecto::Adapters::SQLite3 + q = config.adapter.run(tx, :lock, queryable, query).as(DB::ResultSet) + results = queryable.from_rs(q) + results + end + {% for operation in %w[insert update delete] %} private def run_operation(operation : Multi::{{operation.camelcase.id}}, tx) {{operation.id}}(operation.instance, tx)