activerecord-postgresql-branched 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +81 -80
- data/lib/active_record/connection_adapters/postgresql/branched/adapter.rb +10 -5
- data/lib/active_record/connection_adapters/postgresql/branched/branch_manager.rb +11 -33
- data/lib/active_record/connection_adapters/postgresql/branched/railtie.rb +22 -18
- data/lib/active_record/connection_adapters/postgresql/branched/schema_dumper.rb +1 -3
- data/lib/active_record/connection_adapters/postgresql/branched/shadow.rb +3 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4824fa89d3e5d0b010916b43302ec81d5190f84ba520a48f9bc8f4b629e30d6f
|
|
4
|
+
data.tar.gz: 423dd436e06d97866c3866a7c139ee932501495c603d7ce2a775211d06fb8190
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67ff1ed660c4b1c442235624b64b4c5c2b1fed7a632957b3aa64b622a7c1340aa1e3e3f3b03368ec63d508131f4d29f5fca43fe440c277a1c7893e95f1f69c34
|
|
7
|
+
data.tar.gz: b9a96791beaca51e65470d50f0a2bc92991c16f432cc47c437259cbc3ef860171e45b2d0e72de35fdae5c7d10cb4edfacb30795d66f753346cc2435f9223fdd2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Remove primary branch concept — every branch gets its own schema equally
|
|
6
|
+
- Add shadow interception for `add_foreign_key`, `remove_foreign_key`, `add_check_constraint`, `remove_check_constraint`, `validate_foreign_key`, `validate_check_constraint`
|
|
7
|
+
- Add `db:branch:console` rake task — opens psql with the branch `search_path`
|
|
8
|
+
- Simplify branch resolution to `PGBRANCH` env var with git fallback
|
|
9
|
+
- Remove `branch_override` config option and `BRANCH` env var
|
|
10
|
+
- `db:branch:prune` accepts `KEEP=branch1,branch2` for explicit control
|
|
11
|
+
- Fix railtie to reuse the adapter's existing BranchManager
|
|
12
|
+
- Fix redundant Shadow instantiation in migration table shadowing
|
|
13
|
+
- Quote schema identifiers consistently in shadow SQL
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
Initial release.
|
data/README.md
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
# activerecord-postgresql-branched
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Built for teams running multiple AI coding agents in parallel, each on its own worktree, all sharing one database.
|
|
3
|
+
A Rails database adapter that gives each branch its own PostgreSQL schema. Your database structure follows your branch, just like your code does.
|
|
6
4
|
|
|
7
5
|
## The problem
|
|
8
6
|
|
|
9
|
-
You
|
|
7
|
+
You're working on a feature branch. You write a migration that adds a column to `users`. You run it. Then you need to switch branches — a review, a hotfix, something urgent.
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
- `agent-1` adds a `bio` column to `users`
|
|
13
|
-
- `agent-2` drops a legacy table and adds indexes
|
|
9
|
+
The other branch knows nothing about that column. But your development database does. `schema.rb` is dirty. Migrations are out of sync. If the other branch has its own migration, you now have two branches' worth of structural changes in one database with no way to tell which change belongs where.
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
You either undo your migration before switching (and redo it when you come back), maintain multiple databases by hand, or just live with the mess. None of these are good.
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
Git solved this problem for code decades ago. This adapter solves it for your database.
|
|
18
14
|
|
|
19
15
|
## Installation
|
|
20
16
|
|
|
@@ -30,82 +26,67 @@ development:
|
|
|
30
26
|
database: myapp_development
|
|
31
27
|
```
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
Set the `PGBRANCH` environment variable to name your branch, or let the adapter detect it from git automatically:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export PGBRANCH=feature/payments
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That's it. No PostgreSQL extensions, no initializers, no extra configuration.
|
|
34
36
|
|
|
35
37
|
## How it works
|
|
36
38
|
|
|
37
|
-
On connection, the adapter
|
|
39
|
+
On connection, the adapter creates a dedicated PostgreSQL schema for the current branch and sets `search_path`:
|
|
38
40
|
|
|
39
41
|
```
|
|
40
|
-
|
|
42
|
+
PGBRANCH=feature/payments
|
|
43
|
+
|
|
41
44
|
schema: branch_feature_payments
|
|
42
45
|
search_path: branch_feature_payments, public
|
|
43
46
|
```
|
|
44
47
|
|
|
45
|
-
New tables go into the branch schema. Queries against
|
|
48
|
+
New tables go into the branch schema. Queries against tables that don't exist in the branch schema fall through to `public` via standard PostgreSQL name resolution. No data copying for reads.
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
### The shadow rule
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
When a migration modifies a table that exists in `public` but not yet in the branch schema, the adapter **shadows** it first — copies the table structure and data into the branch schema, then applies the DDL to the copy:
|
|
50
53
|
|
|
51
|
-
## Agentic workflows
|
|
52
|
-
|
|
53
|
-
Give each agent its own branch identity:
|
|
54
|
-
|
|
55
|
-
```yaml
|
|
56
|
-
# config/database.yml
|
|
57
|
-
development:
|
|
58
|
-
adapter: postgresql_branched
|
|
59
|
-
database: myapp_development
|
|
60
|
-
branch_override: <%= ENV.fetch("AGENT_BRANCH", nil) %>
|
|
61
54
|
```
|
|
55
|
+
add_column :users, :bio, :string
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
AGENT_BRANCH=agent-1 bundle exec rails ...
|
|
67
|
-
AGENT_BRANCH=agent-2 bundle exec rails ...
|
|
57
|
+
1. CREATE TABLE branch_feature_payments.users (LIKE public.users INCLUDING ALL)
|
|
58
|
+
2. INSERT INTO branch_feature_payments.users SELECT * FROM public.users
|
|
59
|
+
3. ALTER TABLE users ADD COLUMN bio VARCHAR
|
|
68
60
|
```
|
|
69
61
|
|
|
70
|
-
|
|
62
|
+
The public table is never touched. Step 3 operates on the shadow because `search_path` resolves `users` to the branch schema first.
|
|
71
63
|
|
|
72
|
-
|
|
64
|
+
This applies to any DDL that modifies an existing table: `add_column`, `remove_column`, `add_index`, `add_foreign_key`, `drop_table`, and so on.
|
|
73
65
|
|
|
74
|
-
|
|
66
|
+
### schema.rb stays clean
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
rails db:branch:prune
|
|
78
|
-
```
|
|
68
|
+
`db:schema:dump` presents a unified view as if everything lived in `public`:
|
|
79
69
|
|
|
80
|
-
|
|
70
|
+
- Branch-local tables appear without schema prefixes
|
|
71
|
+
- Shadowed tables show the branch version (with new columns, dropped indexes, etc.)
|
|
72
|
+
- Public tables the branch hasn't touched are included as normal
|
|
81
73
|
|
|
82
|
-
|
|
74
|
+
The diff for a schema change looks exactly as it always has. Switch branches, and `schema.rb` reflects that branch's database state — not the accumulated mess of every migration you've run lately.
|
|
83
75
|
|
|
84
|
-
|
|
85
|
-
rails db:branch:discard BRANCH=agent-0
|
|
86
|
-
```
|
|
76
|
+
## Switching branches
|
|
87
77
|
|
|
88
|
-
|
|
78
|
+
This is the whole point. When you switch git branches:
|
|
89
79
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
rails db:branch:diff # show objects in this branch vs public
|
|
95
|
-
rails db:branch:prune # drop schemas for branches no longer in git
|
|
96
|
-
```
|
|
80
|
+
- The new branch gets its own schema (created on first connection if needed)
|
|
81
|
+
- Its migrations are tracked independently
|
|
82
|
+
- Tables it hasn't touched fall through to `public`
|
|
83
|
+
- Tables it has modified are isolated in its own schema
|
|
97
84
|
|
|
98
|
-
|
|
85
|
+
Switch back, and your previous branch's state is exactly where you left it.
|
|
99
86
|
|
|
100
|
-
|
|
101
|
-
development:
|
|
102
|
-
adapter: postgresql_branched
|
|
103
|
-
database: myapp_development
|
|
104
|
-
primary_branch: main # default, can be 'master', 'trunk', etc.
|
|
105
|
-
branch_override: agent-0 # bypass git, set branch explicitly
|
|
106
|
-
```
|
|
87
|
+
## Deploying
|
|
107
88
|
|
|
108
|
-
The
|
|
89
|
+
The adapter is for development and test only. Production and staging use the standard `postgresql` adapter — migrations land directly in the database as they always have. The canonical schema advances through your normal deployment process.
|
|
109
90
|
|
|
110
91
|
## Rebasing
|
|
111
92
|
|
|
@@ -115,35 +96,55 @@ rails db:branch:reset
|
|
|
115
96
|
rails db:migrate
|
|
116
97
|
```
|
|
117
98
|
|
|
118
|
-
`db:branch:reset` drops the branch schema.
|
|
99
|
+
`db:branch:reset` drops the branch schema. Queries fall through to `public`. Re-running `db:migrate` reapplies your branch's migrations on top of the current baseline.
|
|
119
100
|
|
|
120
|
-
##
|
|
101
|
+
## Parallel agents
|
|
121
102
|
|
|
122
|
-
The
|
|
103
|
+
The same isolation that helps you switch branches also helps when running multiple AI agents in parallel, each on its own worktree:
|
|
123
104
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
6. `schema.rb` updated and committed
|
|
130
|
-
7. All active branch schemas see updated `public` via fallthrough
|
|
105
|
+
```bash
|
|
106
|
+
PGBRANCH=agent-0 bundle exec rails ...
|
|
107
|
+
PGBRANCH=agent-1 bundle exec rails ...
|
|
108
|
+
PGBRANCH=agent-2 bundle exec rails ...
|
|
109
|
+
```
|
|
131
110
|
|
|
132
|
-
|
|
111
|
+
Each agent gets full isolation. No locks, no coordination. When an agent's work is done:
|
|
133
112
|
|
|
134
|
-
|
|
113
|
+
```bash
|
|
114
|
+
rails db:branch:discard BRANCH=agent-0
|
|
115
|
+
```
|
|
135
116
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
## Rake tasks
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
rails db:branch:reset # drop and recreate current branch schema
|
|
121
|
+
rails db:branch:discard # drop current branch schema (or BRANCH=name)
|
|
122
|
+
rails db:branch:list # list all branch schemas and their sizes
|
|
123
|
+
rails db:branch:diff # show tables in the current branch schema
|
|
124
|
+
rails db:branch:prune # drop stale schemas (KEEP=main,feature/x or auto-detect from git)
|
|
125
|
+
rails db:branch:console # open psql with the branch search_path
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### psql
|
|
129
|
+
|
|
130
|
+
`psql` connects directly to PostgreSQL and knows nothing about branch schemas. Use `db:branch:console` instead — it launches psql with `search_path` set to your branch schema, so you see exactly what your Rails app sees.
|
|
131
|
+
|
|
132
|
+
## Configuration
|
|
133
|
+
|
|
134
|
+
The adapter needs one thing: a branch name. Resolution order:
|
|
135
|
+
|
|
136
|
+
1. `PGBRANCH` environment variable
|
|
137
|
+
2. `git branch --show-current` (automatic fallback)
|
|
138
|
+
|
|
139
|
+
If neither is available, the adapter raises an error.
|
|
140
|
+
|
|
141
|
+
All standard PostgreSQL connection parameters work as normal (`host`, `port`, `username`, `password`, etc.).
|
|
140
142
|
|
|
141
|
-
|
|
143
|
+
**Do not set `schema_search_path` in database.yml** — it conflicts with the adapter's `search_path` management.
|
|
142
144
|
|
|
143
145
|
## Limitations
|
|
144
146
|
|
|
145
|
-
- **Rails +
|
|
146
|
-
- **
|
|
147
|
-
- **
|
|
148
|
-
- **
|
|
149
|
-
- **Sequences on shadowed tables** -- `rename_table` on a shadowed table with serial columns works, but the sequence keeps its original name
|
|
147
|
+
- **Rails + PostgreSQL only** — uses PostgreSQL schemas and `search_path`
|
|
148
|
+
- **Development and test only** — production should use the standard `postgresql` adapter
|
|
149
|
+
- **Non-ActiveRecord DDL** — raw SQL outside of migrations bypasses the shadow rule
|
|
150
|
+
- **Sequences on shadowed tables** — `rename_table` on a shadowed table with serial columns works, but the sequence keeps its original name
|
|
@@ -17,6 +17,12 @@ module ActiveRecord
|
|
|
17
17
|
add_index
|
|
18
18
|
remove_index
|
|
19
19
|
rename_index
|
|
20
|
+
add_foreign_key
|
|
21
|
+
remove_foreign_key
|
|
22
|
+
add_check_constraint
|
|
23
|
+
remove_check_constraint
|
|
24
|
+
validate_foreign_key
|
|
25
|
+
validate_check_constraint
|
|
20
26
|
drop_table
|
|
21
27
|
change_table
|
|
22
28
|
bulk_change_table
|
|
@@ -25,17 +31,17 @@ module ActiveRecord
|
|
|
25
31
|
def initialize(...)
|
|
26
32
|
super
|
|
27
33
|
@branch_manager = BranchManager.new(self, @config)
|
|
28
|
-
@shadow = Shadow.new(self, @branch_manager.branch_schema)
|
|
34
|
+
@shadow = Shadow.new(self, @branch_manager.branch_schema)
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def configure_connection
|
|
32
38
|
super
|
|
33
|
-
@branch_manager.activate
|
|
39
|
+
@branch_manager.activate(@shadow)
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
SHADOW_BEFORE.each do |method|
|
|
37
43
|
define_method(method) do |table_name, *args, **kwargs, &block|
|
|
38
|
-
@shadow
|
|
44
|
+
@shadow.call(table_name)
|
|
39
45
|
super(table_name, *args, **kwargs, &block)
|
|
40
46
|
end
|
|
41
47
|
end
|
|
@@ -45,10 +51,9 @@ module ActiveRecord
|
|
|
45
51
|
# the branch schema. The table and index renames succeed before the
|
|
46
52
|
# sequence rename fails, so we rescue the sequence error.
|
|
47
53
|
def rename_table(table_name, new_name, **options)
|
|
48
|
-
@shadow
|
|
54
|
+
@shadow.call(table_name)
|
|
49
55
|
super
|
|
50
56
|
rescue ActiveRecord::StatementInvalid => e
|
|
51
|
-
raise if @branch_manager.primary_branch?
|
|
52
57
|
raise unless e.cause.is_a?(PG::UndefinedTable)
|
|
53
58
|
end
|
|
54
59
|
|
|
@@ -12,16 +12,10 @@ module ActiveRecord
|
|
|
12
12
|
@branch_schema = self.class.sanitise(@branch)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def activate
|
|
16
|
-
return if primary_branch?
|
|
17
|
-
|
|
15
|
+
def activate(shadow)
|
|
18
16
|
ensure_schema
|
|
19
17
|
set_search_path
|
|
20
|
-
shadow_migration_tables
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def primary_branch?
|
|
24
|
-
@branch == primary_branch_name
|
|
18
|
+
shadow_migration_tables(shadow)
|
|
25
19
|
end
|
|
26
20
|
|
|
27
21
|
def reset
|
|
@@ -32,11 +26,6 @@ module ActiveRecord
|
|
|
32
26
|
|
|
33
27
|
def discard(branch_name = @branch)
|
|
34
28
|
schema = self.class.sanitise(branch_name)
|
|
35
|
-
|
|
36
|
-
if schema == self.class.sanitise(primary_branch_name)
|
|
37
|
-
raise "Cannot discard the primary branch schema"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
29
|
@connection.execute("DROP SCHEMA IF EXISTS #{quote(schema)} CASCADE")
|
|
41
30
|
end
|
|
42
31
|
|
|
@@ -56,8 +45,6 @@ module ActiveRecord
|
|
|
56
45
|
end
|
|
57
46
|
|
|
58
47
|
def diff
|
|
59
|
-
return [] if primary_branch?
|
|
60
|
-
|
|
61
48
|
@connection.select_values(<<~SQL)
|
|
62
49
|
SELECT table_name FROM information_schema.tables
|
|
63
50
|
WHERE table_schema = #{@connection.quote(@branch_schema)}
|
|
@@ -75,14 +62,13 @@ module ActiveRecord
|
|
|
75
62
|
|
|
76
63
|
return schema if schema.bytesize <= MAX_SCHEMA_LENGTH
|
|
77
64
|
|
|
78
|
-
# Truncate and append a short hash to avoid collisions
|
|
79
65
|
hash = Digest::SHA256.hexdigest(slug)[0, 8]
|
|
80
|
-
max_slug = MAX_SCHEMA_LENGTH - PREFIX.bytesize - 9
|
|
66
|
+
max_slug = MAX_SCHEMA_LENGTH - PREFIX.bytesize - 9
|
|
81
67
|
PREFIX + slug[0, max_slug] + "_" + hash
|
|
82
68
|
end
|
|
83
69
|
|
|
84
70
|
def prune(keep: nil)
|
|
85
|
-
|
|
71
|
+
keep_schemas = if keep
|
|
86
72
|
Array(keep).map { |b| self.class.sanitise(b) }.to_set
|
|
87
73
|
else
|
|
88
74
|
git_branches = `git branch --list 2>/dev/null`.lines.map { |l| l.strip.delete_prefix("* ") }
|
|
@@ -97,37 +83,30 @@ module ActiveRecord
|
|
|
97
83
|
WHERE schema_name LIKE 'branch_%'
|
|
98
84
|
SQL
|
|
99
85
|
|
|
100
|
-
stale = all_branch_schemas.reject { |s|
|
|
86
|
+
stale = all_branch_schemas.reject { |s| keep_schemas.include?(s) }
|
|
101
87
|
stale.each do |schema|
|
|
102
88
|
@connection.execute("DROP SCHEMA IF EXISTS #{quote(schema)} CASCADE")
|
|
103
89
|
end
|
|
104
90
|
stale
|
|
105
91
|
end
|
|
106
92
|
|
|
107
|
-
def self.resolve_branch_name
|
|
108
|
-
|
|
109
|
-
ENV["BRANCH"] ||
|
|
110
|
-
ENV["PGBRANCH"] ||
|
|
111
|
-
git_branch
|
|
93
|
+
def self.resolve_branch_name
|
|
94
|
+
ENV["PGBRANCH"] || git_branch
|
|
112
95
|
end
|
|
113
96
|
|
|
114
97
|
private
|
|
115
98
|
|
|
116
99
|
def resolve_branch
|
|
117
|
-
name = self.class.resolve_branch_name
|
|
100
|
+
name = self.class.resolve_branch_name
|
|
118
101
|
|
|
119
102
|
if name.nil? || name.empty?
|
|
120
|
-
raise "Could not determine
|
|
121
|
-
"Set
|
|
103
|
+
raise "Could not determine branch. " \
|
|
104
|
+
"Set the PGBRANCH environment variable."
|
|
122
105
|
end
|
|
123
106
|
|
|
124
107
|
name
|
|
125
108
|
end
|
|
126
109
|
|
|
127
|
-
def primary_branch_name
|
|
128
|
-
(@config[:primary_branch] || "main").to_s
|
|
129
|
-
end
|
|
130
|
-
|
|
131
110
|
def ensure_schema
|
|
132
111
|
@connection.execute("CREATE SCHEMA IF NOT EXISTS #{quote(@branch_schema)}")
|
|
133
112
|
end
|
|
@@ -140,8 +119,7 @@ module ActiveRecord
|
|
|
140
119
|
@connection.schema_search_path = "#{@branch_schema}, public"
|
|
141
120
|
end
|
|
142
121
|
|
|
143
|
-
def shadow_migration_tables
|
|
144
|
-
shadow = Shadow.new(@connection, @branch_schema)
|
|
122
|
+
def shadow_migration_tables(shadow)
|
|
145
123
|
shadow.call(ActiveRecord::Base.schema_migrations_table_name)
|
|
146
124
|
shadow.call(ActiveRecord::Base.internal_metadata_table_name)
|
|
147
125
|
end
|
|
@@ -9,17 +9,11 @@ module ActiveRecord
|
|
|
9
9
|
desc "Drop and recreate the current branch schema"
|
|
10
10
|
task reset: :load_config do
|
|
11
11
|
manager = branch_manager
|
|
12
|
-
|
|
13
|
-
if manager.primary_branch?
|
|
14
|
-
puts "On primary branch (#{manager.branch}), nothing to reset."
|
|
15
|
-
next
|
|
16
|
-
end
|
|
17
|
-
|
|
18
12
|
manager.reset
|
|
19
13
|
puts "Reset branch schema #{manager.branch_schema}. Run db:migrate to reapply branch migrations."
|
|
20
14
|
end
|
|
21
15
|
|
|
22
|
-
desc "Drop
|
|
16
|
+
desc "Drop a branch schema (current branch or BRANCH=name)"
|
|
23
17
|
task discard: :load_config do
|
|
24
18
|
manager = branch_manager
|
|
25
19
|
branch = ENV["BRANCH"] || manager.branch
|
|
@@ -43,9 +37,10 @@ module ActiveRecord
|
|
|
43
37
|
end
|
|
44
38
|
end
|
|
45
39
|
|
|
46
|
-
desc "Drop schemas
|
|
40
|
+
desc "Drop schemas not in KEEP list (or not matching local git branches)"
|
|
47
41
|
task prune: :load_config do
|
|
48
|
-
|
|
42
|
+
keep = ENV["KEEP"]&.split(",")&.map(&:strip)
|
|
43
|
+
pruned = branch_manager.prune(keep: keep)
|
|
49
44
|
|
|
50
45
|
if pruned.empty?
|
|
51
46
|
puts "No stale branch schemas found."
|
|
@@ -55,15 +50,9 @@ module ActiveRecord
|
|
|
55
50
|
end
|
|
56
51
|
end
|
|
57
52
|
|
|
58
|
-
desc "Show objects in the current branch schema
|
|
53
|
+
desc "Show objects in the current branch schema"
|
|
59
54
|
task diff: :load_config do
|
|
60
55
|
manager = branch_manager
|
|
61
|
-
|
|
62
|
-
if manager.primary_branch?
|
|
63
|
-
puts "On primary branch, no diff."
|
|
64
|
-
next
|
|
65
|
-
end
|
|
66
|
-
|
|
67
56
|
tables = manager.diff
|
|
68
57
|
|
|
69
58
|
if tables.empty?
|
|
@@ -73,12 +62,27 @@ module ActiveRecord
|
|
|
73
62
|
tables.each { |t| puts " #{t}" }
|
|
74
63
|
end
|
|
75
64
|
end
|
|
65
|
+
|
|
66
|
+
desc "Open psql with the branch search_path"
|
|
67
|
+
task console: :load_config do
|
|
68
|
+
manager = branch_manager
|
|
69
|
+
config = ActiveRecord::Base.connection_db_config.configuration_hash
|
|
70
|
+
|
|
71
|
+
env = { "PGOPTIONS" => "-c search_path=#{manager.branch_schema},public" }
|
|
72
|
+
args = ["psql"]
|
|
73
|
+
args.push("-h", config[:host].to_s) if config[:host]
|
|
74
|
+
args.push("-p", config[:port].to_s) if config[:port]
|
|
75
|
+
args.push("-U", config[:username].to_s) if config[:username]
|
|
76
|
+
args.push(config[:database].to_s)
|
|
77
|
+
|
|
78
|
+
puts "Connecting to #{config[:database]} as #{manager.branch_schema}..."
|
|
79
|
+
exec(env, *args)
|
|
80
|
+
end
|
|
76
81
|
end
|
|
77
82
|
end
|
|
78
83
|
|
|
79
84
|
def branch_manager
|
|
80
|
-
|
|
81
|
-
BranchManager.new(connection, connection.instance_variable_get(:@config))
|
|
85
|
+
ActiveRecord::Base.lease_connection.branch_manager
|
|
82
86
|
end
|
|
83
87
|
end
|
|
84
88
|
|
|
@@ -8,9 +8,7 @@ module ActiveRecord
|
|
|
8
8
|
private
|
|
9
9
|
|
|
10
10
|
def on_branch?
|
|
11
|
-
@connection.respond_to?(:branch_manager)
|
|
12
|
-
@connection.branch_manager &&
|
|
13
|
-
!@connection.branch_manager.primary_branch?
|
|
11
|
+
@connection.respond_to?(:branch_manager)
|
|
14
12
|
end
|
|
15
13
|
|
|
16
14
|
def initialize(connection, options = {})
|
|
@@ -34,13 +34,14 @@ module ActiveRecord
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def create_shadow(table)
|
|
37
|
+
quoted_branch = @connection.quote_column_name(@branch_schema)
|
|
37
38
|
quoted_table = @connection.quote_column_name(table)
|
|
38
39
|
@connection.execute(<<~SQL)
|
|
39
|
-
CREATE TABLE #{
|
|
40
|
+
CREATE TABLE #{quoted_branch}.#{quoted_table}
|
|
40
41
|
(LIKE public.#{quoted_table} INCLUDING ALL)
|
|
41
42
|
SQL
|
|
42
43
|
@connection.execute(<<~SQL)
|
|
43
|
-
INSERT INTO #{
|
|
44
|
+
INSERT INTO #{quoted_branch}.#{quoted_table}
|
|
44
45
|
SELECT * FROM public.#{quoted_table}
|
|
45
46
|
SQL
|
|
46
47
|
end
|