Skip to content

Commit

Permalink
feat: Add testing for activerecord 7.1 and support for trilogy adapter (
Browse files Browse the repository at this point in the history
#77)

---------

Co-authored-by: Hartley McGuire <skipkayhil@gmail.com>
  • Loading branch information
seuros and skipkayhil authored Oct 29, 2023
1 parent 00f1cfe commit 69c23fe
Show file tree
Hide file tree
Showing 23 changed files with 297 additions and 490 deletions.
17 changes: 4 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
services:
mysql:
image: mysql/mysql-server:8.0.30
image: mysql/mysql-server:5.7
ports:
- "3306:3306"
env:
Expand Down Expand Up @@ -41,24 +41,15 @@ jobs:
- '2.7'
- 'truffleruby'
rails:
- activerecord_7.1
- activerecord_7.0
- activerecord_6.1
- activerecord_6.0
- activerecord_edge
adapter:
- sqlite3:///tmp/test.sqlite3
- mysql2://root:root@0/with_advisory_lock_test
- trilogy://root:root@0/with_advisory_lock_test
- postgres://closure_tree:closure_tree@0/with_advisory_lock_test
include:
- ruby: jruby
rails: activerecord_6.0
adapter: jdbcmysql://root:root@0/with_advisory_lock_test
- ruby: jruby
rails: activerecord_6.0
adapter: jdbcsqlite3:///tmp/test.sqlite3
- ruby: jruby
rails: activerecord_6.0
adapter: jdbcpostgresql://closure_tree:closure_tree@0/with_advisory_lock_test
- ruby: jruby
rails: activerecord_6.1
adapter: jdbcmysql://root:root@0/with_advisory_lock_test
Expand All @@ -70,7 +61,7 @@ jobs:
adapter: jdbcpostgresql://closure_tree:closure_tree@0/with_advisory_lock_test
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ test/tmp
test/version_tmp
tmp
*.iml
test/sqlite.db
test/sqlite.db
.env
36 changes: 17 additions & 19 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
# frozen_string_literal: true

appraise 'activerecord-6.0' do
gem 'activerecord', '~> 6.0.0'
appraise 'activerecord-7.1' do
gem 'activerecord', '~> 7.1.0'
platforms :ruby do
gem 'sqlite3'
gem 'mysql2'
gem 'trilogy'
gem 'pg'
end
platforms :jruby do
gem "activerecord-jdbcmysql-adapter"
gem "activerecord-jdbcpostgresql-adapter"
gem "activerecord-jdbcsqlite3-adapter"
end
end

appraise 'activerecord-6.1' do
gem 'activerecord', '~> 6.1.0'
appraise 'activerecord-7.0' do
gem 'activerecord', '~> 7.0.0'
platforms :ruby do
gem 'sqlite3'
gem 'mysql2'
gem 'trilogy'
gem "activerecord-trilogy-adapter"
gem 'pg'
end
platforms :jruby do
Expand All @@ -28,20 +26,20 @@ appraise 'activerecord-6.1' do
end
end

appraise 'activerecord-7.0' do
gem 'activerecord', '~> 7.0.0'
platforms :ruby do
gem 'sqlite3'
gem 'mysql2'
gem 'pg'
end
end
appraise 'activerecord-6.1' do
gem 'activerecord', '~> 6.1.0'

appraise 'activerecord-edge' do
gem 'activerecord', github: 'rails/rails', branch: 'main'
platforms :ruby do
gem 'sqlite3'
gem 'mysql2'
gem 'trilogy'
gem "activerecord-trilogy-adapter"
gem 'pg'
end
platforms :jruby do
gem "activerecord-jdbcmysql-adapter"
gem "activerecord-jdbcpostgresql-adapter"
gem "activerecord-jdbcsqlite3-adapter"
end
end

19 changes: 0 additions & 19 deletions gemfiles/activerecord_6.0.gemfile

This file was deleted.

2 changes: 2 additions & 0 deletions gemfiles/activerecord_6.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ gem "activerecord", "~> 6.1.0"
platforms :ruby do
gem "sqlite3"
gem "mysql2"
gem "trilogy"
gem "activerecord-trilogy-adapter"
gem "pg"
end

Expand Down
8 changes: 8 additions & 0 deletions gemfiles/activerecord_7.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ gem "activerecord", "~> 7.0.0"
platforms :ruby do
gem "sqlite3"
gem "mysql2"
gem "trilogy"
gem "activerecord-trilogy-adapter"
gem "pg"
end

platforms :jruby do
gem "activerecord-jdbcmysql-adapter"
gem "activerecord-jdbcpostgresql-adapter"
gem "activerecord-jdbcsqlite3-adapter"
end

gemspec path: "../"
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

source "https://rubygems.org"

gem "activerecord", github: "rails/rails", branch: "main"
gem "activerecord", "~> 7.1.0"

platforms :ruby do
gem "sqlite3"
gem "mysql2"
gem "trilogy"
gem "pg"
end

Expand Down
4 changes: 1 addition & 3 deletions lib/with_advisory_lock.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'with_advisory_lock/version'
require 'active_support'
require_relative 'with_advisory_lock/failed_to_acquire_lock'

module WithAdvisoryLock
extend ActiveSupport::Autoload
Expand All @@ -9,9 +10,6 @@ module WithAdvisoryLock
autoload :DatabaseAdapterSupport
autoload :Flock
autoload :MySQL, 'with_advisory_lock/mysql'
autoload :MySQLNoNesting, 'with_advisory_lock/mysql_no_nesting'
autoload :NestedAdvisoryLockError
autoload :FailedToAcquireLock
autoload :PostgreSQL, 'with_advisory_lock/postgresql'
end

Expand Down
21 changes: 4 additions & 17 deletions lib/with_advisory_lock/concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Concern
extend ActiveSupport::Concern
delegate :with_advisory_lock, :with_advisory_lock!, :advisory_lock_exists?, to: 'self.class'

module ClassMethods
class_methods do
def with_advisory_lock(lock_name, options = {}, &block)
result = with_advisory_lock_result(lock_name, options, &block)
result.lock_was_acquired? ? result.result : false
Expand All @@ -23,8 +23,7 @@ def with_advisory_lock!(lock_name, options = {}, &block)
end

def with_advisory_lock_result(lock_name, options = {}, &block)
class_options = options.extract!(:force_nested_lock_support) if options.respond_to?(:fetch)
impl = impl_class(class_options).new(connection, lock_name, options)
impl = impl_class.new(connection, lock_name, options)
impl.with_advisory_lock_if_needed(&block)
end

Expand All @@ -40,24 +39,12 @@ def current_advisory_lock

private

def impl_class(options = nil)
def impl_class
adapter = WithAdvisoryLock::DatabaseAdapterSupport.new(connection)
if adapter.postgresql?
WithAdvisoryLock::PostgreSQL
elsif adapter.mysql?
nested_lock = if options.respond_to?(:fetch) && [true,
false].include?(options.fetch(:force_nested_lock_support,
nil))
options.fetch(:force_nested_lock_support)
else
adapter.mysql_nested_lock_support?
end

if nested_lock
WithAdvisoryLock::MySQL
else
WithAdvisoryLock::MySQLNoNesting
end
WithAdvisoryLock::MySQL
else
WithAdvisoryLock::Flock
end
Expand Down
41 changes: 1 addition & 40 deletions lib/with_advisory_lock/database_adapter_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,7 @@ def initialize(connection)
end

def mysql?
@sym_name == :mysql2
end

# Nested lock support for MySQL was introduced in 5.7.5
# Checking by version number is complicated by MySQL compatible DBs (like MariaDB) having their own versioning schemes
# Therefore, we check for nested lock support by simply trying a nested lock, then testing and caching the outcome
def mysql_nested_lock_support?
return false unless mysql?

# We select the MySQL version this way and cache on it, as MySQL will report versions like "5.7.5", and MariaDB will
# report versions like "10.3.8-MariaDB", which allow us to cache on features without introducing problems.
version = @connection.select_value('SELECT version()')

@@mysql_nl_cache_mutex.synchronize do
return @@mysql_nl_cache[version] if @@mysql_nl_cache.keys.include?(version)

lock_1 = "\"nested-test-1-#{SecureRandom.hex}\""
lock_2 = "\"nested-test-2-#{SecureRandom.hex}\""

get_1 = @connection.select_value("SELECT GET_LOCK(#{lock_1}, 0) AS t#{SecureRandom.hex}")
get_2 = @connection.select_value("SELECT GET_LOCK(#{lock_2}, 0) AS t#{SecureRandom.hex}")

# Both locks should succeed in old and new MySQL versions with "1"
raise "Unexpected nested lock acquire result #{get_1}, #{get_2}" unless [get_1, get_2] == [1, 1]

release_1 = @connection.select_value("SELECT RELEASE_LOCK(#{lock_1}) AS t#{SecureRandom.hex}")
release_2 = @connection.select_value("SELECT RELEASE_LOCK(#{lock_2}) AS t#{SecureRandom.hex}")

# In MySQL < 5.7.5 release_1 will return nil (not currently locked) and release_2 will return 1 (successfully unlocked)
# In MySQL >= 5.7.5 release_1 and release_2 will return 1 (both successfully unlocked)
# See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock for more
@@mysql_nl_cache[version] = case [release_1, release_2]
when [1, 1]
true
when [nil, 1]
false
else
raise "Unexpected nested lock release result #{release_1}, #{release_2}"
end
end
%i[mysql2 trilogy].include? @sym_name
end

def postgresql?
Expand Down
1 change: 0 additions & 1 deletion lib/with_advisory_lock/mysql.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

module WithAdvisoryLock
# MySQL > 5.7.5 supports nested locks
class MySQL < Base
# See https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
def try_lock
Expand Down
22 changes: 0 additions & 22 deletions lib/with_advisory_lock/mysql_no_nesting.rb

This file was deleted.

16 changes: 0 additions & 16 deletions lib/with_advisory_lock/nested_advisory_lock_error.rb

This file was deleted.

18 changes: 8 additions & 10 deletions test/concern_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,32 @@

require 'test_helper'

describe 'with_advisory_lock.concern' do
it 'adds with_advisory_lock to ActiveRecord classes' do
class WithAdvisoryLockConcernTest < GemTestCase
test 'adds with_advisory_lock to ActiveRecord classes' do
assert_respond_to(Tag, :with_advisory_lock)
end

it 'adds with_advisory_lock to ActiveRecord instances' do
test 'adds with_advisory_lock to ActiveRecord instances' do
assert_respond_to(Label.new, :with_advisory_lock)
end

it 'adds advisory_lock_exists? to ActiveRecord classes' do
test 'adds advisory_lock_exists? to ActiveRecord classes' do
assert_respond_to(Tag, :advisory_lock_exists?)
end

it 'adds advisory_lock_exists? to ActiveRecord classes' do
test 'adds advisory_lock_exists? to ActiveRecord instances' do
assert_respond_to(Label.new, :advisory_lock_exists?)
end
end

describe 'ActiveRecord query cache' do
it 'does not disable quary cache by default' do
class ActiveRecordQueryCacheTest < GemTestCase
test 'does not disable quary cache by default' do
ActiveRecord::Base.expects(:uncached).never

Tag.with_advisory_lock('lock') { Tag.first }
end

it 'can disable ActiveRecord query cache' do
test 'can disable ActiveRecord query cache' do
ActiveRecord::Base.expects(:uncached).once

Tag.with_advisory_lock('a-lock', disable_query_cache: true) { Tag.first }
end
end
Loading

0 comments on commit 69c23fe

Please sign in to comment.