postgres_upsert 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
data/bin/setup CHANGED
@@ -4,6 +4,19 @@ require 'pathname'
4
4
  # path to your application root.
5
5
  APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
6
6
 
7
+
8
+ def silently command
9
+ system command + " > /dev/null 2>&1"
10
+ end
11
+
12
+ def successfully command
13
+ silently command or fail "Error in script: " + command
14
+ end
15
+
16
+ def announce message
17
+ puts "\n== " + message + " =="
18
+ end
19
+
7
20
  Dir.chdir APP_ROOT do
8
21
  # This script is a starting point to setup your application.
9
22
  # Add necessary setup steps to this file:
@@ -17,6 +30,20 @@ Dir.chdir APP_ROOT do
17
30
  # system "cp config/database.yml.sample config/database.yml"
18
31
  # end
19
32
 
33
+ unless silently "ls -A /usr/local/var/postgres"
34
+ announce "initializing postgres db in /usr/local/var/postgres"
35
+ silently "initdb /usr/local/var/postgres"
36
+
37
+ announce "creating superuser 'postgres'. Hope that's cool?"
38
+ silently "createuser -s postgres"
39
+ end
40
+
41
+ unless silently "ls -A ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist"
42
+ announce "setting up Postgres to start on launch"
43
+ silently "ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents"
44
+ silently "launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist"
45
+ end
46
+
20
47
  puts "\n== Preparing database =="
21
48
  system "bin/rake db:setup"
22
49
 
@@ -18,7 +18,4 @@ class Application < Rails::Application
18
18
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
19
19
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
20
20
  # config.i18n.default_locale = :de
21
-
22
- # Do not swallow errors in after_commit/after_rollback callbacks.
23
- config.active_record.raise_in_transactional_callbacks = true
24
21
  end
data/config/database.yml CHANGED
@@ -9,6 +9,8 @@ default: &default
9
9
  host: localhost
10
10
  port: 5432
11
11
  pool: 5
12
+ username: root
13
+ password: root
12
14
 
13
15
  development:
14
16
  <<: *default
@@ -0,0 +1,23 @@
1
+ # SQLite version 3.x
2
+ # gem 'activerecord-jdbcsqlite3-adapter'
3
+ #
4
+ # Configure Using Gemfile
5
+ # gem 'activerecord-jdbcsqlite3-adapter'
6
+ #
7
+ default: &default
8
+ adapter: postgresql
9
+ host: localhost
10
+ port: 5432
11
+ pool: 5
12
+ username: postgres
13
+
14
+ development:
15
+ <<: *default
16
+ database: ar_pg_copy_dev
17
+
18
+ # Warning: The database defined as "test" will be erased and
19
+ # re-generated from your development database when you run "rake".
20
+ # Do not set this db to the same as development or production.
21
+ test:
22
+ <<: *default
23
+ database: ar_pg_copy_test
@@ -1,10 +1,15 @@
1
- class CreateTestTables < ActiveRecord::Migration
1
+ class CreateTestTables < ActiveRecord::Migration[6.0]
2
2
  def change
3
3
  create_table :test_models do |t|
4
4
  t.string :data
5
5
  t.timestamps
6
6
  end
7
7
 
8
+ create_table :test_model_copies do |t|
9
+ t.string :data
10
+ t.timestamps
11
+ end
12
+
8
13
  create_table :three_columns do |t|
9
14
  t.string :data
10
15
  t.string :extra
@@ -1,4 +1,4 @@
1
- class CreateCompositeModelsTable < ActiveRecord::Migration
1
+ class CreateCompositeModelsTable < ActiveRecord::Migration[6.0]
2
2
  def change
3
3
  create_table :composite_key_models do |t|
4
4
  t.integer :comp_key_1
data/db/schema.rb CHANGED
@@ -1,17 +1,16 @@
1
- # encoding: UTF-8
2
1
  # This file is auto-generated from the current state of the database. Instead
3
2
  # of editing this file, please use the migrations feature of Active Record to
