# frozen_string_literal: truemoduleActiveRecord::Import::SQLite3AdapterincludeActiveRecord::Import::ImportSupportincludeActiveRecord::Import::OnDuplicateKeyUpdateSupportMIN_VERSION_FOR_IMPORT="3.7.11"MIN_VERSION_FOR_UPSERT="3.24.0"SQLITE_LIMIT_COMPOUND_SELECT=500# Override our conformance to ActiveRecord::Import::ImportSupport interface# to ensure that we only support import in supported version of SQLite.# Which INSERT statements with multiple value sets was introduced in 3.7.11.defsupports_import?database_version>=MIN_VERSION_FOR_IMPORTenddefsupports_on_duplicate_key_update?database_version>=MIN_VERSION_FOR_UPSERTend# +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.definsert_many(sql,values,_options={},*args)# :nodoc:number_of_inserts=0base_sql,post_sql=casesqlwhenString[sql,'']whenArray[sql.shift,sql.join(' ')]endvalue_sets=::ActiveRecord::Import::ValueSetsRecordsParser.parse(values,max_records: SQLITE_LIMIT_COMPOUND_SELECT)transaction(requires_new: true)dovalue_sets.eachdo|value_set|number_of_inserts+=1sql2insert=base_sql+value_set.join(',')+post_sqlinsert(sql2insert,*args)endendActiveRecord::Import::Result.new([],number_of_inserts,[],[])enddefpre_sql_statements(options)sql=[]# Options :recursive and :on_duplicate_key_ignore are mutually exclusiveif!supports_on_duplicate_key_update?&&(options[:ignore]||options[:on_duplicate_key_ignore])sql<<"OR IGNORE"endsql+superenddefpost_sql_statements(table_name,options)# :nodoc:sql=[]# Options :recursive and :on_duplicate_key_ignore are mutually exclusiveifsupports_on_duplicate_key_update?&&((options[:ignore]||options[:on_duplicate_key_ignore])&&!options[:on_duplicate_key_update])sql<<sql_for_on_duplicate_key_ignore(options[:on_duplicate_key_ignore])endsql+superenddefnext_value_for_sequence(sequence_name)%{nextval('#{sequence_name}')}end# Add a column to be updated on duplicate key updatedefadd_column_for_on_duplicate_key_update(column,options={})# :nodoc:arg=options[:on_duplicate_key_update]caseargwhenHashcolumns=arg.fetch(:columns){arg[:columns]=[]}casecolumnswhenArraythencolumns<<column.to_symunlesscolumns.include?(column.to_sym)whenHashthencolumns[column.to_sym]=column.to_symendwhenArrayarg<<column.to_symunlessarg.include?(column.to_sym)endend# Returns a generated ON CONFLICT DO NOTHING statement given the passed# in +args+.defsql_for_on_duplicate_key_ignore(*args)# :nodoc:arg=args.firstconflict_target=sql_for_conflict_target(arg)ifarg.is_a?(Hash)" ON CONFLICT #{conflict_target}DO NOTHING"end# Returns a generated ON CONFLICT DO UPDATE statement given the passed# in +args+.defsql_for_on_duplicate_key_update(table_name,*args)# :nodoc:arg,model,primary_key,locking_column=argsarg={columns: arg}ifarg.is_a?(Array)||arg.is_a?(String)returnunlessarg.is_a?(Hash)sql=' ON CONFLICT '.dupconflict_target=sql_for_conflict_target(arg)columns=arg.fetch(:columns,[])condition=arg[:condition]ifcolumns.respond_to?(:empty?)&&columns.empty?returnsql<<"#{conflict_target}DO NOTHING"endconflict_target||=sql_for_default_conflict_target(primary_key)unlessconflict_targetraiseArgumentError,'Expected :conflict_target to be specified'endsql<<"#{conflict_target}DO UPDATE SET "casecolumnswhenArraysql<<sql_for_on_duplicate_key_update_as_array(table_name,model,locking_column,columns)whenHashsql<<sql_for_on_duplicate_key_update_as_hash(table_name,model,locking_column,columns)whenStringsql<<columnselseraiseArgumentError,'Expected :columns to be an Array or Hash'endsql<<" WHERE #{condition}"ifcondition.present?sqlenddefsql_for_on_duplicate_key_update_as_array(table_name,model,locking_column,arr)# :nodoc:results=arr.mapdo|column|original_column_name=model.attribute_alias?(column)?model.attribute_alias(column):columnqc=quote_column_name(original_column_name)"#{qc}=EXCLUDED.#{qc}"endincrement_locking_column!(table_name,results,locking_column)results.join(',')enddefsql_for_on_duplicate_key_update_as_hash(table_name,model,locking_column,hsh)# :nodoc:results=hsh.mapdo|column1,column2|original_column1_name=model.attribute_alias?(column1)?model.attribute_alias(column1):column1qc1=quote_column_name(original_column1_name)original_column2_name=model.attribute_alias?(column2)?model.attribute_alias(column2):column2qc2=quote_column_name(original_column2_name)"#{qc1}=EXCLUDED.#{qc2}"endincrement_locking_column!(table_name,results,locking_column)results.join(',')enddefsql_for_conflict_target(args={})conflict_target=args[:conflict_target]index_predicate=args[:index_predicate]ifconflict_target.present?sql="(#{Array(conflict_target).reject(&:blank?).join(', ')}) "sql+="WHERE #{index_predicate} "ifindex_predicatesqlendenddefsql_for_default_conflict_target(primary_key)conflict_target=Array(primary_key).join(', ')"(#{conflict_target}) "ifconflict_target.present?end# Return true if the statement is a duplicate key record errordefduplicate_key_update_error?(exception)# :nodoc:exception.is_a?(ActiveRecord::StatementInvalid)&&exception.to_s.include?('duplicate key')enddefdatabase_versiondefined?(sqlite_version)?sqlite_version:superendend