Class: ActiveRecord::ConnectionAdapters::PostgreSQL::Branched::BranchManager

Inherits:
Object
  • Object
show all
Defined in:
lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb

Constant Summary collapse

MAX_SCHEMA_LENGTH =
63
PREFIX =
"branch_"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection, config) ⇒ BranchManager

Returns a new instance of BranchManager.



8
9
10
11
12
13
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 8

def initialize(connection, config)
  @connection = connection
  @config = config
  @branch = resolve_branch
  @branch_schema = self.class.sanitise(@branch)
end

Instance Attribute Details

#branchObject (readonly)

Returns the value of attribute branch.



6
7
8
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 6

def branch
  @branch
end

#branch_schemaObject (readonly)

Returns the value of attribute branch_schema.



6
7
8
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 6

def branch_schema
  @branch_schema
end

Class Method Details

.git_branchObject



148
149
150
151
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 148

def self.git_branch
  result = `git branch --show-current 2>/dev/null`.strip
  result.empty? ? nil : result
end

.resolve_branch_nameObject



106
107
108
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 106

def self.resolve_branch_name
  ENV["PGBRANCH"] || git_branch
end

.sanitise(branch) ⇒ Object



72
73
74
75
76
77
78
79
80
81
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 72

def self.sanitise(branch)
  slug = branch.downcase.gsub(/[\/\-\.]/, "_").gsub(/[^a-z0-9_]/, "")
  schema = PREFIX + slug

  return schema if schema.bytesize <= MAX_SCHEMA_LENGTH

  hash = Digest::SHA256.hexdigest(slug)[0, 8]
  max_slug = MAX_SCHEMA_LENGTH - PREFIX.bytesize - 9
  PREFIX + slug[0, max_slug] + "_" + hash
end

Instance Method Details

#activate(shadow) ⇒ Object



15
16
17
18
19
20
21
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 15

def activate(shadow)
  return if primary_branch?

  ensure_schema
  set_search_path
  shadow_migration_tables(shadow)
end

#diffObject



58
59
60
61
62
63
64
65
66
67
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 58

def diff
  return [] if primary_branch?

  @connection.select_values(<<~SQL)
    SELECT table_name FROM information_schema.tables
    WHERE table_schema = #{@connection.quote(@branch_schema)}
      AND table_type = 'BASE TABLE'
    ORDER BY table_name
  SQL
end

#discard(branch_name = @branch) ⇒ Object



33
34
35
36
37
38
39
40
41
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 33

def discard(branch_name = @branch)
  schema = self.class.sanitise(branch_name)

  if schema == self.class.sanitise(primary_branch_name)
    raise "Cannot discard the primary branch schema"
  end

  @connection.execute("DROP SCHEMA IF EXISTS #{quote(schema)} CASCADE")
end

#listObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 43

def list
  @connection.select_rows(<<~SQL)
    SELECT s.schema_name,
           COALESCE(pg_size_pretty(sum(pg_total_relation_size(
             quote_ident(t.table_schema) || '.' || quote_ident(t.table_name)
           ))), '0 bytes') AS size
    FROM information_schema.schemata s
    LEFT JOIN information_schema.tables t
      ON t.table_schema = s.schema_name AND t.table_type = 'BASE TABLE'
    WHERE s.schema_name LIKE 'branch_%'
    GROUP BY s.schema_name
    ORDER BY s.schema_name
  SQL
end

#primary_branch?Boolean

Returns:

  • (Boolean)


23
24
25
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 23

def primary_branch?
  @branch == primary_branch_name
end

#prune(keep: nil) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 83

def prune(keep: nil)
  keep_schemas = if keep
    Array(keep).map { |b| self.class.sanitise(b) }.to_set
  else
    git_branches = `git branch --list 2>/dev/null`.lines.map { |l| l.strip.delete_prefix("* ") }
    if git_branches.empty?
      raise "No git branches found. Pass branch names explicitly: prune(keep: ['main', 'feature/x'])"
    end
    git_branches.map { |b| self.class.sanitise(b) }.to_set
  end

  all_branch_schemas = @connection.select_values(<<~SQL)
    SELECT schema_name FROM information_schema.schemata
    WHERE schema_name LIKE 'branch_%'
  SQL

  stale = all_branch_schemas.reject { |s| keep_schemas.include?(s) }
  stale.each do |schema|
    @connection.execute("DROP SCHEMA IF EXISTS #{quote(schema)} CASCADE")
  end
  stale
end

#resetObject



27
28
29
30
31
# File 'lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb', line 27

def reset
  drop_schema
  ensure_schema
  set_search_path
end