pgsync 0.3.8 → 0.3.9
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.
Potentially problematic release.
This version of pgsync might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +8 -0
 - data/README.md +13 -4
 - data/lib/pgsync/client.rb +280 -0
 - data/lib/pgsync/data_source.rb +191 -0
 - data/lib/pgsync/table_list.rb +105 -0
 - data/lib/pgsync/table_sync.rb +239 -0
 - data/lib/pgsync/version.rb +1 -1
 - data/lib/pgsync.rb +7 -659
 - metadata +6 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: f8d5362420e8f65878dcb39bfcb945db069d585f
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: a0d4d2b74ca25148f6ddcb2b4bdfa0bea7ef2c99
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: ed08a53660ea4973932176da8ea2bb1354f457c67dc9dd9cff832a67eb2b785d399f84a1840e21485579c0d595e81657303213d8831ae8ae72cba6cede713dd2
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 4f56fdbec5b5b93b4ff6ffe781edba94076107203e5f620a5708c3487d8a1cd77e8388a06cf0c9ddbd403afefa06832dd648382dfb3dadde9fc857b83ddea089
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -122,16 +122,24 @@ pgsync product:123 
     | 
|
| 
       122 
122 
     | 
    
         | 
| 
       123 
123 
     | 
    
         
             
            ### Schema
         
     | 
| 
       124 
124 
     | 
    
         | 
| 
       125 
     | 
    
         
            -
            Sync schema
         
     | 
| 
      
 125 
     | 
    
         
            +
            Sync schema before the data
         
     | 
| 
       126 
126 
     | 
    
         | 
| 
       127 
127 
     | 
    
         
             
            ```sh
         
     | 
| 
       128 
     | 
    
         
            -
            pgsync --schema- 
     | 
| 
      
 128 
     | 
    
         
            +
            pgsync --schema-first
         
     | 
| 
       129 
129 
     | 
    
         
             
            ```
         
     | 
| 
       130 
130 
     | 
    
         | 
| 
      
 131 
     | 
    
         
            +
            **Note:** This wipes out existing data
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
       131 
133 
     | 
    
         
             
            Specify tables
         
     | 
| 
       132 
134 
     | 
    
         | 
| 
       133 
135 
     | 
    
         
             
            ```sh
         
     | 
| 
       134 
     | 
    
         
            -
            pgsync table1,table2 --schema- 
     | 
| 
      
 136 
     | 
    
         
            +
            pgsync table1,table2 --schema-first
         
     | 
| 
      
 137 
     | 
    
         
            +
            ```
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
            Or just the schema
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            ```sh
         
     | 
| 
      
 142 
     | 
    
         
            +
            pgsync --schema-only
         
     | 
| 
       135 
143 
     | 
    
         
             
            ```
         
     | 
| 
       136 
144 
     | 
    
         | 
| 
       137 
145 
     | 
    
         
             
            ## Sensitive Information
         
     | 
| 
         @@ -161,6 +169,7 @@ Options for replacement are: 
     | 
|
| 
       161 
169 
     | 
    
         
             
            - statement
         
     | 
| 
       162 
170 
     | 
    
         
             
            - unique_email
         
     | 
| 
       163 
171 
     | 
    
         
             
            - unique_phone
         
     | 
| 
      
 172 
     | 
    
         
            +
            - unique_secret
         
     | 
| 
       164 
173 
     | 
    
         
             
            - random_letter
         
     | 
| 
       165 
174 
     | 
    
         
             
            - random_int
         
     | 
| 
       166 
175 
     | 
    
         
             
            - random_date
         
     | 
| 
         @@ -246,7 +255,7 @@ To use master, run: 
     | 
|
| 
       246 
255 
     | 
    
         | 
| 
       247 
256 
     | 
    
         
             
            ```sh
         
     | 
| 
       248 
257 
     | 
    
         
             
            gem install specific_install
         
     | 
| 
       249 
     | 
    
         
            -
            gem specific_install ankane/pgsync
         
     | 
| 
      
 258 
     | 
    
         
            +
            gem specific_install https://github.com/ankane/pgsync.git
         
     | 
| 
       250 
259 
     | 
    
         
             
            ```
         
     | 
| 
       251 
260 
     | 
    
         | 
| 
       252 
261 
     | 
    
         
             
            ## Thanks
         
     | 
| 
         @@ -0,0 +1,280 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module PgSync
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Client
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize(args)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  $stdout.sync = true
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @exit = false
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @arguments, @options = parse_args(args)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @mutex = windows? ? Mutex.new : MultiProcessing::Mutex.new
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                # TODO clean up this mess
         
     | 
| 
      
 11 
     | 
    
         
            +
                def perform
         
     | 
| 
      
 12 
     | 
    
         
            +
                  return if @exit
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  args, opts = @arguments, @options
         
     | 
