From 5b2b036736e63524ed5448979f6355957085a6ab Mon Sep 17 00:00:00 2001 From: prog-supdex Date: Thu, 19 Oct 2023 15:24:00 +0200 Subject: [PATCH] add an ability to work with sqlite3 adapter --- .../adapters/base_adapter.rb | 79 +++++++++++++++++++ .../adapters/pg_upsert.rb | 12 ++- .../adapters/rails_upsert.rb | 8 +- .../adapters/sqlite_upsert.rb | 15 ++++ .../has_slotted_counter.rb | 11 ++- spec/slotted_counter_spec.rb | 11 ++- .../shared_examples_for_cache_counters.rb | 11 ++- 7 files changed, 126 insertions(+), 21 deletions(-) create mode 100644 lib/activerecord_slotted_counters/adapters/base_adapter.rb create mode 100644 lib/activerecord_slotted_counters/adapters/sqlite_upsert.rb diff --git a/lib/activerecord_slotted_counters/adapters/base_adapter.rb b/lib/activerecord_slotted_counters/adapters/base_adapter.rb new file mode 100644 index 0000000..cb2d734 --- /dev/null +++ b/lib/activerecord_slotted_counters/adapters/base_adapter.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module ActiveRecordSlottedCounters + module Adapters + class BaseAdapter + attr_reader :klass, :current_adapter_name + + def initialize(klass, current_adapter_name) + @klass = klass + @current_adapter_name = current_adapter_name + end + + def apply? + raise NoMethodError + end + + def bulk_insert(attributes, on_duplicate: nil, unique_by: nil) + raise NoMethodError + end + + def wrap_column_name(value) + "EXCLUDED.#{value}" + end + + private + + def build_base_sql(attributes) + keys = attributes.first.keys + klass.all_timestamp_attributes_in_model + + current_time = klass.current_time_from_proper_timezone + data = attributes.map { |attr| attr.values + [current_time, current_time] } + + columns = columns_for_attributes(keys) + + fields_str = quote_column_names(columns) + values_str = quote_many_records(columns, data) + + <<~SQL + INSERT INTO #{klass.quoted_table_name} + (#{fields_str}) + VALUES #{values_str} + SQL + end + + def unique_indexes + klass.connection.schema_cache.indexes(klass.table_name).select(&:unique) + end + + def columns_for_attributes(attributes) + attributes.map do |attribute| + klass.column_for_attribute(attribute) + end + end + + def quote_column_names(columns, table_name: false) + columns.map do |column| + column_name = klass.connection.quote_column_name(column.name) + if table_name + "#{klass.quoted_table_name}.#{column_name}" + else + column_name + end + end.join(",") + end + + def quote_record(columns, record_values) + values_str = record_values.each_with_index.map do |value, i| + type = klass.connection.lookup_cast_type_from_column(columns[i]) + klass.connection.quote(type.serialize(value)) + end.join(",") + "(#{values_str})" + end + + def quote_many_records(columns, data) + data.map { |values| quote_record(columns, values) }.join(",") + end + end + end +end diff --git a/lib/activerecord_slotted_counters/adapters/pg_upsert.rb b/lib/activerecord_slotted_counters/adapters/pg_upsert.rb index b16ad8f..ead9ae0 100644 --- a/lib/activerecord_slotted_counters/adapters/pg_upsert.rb +++ b/lib/activerecord_slotted_counters/adapters/pg_upsert.rb @@ -2,15 +2,13 @@ module ActiveRecordSlottedCounters module Adapters - class PgUpsert - attr_reader :klass + class PgUpsert < BaseAdapter + def apply? + return false if ActiveRecord::VERSION::MAJOR >= 7 - def initialize(klass) - @klass = klass - end + return false unless defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) - def apply? - ActiveRecord::VERSION::MAJOR < 7 && klass.connection.adapter_name == ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::ADAPTER_NAME + current_adapter_name == ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::ADAPTER_NAME end def bulk_insert(attributes, on_duplicate: nil, unique_by: nil) diff --git a/lib/activerecord_slotted_counters/adapters/rails_upsert.rb b/lib/activerecord_slotted_counters/adapters/rails_upsert.rb index 1dd103e..a6dbdc8 100644 --- a/lib/activerecord_slotted_counters/adapters/rails_upsert.rb +++ b/lib/activerecord_slotted_counters/adapters/rails_upsert.rb @@ -2,13 +2,7 @@ module ActiveRecordSlottedCounters module Adapters - class RailsUpsert - attr_reader :klass - - def initialize(klass) - @klass = klass - end - + class RailsUpsert < BaseAdapter def apply? ActiveRecord::VERSION::MAJOR >= 7 end diff --git a/lib/activerecord_slotted_counters/adapters/sqlite_upsert.rb b/lib/activerecord_slotted_counters/adapters/sqlite_upsert.rb new file mode 100644 index 0000000..2764481 --- /dev/null +++ b/lib/activerecord_slotted_counters/adapters/sqlite_upsert.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecordSlottedCounters + module Adapters + class SQLiteUpsert < PgUpsert + def apply? + return false if ActiveRecord::VERSION::MAJOR >= 7 + + return false unless defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + + current_adapter_name == ActiveRecord::ConnectionAdapters::SQLite3Adapter::ADAPTER_NAME + end + end + end +end diff --git a/lib/activerecord_slotted_counters/has_slotted_counter.rb b/lib/activerecord_slotted_counters/has_slotted_counter.rb index 259db8c..c690ee6 100644 --- a/lib/activerecord_slotted_counters/has_slotted_counter.rb +++ b/lib/activerecord_slotted_counters/has_slotted_counter.rb @@ -3,8 +3,10 @@ require "active_support" require "activerecord_slotted_counters/utils" +require "activerecord_slotted_counters/adapters/base_adapter" require "activerecord_slotted_counters/adapters/rails_upsert" require "activerecord_slotted_counters/adapters/pg_upsert" +require "activerecord_slotted_counters/adapters/sqlite_upsert" module ActiveRecordSlottedCounters class SlottedCounter < ::ActiveRecord::Base @@ -42,14 +44,17 @@ def slotted_counter_db_adapter def set_slotted_counter_db_adapter available_adapters = [ ActiveRecordSlottedCounters::Adapters::RailsUpsert, - ActiveRecordSlottedCounters::Adapters::PgUpsert + ActiveRecordSlottedCounters::Adapters::PgUpsert, + ActiveRecordSlottedCounters::Adapters::SQLiteUpsert ] + current_adapter_name = connection.adapter_name + adapter = available_adapters - .map { |adapter| adapter.new(self) } + .map { |adapter| adapter.new(self, current_adapter_name) } .detect { |adapter| adapter.apply? } - raise NotSupportedAdapter.new(connection.adapter_name) if adapter.nil? + raise NotSupportedAdapter.new(current_adapter_name) if adapter.nil? adapter end diff --git a/spec/slotted_counter_spec.rb b/spec/slotted_counter_spec.rb index 6645412..cc41d44 100644 --- a/spec/slotted_counter_spec.rb +++ b/spec/slotted_counter_spec.rb @@ -128,11 +128,18 @@ def insert_association_sql(association_class, article_id) association_table = association_class.arel_table foreign_key = association_class.reflections["article"].foreign_key + current_date_sql_command = + if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + "date('now')" + else + "now()" + end + insert_manager = Arel::InsertManager.new insert_manager.insert([ [association_table[foreign_key], article_id], - [association_table[:created_at], Arel.sql("now()")], - [association_table[:updated_at], Arel.sql("now()")] + [association_table[:created_at], Arel.sql(current_date_sql_command)], + [association_table[:updated_at], Arel.sql(current_date_sql_command)] ]) insert_manager.to_sql diff --git a/spec/support/shared_examples_for_cache_counters.rb b/spec/support/shared_examples_for_cache_counters.rb index c1e1200..c9342e3 100644 --- a/spec/support/shared_examples_for_cache_counters.rb +++ b/spec/support/shared_examples_for_cache_counters.rb @@ -153,11 +153,18 @@ def insert_comment_sql(comment_class, article_id) comment_table = comment_class.arel_table foreign_key = comment_class.reflections["article"].foreign_key + current_date_sql_command = + if defined?(ActiveRecord::ConnectionAdapters::SQLite3Adapter) + "date('now')" + else + "now()" + end + insert_manager = Arel::InsertManager.new insert_manager.insert([ [comment_table[foreign_key], article_id], - [comment_table[:created_at], Arel.sql("now()")], - [comment_table[:updated_at], Arel.sql("now()")] + [comment_table[:created_at], Arel.sql(current_date_sql_command)], + [comment_table[:updated_at], Arel.sql(current_date_sql_command)] ]) insert_manager.to_sql