module ActiveRecord class Base # Copy data to a file passed as a string (the file path) or to lines that are passed to a block def self.pg_copy_to path = nil, options = {} options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) options_string = if options[:format] == :binary "BINARY" else "DELIMITER '#{options[:delimiter]}' CSV #{options[:header] ? 'HEADER' : ''}" end if path raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? connection.execute "COPY (#{self.scoped.to_sql}) TO #{sanitize(path)} WITH #{options_string}" else connection.execute "COPY (#{self.scoped.to_sql}) TO STDOUT WITH #{options_string}" while line = connection.raw_connection.get_copy_data do yield(line) if block_given? end end return self end # Copy all data to a single string def self.pg_copy_to_string options = {} data = '' self.pg_copy_to(nil, options){|l| data << l } if options[:format] == :binary data.force_encoding("ASCII-8BIT") end data end # Copy data from a CSV that can be passed as a string (the file path) or as an IO object. # * You can change the default delimiter passing delimiter: '' in the options hash # * You can map fields from the file to different fields in the table using a map in the options hash # * For further details on usage take a look at the README.md def self.pg_copy_from path_or_io, options = {} options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) options_string = if options[:format] == :binary "BINARY" else "DELIMITER '#{options[:delimiter]}' CSV" end io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io if options[:format] == :binary columns_list = options[:columns] || [] elsif options[:header] line = io.gets columns_list = options[:columns] || line.strip.split(options[:delimiter]) else columns_list = options[:columns] end table = if options[:table] connection.quote_table_name(options[:table]) else quoted_table_name end columns_list = columns_list.map{|c| options[:map][c.to_s] } if options[:map] columns_string = columns_list.size > 0 ? "(\"#{columns_list.join('","')}\")" : "" connection.execute %{COPY #{table} #{columns_string} FROM STDIN #{options_string}} if options[:format] == :binary bytes = 0 begin while line = io.readpartial(10240) connection.raw_connection.put_copy_data line bytes += line.bytesize end rescue EOFError end else while line = io.gets do next if line.strip.size == 0 if block_given? row = line.strip.split(options[:delimiter]) yield(row) line = row.join(options[:delimiter]) + "\n" end connection.raw_connection.put_copy_data line end end connection.raw_connection.put_copy_end end end end