| 
      
 15 
     | 
    
         
            +
                  [:to, :from, :to_safe, :exclude].each do |opt|
         
     | 
| 
      
 16 
     | 
    
         
            +
                    opts[opt] ||= config[opt.to_s]
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  map_deprecations(args, opts)
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  if opts[:setup]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    setup(db_config_file(args[0]) || config_file || ".pgsync.yml")
         
     | 
| 
      
 22 
     | 
    
         
            +
                  else
         
     | 
| 
      
 23 
     | 
    
         
            +
                    sync(args, opts)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  true
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                protected
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def sync(args, opts)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  start_time = Time.now
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  if args.size > 2
         
     | 
| 
      
 35 
     | 
    
         
            +
                    raise PgSync::Error, "Usage:\n    pgsync [options]"
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  source = DataSource.new(opts[:from])
         
     | 
| 
      
 39 
     | 
    
         
            +
                  raise PgSync::Error, "No source" unless source.exists?
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  destination = DataSource.new(opts[:to])
         
     | 
| 
      
 42 
     | 
    
         
            +
                  raise PgSync::Error, "No destination" unless destination.exists?
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  unless opts[:to_safe] || destination.local?
         
     | 
| 
      
 45 
     | 
    
         
            +
                    raise PgSync::Error, "Danger! Add `to_safe: true` to `.pgsync.yml` if the destination is not localhost or 127.0.0.1"
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  print_description("From", source)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  print_description("To", destination)
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  tables = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 53 
     | 
    
         
            +
                    tables = TableList.new(args, opts, source, config).tables
         
     | 
| 
      
 54 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 55 
     | 
    
         
            +
                    source.close
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  confirm_tables_exist(source, tables, "source")
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  if opts[:list]
         
     | 
| 
      
 61 
     | 
    
         
            +
                    confirm_tables_exist(destination, tables, "destination")
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                    list_items =
         
     | 
| 
      
 64 
     | 
    
         
            +
                      if args[0] == "groups"
         
     | 
| 
      
 65 
     | 
    
         
            +
                        (config["groups"] || {}).keys
         
     | 
| 
      
 66 
     | 
    
         
            +
                      else
         
     | 
| 
      
 67 
     | 
    
         
            +
                        tables.keys
         
     | 
| 
      
 68 
     | 
    
         
            +
                      end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                    pretty_list list_items
         
     | 
| 
      
 71 
     | 
    
         
            +
                  else
         
     | 
| 
      
 72 
     | 
    
         
            +
                    if opts[:schema_first] || opts[:schema_only]
         
     | 
| 
      
 73 
     | 
    
         
            +
                      if opts[:preserve]
         
     | 
| 
      
 74 
     | 
    
         
            +
                        raise PgSync::Error, "Cannot use --preserve with --schema-first or --schema-only"
         
     | 
| 
      
 75 
     | 
    
         
            +
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                      log "* Dumping schema"
         
     | 
| 
      
 78 
     | 
    
         
            +
                      sync_schema(source, destination, tables)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    unless opts[:schema_only]
         
     | 
| 
      
 82 
     | 
    
         
            +
                      confirm_tables_exist(destination, tables, "destination")
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                      in_parallel(tables) do |table, table_opts|
         
     | 
| 
      
 85 
     | 
    
         
            +
                        TableSync.new.sync_with_benchmark(@mutex, config, table, opts.merge(table_opts), source.url, destination.url)
         
     | 
| 
      
 86 
     | 
    
         
            +
                      end
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                    log_completed(start_time)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                def confirm_tables_exist(destination, tables, description)
         
     | 
| 
      
 94 
     | 
    
         
            +
                  tables.keys.each do |table|
         
     | 
| 
      
 95 
     | 
    
         
            +
                    unless destination.table_exists?(table)
         
     | 
| 
      
 96 
     | 
    
         
            +
                      raise PgSync::Error, "Table does not exist in #{description}: #{table}"
         
     | 
| 
      
 97 
     | 
    
         
            +
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 100 
     | 
    
         
            +
                  destination.close
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                def map_deprecations(args, opts)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  command = args[0]
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  case command
         
     | 
| 
      
 107 
     | 
    
         
            +
                  when "setup"
         
     | 
| 
      
 108 
     | 
    
         
            +
                    args.shift
         
     | 
| 
      
 109 
     | 
    
         
            +
                    opts[:setup] = true
         
     | 
| 
      
 110 
     | 
    
         
            +
                    deprecated "Use `psync --setup` instead"
         
     | 
| 
      
 111 
     | 
    
         
            +
                  when "schema"
         
     | 
| 
      
 112 
     | 
    
         
            +
                    args.shift
         
     | 
| 
      
 113 
     | 
    
         
            +
                    opts[:schema_only] = true
         
     | 
| 
      
 114 
     | 
    
         
            +
                    deprecated "Use `psync --schema-only` instead"
         
     | 