4
3
  # incrementally modify your database, and then regenerate this schema definition.
5
4
  #
6
- # Note that this schema.rb definition is the authoritative source for your
7
- # database schema. If you need to create the application database on another
8
- # system, you should be using db:schema:load, not running all the migrations
9
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
- # you'll amass, the slower it'll run and the greater likelihood for issues).
5
+ # This file is the source Rails uses to define your schema when running `rails
6
+ # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
11
10
  #
12
11
  # It's strongly recommended that you check this file into your version control system.
13
12
 
14
- ActiveRecord::Schema.define(version: 20150710162236) do
13
+ ActiveRecord::Schema.define(version: 2015_07_10_162236) do
15
14
 
16
15
  # These are extensions that must be enabled in order to support this database
17
16
  enable_extension "plpgsql"
@@ -19,25 +18,31 @@ ActiveRecord::Schema.define(version: 20150710162236) do
19
18
  create_table "composite_key_models", force: :cascade do |t|
20
19
  t.integer "comp_key_1"
21
20
  t.integer "comp_key_2"
22
- t.string "data"
21
+ t.string "data"
23
22
  end
24
23
 
25
24
  create_table "reserved_word_models", force: :cascade do |t|
26
- t.string "select", limit: 255
27
- t.string "group", limit: 255
25
+ t.string "select"
26
+ t.string "group"
27
+ end
28
+
29
+ create_table "test_model_copies", force: :cascade do |t|
30
+ t.string "data"
31
+ t.datetime "created_at", precision: 6, null: false
32
+ t.datetime "updated_at", precision: 6, null: false
28
33
  end
29
34
 
30
35
  create_table "test_models", force: :cascade do |t|
31
- t.string "data", limit: 255
32
- t.datetime "created_at"
33
- t.datetime "updated_at"
36
+ t.string "data"
37
+ t.datetime "created_at", precision: 6, null: false
38
+ t.datetime "updated_at", precision: 6, null: false
34
39
  end
35
40
 
36
41
  create_table "three_columns", force: :cascade do |t|
37
- t.string "data", limit: 255
38
- t.string "extra", limit: 255
39
- t.datetime "created_at"
40
- t.datetime "updated_at"
42
+ t.string "data"
43
+ t.string "extra"
44
+ t.datetime "created_at", precision: 6, null: false
45
+ t.datetime "updated_at", precision: 6, null: false
41
46
  end
42
47
 
43
48
  end
@@ -1,17 +1,46 @@
1
1
  require 'rubygems'
2
2
  require 'active_record'
3
+ require 'postgres_upsert/read_adapters/active_record_adapter'
4
+ require 'postgres_upsert/read_adapters/file_adapter'
5
+ require 'postgres_upsert/read_adapters/io_adapter'
6
+ require 'postgres_upsert/write_adapters/active_record_adapter'
7
+ require 'postgres_upsert/write_adapters/table_adapter'
3
8
  require 'postgres_upsert/writer'
4
9
  require 'postgres_upsert/table_writer'
10
+ require 'postgres_upsert/model_to_model_adapter'
5
11
  require 'postgres_upsert/result'
6
12
  require 'rails'
7
13
 
8
14
  module PostgresUpsert
9
-
10
15
  class << self
11
- def write class_or_table, path_or_io, options = {}
12
- writer = class_or_table.is_a?(String) ?
13
- TableWriter : Writer
14
- writer.new(class_or_table, path_or_io, options).write
16
+ def write(destination, source, options = {})
17
+ read_adapter = read_adapter(source).new(source, options)
18
+ write_adapter = write_adapter(destination).new(destination, options)
19
+ Writer.new(destination, write_adapter, read_adapter, options).write
20
+ end
21
+
22
+ def read_adapter(source)
23
+ if [StringIO, File].include?(source.class)
24
+ ReadAdapters::IOAdapter
25
+ elsif [String].include?(source.class)
26
+ ReadAdapters::FileAdapter
27
+ elsif source < ActiveRecord::Base
28
+ ReadAdapters::ActiveRecordAdapter
29
+ else
30
+ raise "Source must be a Filename string, StringIO of data, or a ActiveRecord Class."
31
+ end
32
+ end
33
+
34
+ def write_adapter(destination)
35
+ if [String].include?(destination.class)
36
+ WriteAdapters::TableAdapter
37
+ elsif destination <= ActiveRecord::Base
38
+ WriteAdapters::ActiveRecordAdapter
39
+ # elsif source < ActiveRecord::Base && destination < ActiveRecord::Base
40
+ #ModelToModelAdapter
41
+ else
42
+ raise "Destination must be an ActiveRecord class or a table name string"
43
+ end
15
44
  end
