| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
 | module AF83
  class SchemaCloner
    attr_reader :source_schema, :target_schema
    def clone_schema
      assure_schema_preconditons
      create_target_schema
    end
    private
    def adjust_default table_name, column_name, default_val
      changed_default = default_val.sub(%r{\Anextval\('#{source_schema}}, "nextval('#{target_schema}")
      execute "ALTER TABLE #{target_schema}.#{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{changed_default}"
    end
    def adjust_defaults table_name
      pairs = execute <<-EOSQL
        SELECT column_name, column_default
          FROM information_schema.columns
          WHERE table_schema = '#{target_schema}' AND table_name = '#{table_name}' AND
                column_default LIKE 'nextval(''#{source_schema}%::regclass)'
      EOSQL
      pairs.each do | pair |
        adjust_default table_name, pair['column_name'], pair['column_default']
      end
    end
    def alter_sequence sequence_name
      seq_props = execute_get_ostruct( "SELECT * FROM #{source_schema}.#{sequence_name}" )
      cycle_on_off = seq_props.is_cycled == 't' ? '' : 'NO'
      execute <<-EOSQL
        ALTER SEQUENCE #{target_schema}.#{sequence_name}
          INCREMENT BY #{seq_props.increment_by}
          MINVALUE #{seq_props.min_value}
          MAXVALUE #{seq_props.max_value}
          START WITH #{seq_props.start_value}
          RESTART WITH #{seq_props.last_value}
          CACHE #{seq_props.cache_value}
      #{cycle_on_off} CYCLE;
        -- TODO: What is this good for?
        SELECT setval('#{target_schema}.#{sequence_name}', #{seq_props.last_value}, '#{seq_props.is_called}');
      EOSQL
    end
    def assure_schema_preconditons
      raise RuntimeError, "Source Schema #{source_schema} does not exist" unless source
      log 'found', source
    end
    def clone_foreign_key fk_desc
      relname, conname, constraint_def = fk_desc.values_at(*%w[relname conname constraint_def])
      constraint_def = constraint_def.sub(" REFERENCES #{source_schema}.", " REFERENCES #{target_schema}.")
      execute <<-EOSQL
      ALTER TABLE #{target_schema}.#{relname} ADD CONSTRAINT #{conname} #{constraint_def}
      EOSQL
    end
    def clone_foreign_keys
      get_foreign_keys.each(&method(:clone_foreign_key))
    end
    def clone_sequence sequence_name
      create_sequence sequence_name
      alter_sequence sequence_name
    end
    def clone_sequences
      source_sequence_names.each(&method(:clone_sequence))
    end
    def clone_table table_name
      create_table table_name
    end
    def clone_tables
      table_names.each(&method(:clone_table))
    end
    def create_sequence sequence_name
      execute "CREATE SEQUENCE #{target_schema}.#{sequence_name}"
    end
    def create_table table_name
      execute "CREATE TABLE #{target_schema}.#{table_name} (LIKE #{source_schema}.#{table_name} INCLUDING ALL)"
      execute "INSERT INTO #{target_schema}.#{table_name} SELECT * FROM #{source_schema}.#{table_name}"
      adjust_defaults table_name
    end
    def create_target_schema
      execute("CREATE SCHEMA IF NOT EXISTS #{target_schema}")
      clone_sequences
      clone_tables
      clone_foreign_keys
    end
    def execute(str)
      connection.execute(str).to_a
    end
    def execute_get_first(str)
      execute(str).first
    end
    def execute_get_ostruct(str)
      OpenStruct.new(execute_get_first(str))
    end
    def execute_get_values(str)
      execute(str).flat_map(&:values)
    end
    def get_foreign_keys
      execute <<-EOS
        SELECT rn.relname, ct.conname, pg_get_constraintdef(ct.oid) AS constraint_def
          FROM pg_constraint ct JOIN pg_class rn ON rn.oid = ct.conrelid
          WHERE connamespace = #{source['oid']} AND rn.relkind = 'r' AND ct.contype = 'f'
      EOS
    end
    def get_columns(table_name)
    end
    def initialize(source_schema, target_schema)
      @source_schema = source_schema
      @target_schema = target_schema
    end
    def log(*messages)
      messages.each do | message |
        Rails.logger.info "SchemaCloner: #{message.inspect}"
      end
    end
    #
    #  Memvars
    #  -------
    def connection
      @__connection__ ||= ActiveRecord::Base.connection
    end
    def source
      @__source__ ||= execute("SELECT oid FROM pg_namespace WHERE nspname = '#{source_schema}' LIMIT 1").first;
    end
    def source_sequence_names
      @__source_sequence_names__ ||=
        execute_get_values \
        "SELECT sequence_name::text FROM information_schema.sequences WHERE sequence_schema = '#{source_schema}'"
    end
    def source_oid
      @__source_oid__ ||= source["oid"].to_i;
    end
    def table_names
      @__table_names__ ||= execute_get_values \
        "SELECT TABLE_NAME::text FROM information_schema.tables WHERE table_schema = '#{ source_schema }' AND table_type = 'BASE TABLE'"
    end
  end
end
 |