| 
      
 115 
     | 
    
         
            +
                  when "tables"
         
     | 
| 
      
 116 
     | 
    
         
            +
                    args.shift
         
     | 
| 
      
 117 
     | 
    
         
            +
                    opts[:tables] = args.shift
         
     | 
| 
      
 118 
     | 
    
         
            +
                    deprecated "Use `pgsync #{opts[:tables]}` instead"
         
     | 
| 
      
 119 
     | 
    
         
            +
                  when "groups"
         
     | 
| 
      
 120 
     | 
    
         
            +
                    args.shift
         
     | 
| 
      
 121 
     | 
    
         
            +
                    opts[:groups] = args.shift
         
     | 
| 
      
 122 
     | 
    
         
            +
                    deprecated "Use `pgsync #{opts[:groups]}` instead"
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  if opts[:where]
         
     | 
| 
      
 126 
     | 
    
         
            +
                    opts[:sql] ||= String.new
         
     | 
| 
      
 127 
     | 
    
         
            +
                    opts[:sql] << " WHERE #{opts[:where]}"
         
     | 
| 
      
 128 
     | 
    
         
            +
                    deprecated "Use `\"WHERE #{opts[:where]}\"` instead"
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  if opts[:limit]
         
     | 
| 
      
 132 
     | 
    
         
            +
                    opts[:sql] ||= String.new
         
     | 
| 
      
 133 
     | 
    
         
            +
                    opts[:sql] << " LIMIT #{opts[:limit]}"
         
     | 
| 
      
 134 
     | 
    
         
            +
                    deprecated "Use `\"LIMIT #{opts[:limit]}\"` instead"
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                def sync_schema(source, destination, tables)
         
     | 
| 
      
 139 
     | 
    
         
            +
                  dump_command = source.dump_command(tables)
         
     | 
| 
      
 140 
     | 
    
         
            +
                  restore_command = destination.restore_command
         
     | 
| 
      
 141 
     | 
    
         
            +
                  system("#{dump_command} | #{restore_command}")
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                def parse_args(args)
         
     | 
| 
      
 145 
     | 
    
         
            +
                  opts = Slop.parse(args) do |o|
         
     | 
| 
      
 146 
     | 
    
         
            +
                    o.banner = %{Usage:
         
     | 
| 
      
 147 
     | 
    
         
            +
                pgsync [options]
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            Options:}
         
     | 
| 
      
 150 
     | 
    
         
            +
                    o.string "-d", "--db", "database"
         
     | 
| 
      
 151 
     | 
    
         
            +
                    o.string "-t", "--tables", "tables to sync"
         
     | 
| 
      
 152 
     | 
    
         
            +
                    o.string "-g", "--groups", "groups to sync"
         
     | 
| 
      
 153 
     | 
    
         
            +
                    o.string "--schemas", "schemas to sync"
         
     | 
| 
      
 154 
     | 
    
         
            +
                    o.string "--from", "source"
         
     | 
| 
      
 155 
     | 
    
         
            +
                    o.string "--to", "destination"
         
     | 
| 
      
 156 
     | 
    
         
            +
                    o.string "--where", "where", help: false
         
     | 
| 
      
 157 
     | 
    
         
            +
                    o.integer "--limit", "limit", help: false
         
     | 
| 
      
 158 
     | 
    
         
            +
                    o.string "--exclude", "exclude tables"
         
     | 
| 
      
 159 
     | 
    
         
            +
                    o.string "--config", "config file"
         
     | 
| 
      
 160 
     | 
    
         
            +
                    # TODO much better name for this option
         
     | 
| 
      
 161 
     | 
    
         
            +
                    o.boolean "--to-safe", "accept danger", default: false
         
     | 
| 
      
 162 
     | 
    
         
            +
                    o.boolean "--debug", "debug", default: false
         
     | 
| 
      
 163 
     | 
    
         
            +
                    o.boolean "--list", "list", default: false
         
     | 
| 
      
 164 
     | 
    
         
            +
                    o.boolean "--overwrite", "overwrite existing rows", default: false, help: false
         
     | 
| 
      
 165 
     | 
    
         
            +
                    o.boolean "--preserve", "preserve existing rows", default: false
         
     | 
| 
      
 166 
     | 
    
         
            +
                    o.boolean "--truncate", "truncate existing rows", default: false
         
     | 
| 
      
 167 
     | 
    
         
            +
                    o.boolean "--schema-first", "schema first", default: false
         
     | 
| 
      
 168 
     | 
    
         
            +
                    o.boolean "--schema-only", "schema only", default: false
         
     | 
| 
      
 169 
     | 
    
         
            +
                    o.boolean "--all-schemas", "all schemas", default: false
         
     | 
| 
      
 170 
     | 
    
         
            +
                    o.boolean "--no-rules", "do not apply data rules", default: false
         
     | 
| 
      
 171 
     | 
    
         
            +
                    o.boolean "--setup", "setup", default: false
         
     | 