16
45
  end
17
- end
46
+ end
@@ -0,0 +1,37 @@
1
+ require 'csv'
2
+
3
+ module PostgresUpsert
4
+ class ModelToModelAdapter
5
+ def initialize(destination_model, source_model, options = {})
6
+ @destination_model = destination_model
7
+ @source_model = source_model
8
+ @options = options
9
+ end
10
+
11
+ def write
12
+ source_table = @source_model.table_name
13
+ source_conn = @source_model.connection.raw_connection
14
+
15
+ to_stdout_sql = "COPY #{source_table} TO STDOUT"
16
+
17
+ csv_string = CSV.generate do |csv|
18
+ csv << @source_model.column_names # CSV header row
19
+ source_conn.copy_data(to_stdout_sql) do
20
+ while (line = source_conn.get_copy_data) do
21
+ csv << line.split("\t")
22
+ end
23
+ end
24
+ end
25
+ io = StringIO.new(csv_string)
26
+ Writer.new(@destination_model, io, @options).write
27
+ end
28
+
29
+ private
30
+
31
+ def get_columns
32
+ # columns_list = @options[:columns]
33
+ # columns_list ||=
34
+ @source.column_names
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module PostgresUpsert
2
+ module ReadAdapters
3
+ class ActiveRecordAdapter
4
+ def initialize(source, options)
5
+ @options = sanitize_options(options)
6
+ @source = source
7
+ end
8
+
9
+ def sanitize_options(options)
10
+ options.slice(
11
+ :columns, :map, :unique_key
12
+ )
13
+ end
14
+
15
+ def continuous_write_enabled
16
+ false
17
+ end
18
+
19
+ def gets(&block)
20
+ batch_size = 1_000
21
+ line = ""
22
+ conn = @source.connection.raw_connection
23
+
24
+ conn.copy_data("COPY #{@source.table_name} TO STDOUT") do
25
+ while (line_read = conn.get_copy_data) do
26
+ line << line_read
27
+ end
28
+ end
29
+ yield line
30
+ end
31
+
32
+ def columns
33
+ @source.column_names
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ module PostgresUpsert
2
+ module ReadAdapters
3
+ class FileAdapter
4
+ def initialize(source, options)
5
+ @options = sanitize_options(options)
6
+ @source = File.open(source, 'r')
7
+ end
8
+
9
+ def sanitize_options(options)
10
+ options.slice(
11
+ :delimiter, :header, :columns, :map, :unique_key
12
+ ).reverse_merge(
13
+ header: true,
14
+ delimiter: ',',
15
+ )
16
+ end
17
+
18
+ def continuous_write_enabled
19
+ true
20
+ end
21
+
22
+ def gets
23
+ @source.gets
24
+ end
25
+
26
+ def columns
27
+ @columns ||= begin
28
+ columns_list = @options[:columns] ? @options[:columns].map(&:to_s) : []
29
+ if @options[:header]
30
+ # if header is present, we need to strip it from io, whether we use it for the columns list or not.
31
+ line = gets
32
+ if columns_list.empty?
33
+ columns_list = line.strip.split(@options[:delimiter])
34
+ end
35
+ end
36
+ columns_list = columns_list.map { |c| @options[:map][c.to_s] } if @options[:map]
37
+ columns_list
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ module PostgresUpsert
2
+ module ReadAdapters
3
+ class IOAdapter
4
+ def initialize(source, options)
5
+ @options = sanitize_options(options)
6
+ @source = source
7
+ end
8
+
9
+ def sanitize_options(options)
10
+ options.slice(
11
+ :delimiter, :header, :columns, :map, :unique_key
12
+ ).reverse_merge(
13
+ header: true,
14
+ delimiter: ',',
15
+ )
16
+ end
17
+
18
+ def continuous_write_enabled
19
+ true
20
+ end
21
+
22
+ def gets
23
+ @source.gets
24
+ end
25
+
26
+ def columns
27
+ @columns ||= begin
28
+ columns_list = @options[:columns] ? @options[:columns].map(&:to_s) : []
29
+ if @options[:header]
30
+ # if header is present, we need to strip it from io, whether we use it for the columns list or not.
31
+ line = gets
32
+ if columns_list.empty?
33
+ columns_list = line.strip.split(@options[:delimiter])
34
+ end
35
+ end
36
+ columns_list = columns_list.map { |c| @options[:map][c.to_s] } if @options[:map]
37
+ columns_list
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,8 +4,8 @@ module PostgresUpsert
4
4
 
