Skip to content

Commit

Permalink
initial attempt to add trilogy adapater
Browse files Browse the repository at this point in the history
  • Loading branch information
zmariscal committed Aug 31, 2023
1 parent 0fc4ef1 commit 4435c70
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pg_version = '1.1' if version >= 6.1
group :development, :test do
gem 'rubocop'
gem 'rake'
gem 'activerecord-trilogy-adapter'
end

# Database Adapters
Expand All @@ -26,6 +27,11 @@ platforms :ruby do
gem "sqlite3", "~> #{sqlite3_version}"
# seamless_database_pool requires Ruby ~> 2.0
gem "seamless_database_pool", "~> 1.0.20" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0.0')

RUBY_VERSION.to_f >= 3 do
gem "trilogy"
gem "activerecord-trilogy-adapter"
end
end

platforms :jruby do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require "trilogy/activerecord-trilogy-adapter"
require "active_record/connection_adapters/trilogy_adapter"
require "activerecord-import/adapters/trilogy_adapter"

class ActiveRecord::ConnectionAdapters::TrilogyAdapter
include ActiveRecord::Import::TrilogyAdapter
end
137 changes: 137 additions & 0 deletions lib/activerecord-import/adapters/trilogy_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# frozen_string_literal: true

module ActiveRecord::Import::TrilogyAdapter
include ActiveRecord::Import::ImportSupport
include ActiveRecord::Import::OnDuplicateKeyUpdateSupport

NO_MAX_PACKET = 0
QUERY_OVERHEAD = 8 # This was shown to be true for MySQL, but it's not clear where the overhead is from.

# +sql+ can be a single string or an array. If it is an array all
# elements that are in position >= 1 will be appended to the final SQL.
def insert_many( sql, values, options = {}, *args ) # :nodoc:
# the number of inserts default
number_of_inserts = 0

base_sql, post_sql = case sql
when String
[sql, '']
when Array
[sql.shift, sql.join( ' ' )]
end

sql_size = QUERY_OVERHEAD + base_sql.bytesize + post_sql.bytesize

# the number of bytes the requested insert statement values will take up
values_in_bytes = values.sum(&:bytesize)

# the number of bytes (commas) it will take to comma separate our values
comma_separated_bytes = values.size - 1

# the total number of bytes required if this statement is one statement
total_bytes = sql_size + values_in_bytes + comma_separated_bytes

max = max_allowed_packet

# if we can insert it all as one statement
if max == NO_MAX_PACKET || total_bytes <= max || options[:force_single_insert]
number_of_inserts += 1
sql2insert = base_sql + values.join( ',' ) + post_sql
insert( sql2insert, *args )
else
value_sets = ::ActiveRecord::Import::ValueSetsBytesParser.parse(values,
reserved_bytes: sql_size,
max_bytes: max)

transaction(requires_new: true) do
value_sets.each do |value_set|
number_of_inserts += 1
sql2insert = base_sql + value_set.join( ',' ) + post_sql
insert( sql2insert, *args )
end
end
end

ActiveRecord::Import::Result.new([], number_of_inserts, [], [])
end

# Returns the maximum number of bytes that the server will allow
# in a single packet
def max_allowed_packet # :nodoc:
@max_allowed_packet ||= begin
result = execute( "SELECT @@max_allowed_packet" )
# original Mysql gem responds to #fetch_row while Mysql2 responds to #first
val = result.respond_to?(:fetch_row) ? result.fetch_row[0] : result.first[0]
val.to_i
end
end

def pre_sql_statements( options)
sql = []
sql << "IGNORE" if options[:ignore] || options[:on_duplicate_key_ignore]
sql + super
end

# Add a column to be updated on duplicate key update
def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
if (columns = options[:on_duplicate_key_update])
case columns
when Array then columns << column.to_sym unless columns.include?(column.to_sym)
when Hash then columns[column.to_sym] = column.to_sym
end
end
end

# Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
# in +args+.
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql = ' ON DUPLICATE KEY UPDATE '.dup
arg, model, _primary_key, locking_column = args
case arg
when Array
sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arg )
when Hash
sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, arg )
when String
sql << arg
else
raise ArgumentError, "Expected Array or Hash"
end
sql
end

def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
results = arr.map do |column|
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
qc = quote_column_name( original_column_name )
"#{table_name}.#{qc}=VALUES(#{qc})"
end
increment_locking_column!(table_name, results, locking_column)
results.join( ',' )
end

def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
results = hsh.map do |column1, column2|
original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
qc1 = quote_column_name( original_column1_name )

original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
qc2 = quote_column_name( original_column2_name )

"#{table_name}.#{qc1}=VALUES( #{qc2} )"
end
increment_locking_column!(table_name, results, locking_column)
results.join( ',')
end

# Return true if the statement is a duplicate key record error
def duplicate_key_update_error?(exception) # :nodoc:
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
end

def increment_locking_column!(table_name, results, locking_column)
if locking_column.present?
results << "`#{locking_column}`=#{table_name}.`#{locking_column}`+1"
end
end
end
3 changes: 3 additions & 0 deletions test/adapters/trilogy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

ENV["ARE_DB"] = "trilogy"
4 changes: 4 additions & 0 deletions test/database.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ sqlite3: &sqlite3

spatialite:
<<: *sqlite3

trilogy:
<<: *common
adapter: trilogy
4 changes: 4 additions & 0 deletions test/github/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ sqlite3: &sqlite3

spatialite:
<<: *sqlite3

trilogy:
<<: *common
adapter: trilogy

0 comments on commit 4435c70

Please sign in to comment.