| 
      
 172 
     | 
    
         
            +
                    o.boolean "--in-batches", "in batches", default: false, help: false
         
     | 
| 
      
 173 
     | 
    
         
            +
                    o.integer "--batch-size", "batch size", default: 10000, help: false
         
     | 
| 
      
 174 
     | 
    
         
            +
                    o.float "--sleep", "sleep", default: 0, help: false
         
     | 
| 
      
 175 
     | 
    
         
            +
                    o.on "-v", "--version", "print the version" do
         
     | 
| 
      
 176 
     | 
    
         
            +
                      log PgSync::VERSION
         
     | 
| 
      
 177 
     | 
    
         
            +
                      @exit = true
         
     | 
| 
      
 178 
     | 
    
         
            +
                    end
         
     | 
| 
      
 179 
     | 
    
         
            +
                    o.on "-h", "--help", "prints help" do
         
     | 
| 
      
 180 
     | 
    
         
            +
                      log o
         
     | 
| 
      
 181 
     | 
    
         
            +
                      @exit = true
         
     | 
| 
      
 182 
     | 
    
         
            +
                    end
         
     | 
| 
      
 183 
     | 
    
         
            +
                  end
         
     | 
| 
      
 184 
     | 
    
         
            +
                  [opts.arguments, opts.to_hash]
         
     | 
| 
      
 185 
     | 
    
         
            +
                rescue Slop::Error => e
         
     | 
| 
      
 186 
     | 
    
         
            +
                  raise PgSync::Error, e.message
         
     | 
| 
      
 187 
     | 
    
         
            +
                end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                def config
         
     | 
| 
      
 190 
     | 
    
         
            +
                  @config ||= begin
         
     | 
| 
      
 191 
     | 
    
         
            +
                    if config_file
         
     | 
| 
      
 192 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 193 
     | 
    
         
            +
                        YAML.load_file(config_file) || {}
         
     | 
| 
      
 194 
     | 
    
         
            +
                      rescue Psych::SyntaxError => e
         
     | 
| 
      
 195 
     | 
    
         
            +
                        raise PgSync::Error, e.message
         
     | 
| 
      
 196 
     | 
    
         
            +
                      end
         
     | 
| 
      
 197 
     | 
    
         
            +
                    else
         
     | 
| 
      
 198 
     | 
    
         
            +
                      {}
         
     | 
| 
      
 199 
     | 
    
         
            +
                    end
         
     | 
| 
      
 200 
     | 
    
         
            +
                  end
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                def setup(config_file)
         
     | 
| 
      
 204 
     | 
    
         
            +
                  if File.exist?(config_file)
         
     | 
| 
      
 205 
     | 
    
         
            +
                    raise PgSync::Error, "#{config_file} exists."
         
     | 
| 
      
 206 
     | 
    
         
            +
                  else
         
     | 
| 
      
 207 
     | 
    
         
            +
                    FileUtils.cp(File.dirname(__FILE__) + "/../../config.yml", config_file)
         
     | 
| 
      
 208 
     | 
    
         
            +
                    log "#{config_file} created. Add your database credentials."
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
                end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                def db_config_file(db)
         
     | 
| 
      
 213 
     | 
    
         
            +
                  return unless db
         
     | 
| 
      
 214 
     | 
    
         
            +
                  ".pgsync-#{db}.yml"
         
     | 
| 
      
 215 
     | 
    
         
            +
                end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                def print_description(prefix, source)
         
     | 
| 
      
 218 
     | 
    
         
            +
                  log "#{prefix}: #{source.uri.path.sub(/\A\//, '')} on #{source.uri.host}:#{source.uri.port}"
         
     | 
| 
      
 219 
     | 
    
         
            +
                end
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                def search_tree(file)
         
     | 
| 
      
 222 
     | 
    
         
            +
                  path = Dir.pwd
         
     | 
| 
      
 223 
     | 
    
         
            +
                  # prevent infinite loop
         
     | 
| 
      
 224 
     | 
    
         
            +
                  20.times do
         
     | 
| 
      
 225 
     | 
    
         
            +
                    absolute_file = File.join(path, file)
         
     | 
| 
      
 226 
     | 
    
         
            +
                    if File.exist?(absolute_file)
         
     | 
| 
      
 227 
     | 
    
         
            +
                      break absolute_file
         
     | 
| 
      
 228 
     | 
    
         
            +
                    end
         
     | 
| 
      
 229 
     | 
    
         
            +
                    path = File.dirname(path)
         
     | 
| 
      
 230 
     | 
    
         
            +
                    break if path == "/"
         
     | 
| 
      
 231 
     | 
    
         
            +
                  end
         
     | 
| 
      
 232 
     | 
    
         
            +
                end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                def config_file
         
     | 
| 
      
 235 
     | 
    
         
            +
                  return @config_file if instance_variable_defined?(:@config_file)
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                  @config_file =
         
     | 