5
5
  def initialize(insert_result, update_result, copy_result)
6
6
  @inserted = insert_result ? insert_result.cmd_tuples : 0
7
- @updated = update_result ? update_result.cmd_tuples : 0
8
- @copied = copy_result ? copy_result.cmd_tuples : 0
7
+ @updated = update_result ? update_result.cmd_tuples : 0
8
+ @copied = copy_result ? copy_result.cmd_tuples : 0
9
9
  end
10
10
 
11
11
  def changed_rows
@@ -20,5 +20,4 @@ module PostgresUpsert
20
20
  @updated
21
21
  end
22
22
  end
23
- end
24
-
23
+ end
@@ -1,8 +1,7 @@
1
1
  module PostgresUpsert
2
- # alternate version of PostgresUpsert::Writer which does not rely on AR table information. We
3
- # we can use this model to upsert data into views, or tables not associated to rails models
2
+ # alternate version of PostgresUpsert::Writer which does not rely on AR table information.
3
+ # We can use this model to upsert data into views, or tables not associated to rails models
4
4
  class TableWriter < Writer
5
-
6
5
  def initialize(table_name, source, options = {})
7
6
  @table_name = table_name
8
7
  super(nil, source, options)
@@ -16,7 +15,7 @@ module PostgresUpsert
16
15
 
17
16
  def primary_key
18
17
  @primary_key ||= begin
19
- query = <<-sql
18
+ query = <<-SELECT_KEY
20
19
  SELECT
21
20
  pg_attribute.attname,
22
21
  format_type(pg_attribute.atttypid, pg_attribute.atttypmod)
@@ -27,24 +26,23 @@ module PostgresUpsert
27
26
  pg_attribute.attrelid = pg_class.oid AND
28
27
  pg_attribute.attnum = any(pg_index.indkey)
29
28
  AND indisprimary
30
- sql
29
+ SELECT_KEY
31
30
 
32
31
  pg_result = ActiveRecord::Base.connection.execute query
33
- pg_result.each{ |row| return row['attname'] }
32
+ pg_result.each { |row| return row['attname'] }
34
33
  end
35
34
  end
36
35
 
37
- def column_names
36
+ def destination_columns
38
37
  @column_names ||= begin
39
38
  query = "SELECT * FROM information_schema.columns WHERE TABLE_NAME = '#{@table_name}'"
40
39
  pg_result = ActiveRecord::Base.connection.execute query
41
- pg_result.map{ |row| row['column_name'] }
40
+ pg_result.map { |row| row['column_name'] }
42
41
  end
43
42
  end
44
43
 
45
44
  def quoted_table_name
46
45
  @quoted_table_name ||= ActiveRecord::Base.connection.quote_table_name(@table_name)
47
46
  end
48
-
49
47
  end
50
48
  end