| 
      
 238 
     | 
    
         
            +
                    search_tree(
         
     | 
| 
      
 239 
     | 
    
         
            +
                      if @options[:db]
         
     | 
| 
      
 240 
     | 
    
         
            +
                        db_config_file(@options[:db])
         
     | 
| 
      
 241 
     | 
    
         
            +
                      else
         
     | 
| 
      
 242 
     | 
    
         
            +
                        @options[:config] || ".pgsync.yml"
         
     | 
| 
      
 243 
     | 
    
         
            +
                      end
         
     | 
| 
      
 244 
     | 
    
         
            +
                    )
         
     | 
| 
      
 245 
     | 
    
         
            +
                end
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
                def log(message = nil)
         
     | 
| 
      
 248 
     | 
    
         
            +
                  $stderr.puts message
         
     | 
| 
      
 249 
     | 
    
         
            +
                end
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                def in_parallel(tables, &block)
         
     | 
| 
      
 252 
     | 
    
         
            +
                  if @options[:debug] || @options[:in_batches]
         
     | 
| 
      
 253 
     | 
    
         
            +
                    tables.each(&block)
         
     | 
| 
      
 254 
     | 
    
         
            +
                  else
         
     | 
| 
      
 255 
     | 
    
         
            +
                    options = {}
         
     | 
| 
      
 256 
     | 
    
         
            +
                    options[:in_threads] = 4 if windows?
         
     | 
| 
      
 257 
     | 
    
         
            +
                    Parallel.each(tables, options, &block)
         
     | 
| 
      
 258 
     | 
    
         
            +
                  end
         
     | 
| 
      
 259 
     | 
    
         
            +
                end
         
     | 
| 
      
 260 
     | 
    
         
            +
             
     | 
| 
      
 261 
     | 
    
         
            +
                def pretty_list(items)
         
     | 
| 
      
 262 
     | 
    
         
            +
                  items.each do |item|
         
     | 
| 
      
 263 
     | 
    
         
            +
                    log item
         
     | 
| 
      
 264 
     | 
    
         
            +
                  end
         
     | 
| 
      
 265 
     | 
    
         
            +
                end
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                def deprecated(message)
         
     | 
| 
      
 268 
     | 
    
         
            +
                  log "[DEPRECATED] #{message}"
         
     | 
| 
      
 269 
     | 
    
         
            +
                end
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
                def log_completed(start_time)
         
     | 
| 
      
 272 
     | 
    
         
            +
                  time = Time.now - start_time
         
     | 
| 
      
 273 
     | 
    
         
            +
                  log "Completed in #{time.round(1)}s"
         
     | 
| 
      
 274 
     | 
    
         
            +
                end
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
                def windows?
         
     | 
| 
      
 277 
     | 
    
         
            +
                  Gem.win_platform?
         
     | 
| 
      
 278 
     | 
    
         
            +
                end
         
     | 
| 
      
 279 
     | 
    
         
            +
              end
         
     | 
| 
      
 280 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,191 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module PgSync
         
     | 
| 
      
 2 
     | 
    
         
            +
              class DataSource
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :url
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(source)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @url = resolve_url(source)
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def exists?
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @url && @url.size > 0
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def local?
         
     | 
| 
      
 14 
     | 
    
         
            +
                  %w(localhost 127.0.0.1).include?(uri.host)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def uri
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @uri ||= begin
         
     | 
| 
      
 19 
     | 
    
         
            +
                    uri = URI.parse(@url)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    uri.scheme ||= "postgres"
         
     | 
| 
      
 21 
     | 
    
         
            +
                    uri.host ||= "localhost"
         
     | 
| 
      
 22 
     | 
    
         
            +
                    uri.port ||= 5432
         
     | 
| 
      
 23 
     | 
    
         
            +
                    uri.path = "/#{uri.path}" if uri.path && uri.path[0] != "/"
         
     | 
| 
      
 24 
     | 
    
         
            +
                    uri
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def schema
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @schema ||= CGI.parse(uri.query.to_s)["schema"][0]
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def tables
         
     | 
| 
      
 33 
     | 
    
         
            +
                  query = "SELECT schemaname, tablename FROM pg_catalog.pg_tables WHERE schemaname NOT IN ('information_schema', 'pg_catalog') ORDER BY 1, 2"
         
     | 
| 
      
 34 
     | 
    
         
            +
                  execute(query).map { |row| "#{row["schemaname"]}.#{row["tablename"]}" }
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def table_exists?(table)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  query = "SELECT 1 FROM information_schema.tables WHERE table_schema = $1 AND table_name = $2"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  execute(query, table.split(".", 2)).size > 0
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                def close
         
     | 
| 
      
 43 
     | 
    
         
            +
                  if @conn
         
     | 
| 
      
 44 
     | 
    
         
            +
                    conn.close
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @conn = nil
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def to_url
         
     | 
| 
      
 50 
     | 
    
         
            +
                  uri = self.uri.dup
         
     | 
| 
      
 51 
     | 
    
         
            +
                  uri.query = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                  uri.to_s
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                def columns(table)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  query = "SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2"
         
     | 
| 
      
 57 
     | 
    
         
            +
                  execute(query, table.split(".", 2)).map { |row| row["column_name"] }
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                def sequences(table, columns)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  execute("SELECT #{columns.map { |f| "pg_get_serial_sequence(#{escape("#{quote_ident_full(table)}")}, #{escape(f)}) AS #{f}" }.join(", ")}")[0].values.compact
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def max_id(table, primary_key, sql_clause = nil)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  execute("SELECT MAX(#{quote_ident(primary_key)}) FROM #{quote_ident_full(table)}#{sql_clause}")[0]["max"].to_i
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def min_id(table, primary_key, sql_clause = nil)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  execute("SELECT MIN(#{quote_ident(primary_key)}) FROM #{quote_ident_full(table)}#{sql_clause}")[0]["min"].to_i
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def last_value(seq)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  execute("select last_value from #{seq}")[0]["last_value"]
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def truncate(table)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  execute("TRUNCATE #{quote_ident_full(table)} CASCADE")
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                # http://stackoverflow.com/a/20537829
         
     | 
| 
      
 81 
     | 
    
         
            +
                def primary_key(table)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  query = <<-SQL
         
     | 
| 
      
 83 
     | 
    
         
            +
                    SELECT
         
     | 
| 
      
 84 
     | 
    
         
            +
                      pg_attribute.attname,
         
     | 
| 
      
 85 
     | 
    
         
            +
                      format_type(pg_attribute.atttypid, pg_attribute.atttypmod)
         
     | 
| 
      
 86 
     | 
    
         
            +
                    FROM
         
     | 
| 
      
 87 
     | 
    
         
            +
                      pg_index, pg_class, pg_attribute, pg_namespace
         
     | 
| 
      
 88 
     | 
    
         
            +
                    WHERE
         
     | 
| 
      
 89 
     | 
    
         
            +
                      pg_class.oid = $2::regclass AND
         
     | 
| 
      
 90 
     | 
    
         
            +
                      indrelid = pg_class.oid AND
         
     | 
| 
      
 91 
     | 
    
         
            +
                      nspname = $1 AND
         
     | 
| 
      
 92 
     | 
    
         
            +
                      pg_class.relnamespace = pg_namespace.oid AND
         
     | 
| 
      
 93 
     | 
    
         
            +
                      pg_attribute.attrelid = pg_class.oid AND
         
     | 
| 
      
 94 
     | 
    
         
            +
                      pg_attribute.attnum = any(pg_index.indkey) AND
         
     | 
| 
      
 95 
     | 
    
         
            +
                      indisprimary
         
     | 
| 
      
 96 
     | 
    
         
            +
                  SQL
         
     | 
| 
      
 97 
     | 
    
         
            +
                  row = execute(query, [table.split(".", 2)[0], quote_ident_full(table)])[0]
         
     | 
| 
      
 98 
     | 
    
         
            +
                  row && row["attname"]
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                # borrowed from
         
     | 
| 
      
 102 
     | 
    
         
            +
                # ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver
         
     | 
| 
      
 103 
     | 
    
         
            +
                def conn
         
     | 
| 
      
 104 
     | 
    
         
            +
                  @conn ||= begin
         
     | 
| 
      
 105 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 106 
     | 
    
         
            +
                      uri_parser = URI::Parser.new
         
     | 
| 
      
 107 
     | 
    
         
            +
                      config = {
         
     | 
| 
      
 108 
     | 
    
         
            +
                          host: uri.host,
         
     | 
| 
      
 109 
     | 
    
         
            +
                          port: uri.port,
         
     | 
| 
      
 110 
     | 
    
         
            +
                          dbname: uri.path.sub(/\A\//, ""),
         
     | 
| 
      
 111 
     | 
    
         
            +
                          user: uri.user,
         
     | 
| 
      
 112 
     | 
    
         
            +
                          password: uri.password,
         
     | 
| 
      
 113 
     | 
    
         
            +
                          connect_timeout: 3
         
     | 
| 
      
 114 
     | 
    
         
            +
                      }.reject { |_, value| value.to_s.empty? }
         
     | 
| 
      
 115 
     | 
    
         
            +
                      config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a?(String) }
         
     | 
| 
      
 116 
     | 
    
         
            +
                      conn = PG::Connection.new(config)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    rescue PG::ConnectionBad => e
         
     | 
| 
      
 118 
     | 
    
         
            +
                      log
         
     | 
| 
      
 119 
     | 
    
         
            +
                      raise PgSync::Error, e.message
         
     | 
| 
      
 120 
     | 
    
         
            +
                    end
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                def dump_command(tables)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  tables = tables.keys.map { |t| "-t #{Shellwords.escape(quote_ident_full(t))}" }.join(" ")
         
     | 
| 
      
 126 
     | 
    
         
            +
                  dump_command = "pg_dump -Fc --verbose --schema-only --no-owner --no-acl #{tables} #{to_url}"
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                def restore_command
         
     | 
| 
      
 130 
     | 
    
         
            +
                  psql_version = Gem::Version.new(`psql --version`.lines[0].chomp.split(" ")[-1].sub(/beta\d/, ""))
         
     | 
| 
      
 131 
     | 
    
         
            +
                  if_exists = psql_version >= Gem::Version.new("9.4.0")
         
     | 
| 
      
 132 
     | 
    
         
            +
                  restore_command = "pg_restore --verbose --no-owner --no-acl --clean #{if_exists ? "--if-exists" : nil} -d #{to_url}"
         
     | 
| 
      
 133 
     | 
    
         
            +
                end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                def fully_resolve_tables(tables)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  no_schema_tables = {}
         
     | 
| 
      
 137 
     | 
    
         
            +
                  search_path_index = Hash[search_path.map.with_index.to_a]
         
     | 
| 
      
 138 
     | 
    
         
            +
                  self.tables.group_by { |t| t.split(".", 2)[-1] }.each do |group, t2|
         
     | 
| 
      
 139 
     | 
    
         
            +
                    no_schema_tables[group] = t2.sort_by { |t| [search_path_index[t.split(".", 2)[0]] || 1000000, t] }[0]
         
     | 
| 
      
 140 
     | 
    
         
            +
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                  Hash[tables.map { |k, v| [no_schema_tables[k] || k, v] }]
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                def search_path
         
     | 
| 
      
 146 
     | 
    
         
            +
                  execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",")
         
     | 
| 
      
 147 
     | 
    
         
            +
                end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                private
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                def quote_ident_full(ident)
         
     | 
| 
      
 152 
     | 
    
         
            +
                  ident.split(".", 2).map { |v| quote_ident(v) }.join(".")
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                def execute(query, params = [])
         
     | 
| 
      
 156 
     | 
    
         
            +
                  conn.exec_params(query, params).to_a
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                def log(message = nil)
         
     | 
| 
      
 160 
     | 
    
         
            +
                  $stderr.puts message
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                def quote_ident(value)
         
     | 
| 
      
 164 
     | 
    
         
            +
                  PG::Connection.quote_ident(value)
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def escape(value)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  if value.is_a?(String)
         
     | 
| 
      
 169 
     | 
    
         
            +
                    "'#{quote_string(value)}'"
         
     | 
| 
      
 170 
     | 
    
         
            +
                  else
         
     | 
| 
      
 171 
     | 
    
         
            +
                    value
         
     | 
| 
      
 172 
     | 
    
         
            +
                  end
         
     | 
| 
      
 173 
     | 
    
         
            +
                end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                # activerecord
         
     | 
| 
      
 176 
     | 
    
         
            +
                def quote_string(s)
         
     | 
| 
      
 177 
     | 
    
         
            +
                  s.gsub(/\\/, '\&\&').gsub(/'/, "''")
         
     | 
| 
      
 178 
     | 
    
         
            +
                end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                def resolve_url(source)
         
     | 
| 
      
 181 
     | 
    
         
            +
                  if source && source[0..1] == "$(" && source[-1] == ")"
         
     | 
| 
      
 182 
     | 
    
         
            +
                    command = source[2..-2]
         
     | 
| 
      
 183 
     | 
    
         
            +
                    source = `#{command}`.chomp
         
     | 
| 
      
 184 
     | 
    
         
            +
                    unless $?.success?
         
     | 
| 
      
 185 
     | 
    
         
            +
                      raise PgSync::Error, "Command exited with non-zero status:\n#{command}"
         
     | 
| 
      
 186 
     | 
    
         
            +
                    end
         
     | 
| 
      
 187 
     | 
    
         
            +
                  end
         
     | 
| 
      
 188 
     | 
    
         
            +
                  source
         
     | 
| 
      
 189 
     | 
    
         
            +
                end
         
     | 
| 
      
 190 
     | 
    
         
            +
              end
         
     | 
| 
      
 191 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,105 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module PgSync
         
     | 
| 
      
 2 
     | 
    
         
            +
              class TableList
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :args, :opts, :source, :config
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(args, options, source, config)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @args = args
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @opts = options
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @source = source
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @config = config
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def tables
         
     | 
| 
      
 13 
     | 
    
         
            +
                  tables = nil
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  if opts[:groups]
         
     | 
| 
      
 16 
     | 
    
         
            +
                    tables ||= Hash.new { |hash, key| hash[key] = {} }
         
     | 
| 
      
 17 
     | 
    
         
            +
                    specified_groups = to_arr(opts[:groups])
         
     | 
| 
      
 18 
     | 
    
         
            +
                    specified_groups.map do |tag|
         
     | 
| 
      
 19 
     | 
    
         
            +
                      group, id = tag.split(":", 2)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      if (t = (config["groups"] || {})[group])
         
     | 
| 
      
 21 
     | 
    
         
            +
                        add_tables(tables, t, id, args[1])
         
     | 
| 
      
 22 
     | 
    
         
            +
                      else
         
     | 
| 
      
 23 
     | 
    
         
            +
                        raise PgSync::Error, "Group not found: #{group}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                      end
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  if opts[:tables]
         
     | 
| 
      
 29 
     | 
    
         
            +
                    tables ||= Hash.new { |hash, key| hash[key] = {} }
         
     | 
| 
      
 30 
     | 
    
         
            +
                    to_arr(opts[:tables]).each do |tag|
         
     | 
| 
      
 31 
     | 
    
         
            +
                      table, id = tag.split(":", 2)
         
     | 
| 
      
 32 
     | 
    
         
            +
                      add_table(tables, table, id, args[1])
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  if args[0]
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # could be a group, table, or mix
         
     | 
| 
      
 38 
     | 
    
         
            +
                    tables ||= Hash.new { |hash, key| hash[key] = {} }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    specified_groups = to_arr(args[0])
         
     | 
| 
      
 40 
     | 
    
         
            +
                    specified_groups.map do |tag|
         
     | 
| 
      
 41 
     | 
    
         
            +
                      group, id = tag.split(":", 2)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      if (t = (config["groups"] || {})[group])
         
     | 
| 
      
 43 
     | 
    
         
            +
                        add_tables(tables, t, id, args[1])
         
     | 
| 
      
 44 
     | 
    
         
            +
                      else
         
     | 
| 
      
 45 
     | 
    
         
            +
                        add_table(tables, group, id, args[1])
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  tables ||= begin
         
     | 
| 
      
 51 
     | 
    
         
            +
                    exclude = to_arr(opts[:exclude])
         
     | 
| 
      
 52 
     | 
    
         
            +
                    exclude = source.fully_resolve_tables(exclude).keys if exclude.any?
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    tabs = source.tables
         
     | 
| 
      
 55 
     | 
    
         
            +
                    unless opts[:all_schemas]
         
     | 
| 
      
 56 
     | 
    
         
            +
                      schemas = Set.new(opts[:schemas] ? to_arr(opts[:schemas]) : [source.schema || "public"])
         
     | 
| 
      
 57 
     | 
    
         
            +
                      tabs.select! { |t| schemas.include?(t.split(".", 2)[0]) }
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    Hash[(tabs - exclude).map { |k| [k, {}] }]
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  source.fully_resolve_tables(tables)
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                private
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def to_arr(value)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  if value.is_a?(Array)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    value
         
     | 
| 
      
 71 
     | 
    
         
            +
                  else
         
     | 
| 
      
 72 
     | 
    
         
            +
                    # Split by commas, but don't use commas inside double quotes
         
     | 
| 
      
 73 
     | 
    
         
            +
                    # http://stackoverflow.com/questions/21105360/regex-find-comma-not-inside-quotes
         
     | 
| 
      
 74 
     | 
    
         
            +
                    value.to_s.split(/(?!\B"[^"]*),(?![^"]*"\B)/)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                def add_tables(tables, t, id, boom)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  t.each do |table|
         
     | 
| 
      
 80 
     | 
    
         
            +
                    sql = nil
         
     | 
| 
      
 81 
     | 
    
         
            +
                    if table.is_a?(Array)
         
     | 
| 
      
 82 
     | 
    
         
            +
                      table, sql = table
         
     | 
| 
      
 83 
     | 
    
         
            +
                    end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    add_table(tables, table, id, boom || sql)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def add_table(tables, table, id, boom, wildcard = false)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  if table.include?("*") && !wildcard
         
     | 
| 
      
 90 
     | 
    
         
            +
                    regex = Regexp.new('\A' + Regexp.escape(table).gsub('\*','[^\.]*') + '\z')
         
     | 
| 
      
 91 
     | 
    
         
            +
                    t2 = source.tables.select { |t| regex.match(t) }
         
     | 
| 
      
 92 
     | 
    
         
            +
                    t2.each do |tab|
         
     | 
| 
      
 93 
     | 
    
         
            +
                      add_table(tables, tab, id, boom, true)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
                  else
         
     | 
| 
      
 96 
     | 
    
         
            +
                    tables[table] = {}
         
     | 
| 
      
 97 
     | 
    
         
            +
                    tables[table][:sql] = boom.gsub("{id}", cast(id)).gsub("{1}", cast(id)) if boom
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                def cast(value)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  value.to_s.gsub(/\A\"|\"\z/, '')
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
            end
         
     |