reble 0.0.1__tar.gz
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.
- reble-0.0.1/LICENSE +151 -0
- reble-0.0.1/PKG-INFO +360 -0
- reble-0.0.1/README.md +335 -0
- reble-0.0.1/pyproject.toml +40 -0
- reble-0.0.1/setup.cfg +4 -0
- reble-0.0.1/src/reble/__init__.py +1 -0
- reble-0.0.1/src/reble/branches.py +320 -0
- reble-0.0.1/src/reble/cli.py +264 -0
- reble-0.0.1/src/reble/config.py +57 -0
- reble-0.0.1/src/reble/diffing.py +129 -0
- reble-0.0.1/src/reble/errors.py +14 -0
- reble-0.0.1/src/reble/runner.py +202 -0
- reble-0.0.1/src/reble/scaffold.py +67 -0
- reble-0.0.1/src/reble/state.py +157 -0
- reble-0.0.1/src/reble.egg-info/PKG-INFO +360 -0
- reble-0.0.1/src/reble.egg-info/SOURCES.txt +23 -0
- reble-0.0.1/src/reble.egg-info/dependency_links.txt +1 -0
- reble-0.0.1/src/reble.egg-info/entry_points.txt +2 -0
- reble-0.0.1/src/reble.egg-info/requires.txt +8 -0
- reble-0.0.1/src/reble.egg-info/top_level.txt +1 -0
- reble-0.0.1/tests/test_branch_first.py +93 -0
- reble-0.0.1/tests/test_branches.py +78 -0
- reble-0.0.1/tests/test_diff_promote.py +90 -0
- reble-0.0.1/tests/test_inference.py +76 -0
- reble-0.0.1/tests/test_runner.py +112 -0
reble-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined in Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
25
|
+
permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or Object
|
|
37
|
+
form, made available under the License, as indicated by a copyright
|
|
38
|
+
notice that is included in or attached to the work.
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original Work and any Derivative Works thereof, that is intentionally
|
|
50
|
+
submitted to, or received by, Licensor for inclusion in the Work
|
|
51
|
+
by the copyright owner or by an individual or Legal Entity authorized
|
|
52
|
+
to submit on behalf of the copyright owner. For the purposes of this
|
|
53
|
+
definition, "submitted" means any form of electronic, verbal, or
|
|
54
|
+
written communication sent to the Licensor or its representatives,
|
|
55
|
+
such as but not limited to communication on electronic mailing lists,
|
|
56
|
+
source code control systems, and issue tracking systems that are
|
|
57
|
+
managed by, or on behalf of, the Licensor for the purpose of
|
|
58
|
+
discussing and improving the Work, but excluding communication
|
|
59
|
+
that is conspicuously marked or otherwise designated in writing by
|
|
60
|
+
the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any Legal Entity on behalf of
|
|
63
|
+
whom a Contribution has been received by Licensor and subsequently
|
|
64
|
+
incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted.
|
|
82
|
+
|
|
83
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
84
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
85
|
+
modifications, and in Source or Object form, provided that You
|
|
86
|
+
meet the following conditions:
|
|
87
|
+
|
|
88
|
+
(a) You must give any other recipients of the Work or
|
|
89
|
+
Derivative Works a copy of this License; and
|
|
90
|
+
|
|
91
|
+
(b) You must cause any modified files to carry prominent notices
|
|
92
|
+
stating that You changed the files; and
|
|
93
|
+
|
|
94
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
95
|
+
that You distribute, all copyright, patent, trademark, and
|
|
96
|
+
attribution notices from the Source form of the Work,
|
|
97
|
+
excluding those notices that do not pertain to any part of
|
|
98
|
+
the Derivative Works; and
|
|
99
|
+
|
|
100
|
+
(d) If the Work includes a "NOTICE" text file, then any
|
|
101
|
+
Derivative Works that You distribute must include a readable
|
|
102
|
+
copy of the attribution notices contained
|
|
103
|
+
within such NOTICE file.
|
|
104
|
+
|
|
105
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
106
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
107
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
108
|
+
this License, without any additional terms or conditions.
|
|
109
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
110
|
+
the terms of any separate license agreement you may have executed
|
|
111
|
+
with Licensor regarding such Contribution.
|
|
112
|
+
|
|
113
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
114
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
115
|
+
except as required for reasonable and customary use in describing the
|
|
116
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
117
|
+
|
|
118
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
119
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
120
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
121
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
122
|
+
implied, including, without limitation, any warranties or conditions
|
|
123
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
124
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
125
|
+
appropriateness of using or redistributing the Work and assume any
|
|
126
|
+
risks associated with Your exercise of permissions under this License.
|
|
127
|
+
|
|
128
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
129
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
130
|
+
unless required by applicable law (such as deliberate and grossly
|
|
131
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
132
|
+
liable to You for damages, including any direct, indirect, special,
|
|
133
|
+
incidental, or consequential damages of any character arising as a
|
|
134
|
+
result of this License or out of the use or inability to use the
|
|
135
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
136
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
137
|
+
other commercial damages or losses), even if such Contributor
|
|
138
|
+
has been advised of the possibility of such damages.
|
|
139
|
+
|
|
140
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
141
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
142
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
143
|
+
or other liability obligations and/or rights consistent with this
|
|
144
|
+
License. However, in accepting such obligations, You may act only
|
|
145
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
146
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
147
|
+
defend, and hold each Contributor harmless for any liability
|
|
148
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
149
|
+
of your accepting any such warranty or additional liability.
|
|
150
|
+
|
|
151
|
+
END OF TERMS AND CONDITIONS
|
reble-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reble
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Your models are just SQL files. Branch your warehouse like you branch your code — local-first analytics on DuckDB + Iceberg + SQLGlot.
|
|
5
|
+
Author: Reble contributors
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/satya1395/reble
|
|
8
|
+
Keywords: data-engineering,iceberg,duckdb,sqlglot,branching,lakehouse,data-diff,analytics
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Database
|
|
13
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pyiceberg[pyarrow,sql-sqlite]==0.11.1
|
|
18
|
+
Requires-Dist: duckdb==1.5.5
|
|
19
|
+
Requires-Dist: sqlmesh==0.236.1
|
|
20
|
+
Requires-Dist: click>=8.1
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Reble
|
|
27
|
+
|
|
28
|
+
**Your models are just SQL files. Branch your warehouse like you branch your code.**
|
|
29
|
+
|
|
30
|
+
```sql
|
|
31
|
+
-- models/demo/orders_clean.sql ← filename = table name. That's the whole format.
|
|
32
|
+
SELECT id, amount FROM raw.orders WHERE amount > 0
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
No `MODEL(...)` headers. No Jinja `{{ ref('...') }}`. No per-model YAML. Dependencies,
|
|
36
|
+
column lineage, and change detection are read from the SQL you already wrote — powered by
|
|
37
|
+
[SQLGlot](https://github.com/tobymao/sqlglot), the parser underneath the ecosystem's
|
|
38
|
+
lineage tooling. A model with no config is a FULL rebuild; the few models that need
|
|
39
|
+
more (incremental, in v0.2) get a couple of lines in the one `reble.yml` the project
|
|
40
|
+
already has — never boilerplate per model.
|
|
41
|
+
|
|
42
|
+
Reble is a single CLI that gives a small data team a complete local-first analytics
|
|
43
|
+
platform — DuckDB + Apache Iceberg + SQLGlot-powered transforms, pre-wired — with
|
|
44
|
+
subset branching of the warehouse and branch-per-PR CI for data pipelines.
|
|
45
|
+
|
|
46
|
+
> ⚠️ **Status: pre-alpha, but real.** The full loop works today — `init → run →
|
|
47
|
+
> branch → run → diff → promote`, both git orders, with 25 passing tests — from
|
|
48
|
+
> source install only (no PyPI release yet). One migration in flight: the current
|
|
49
|
+
> source still embeds SQLMesh as the runner; the SQLGlot-direct core described here
|
|
50
|
+
> is [validated](spikes/04-sqlglot-direct/RESULTS.md) and replacing it. Feedback is
|
|
51
|
+
> the most valuable contribution — open a Discussion.
|
|
52
|
+
|
|
53
|
+
**→ [Why Reble exists](docs/why.md)** — the full story: the four gaps in data
|
|
54
|
+
engineering workflows, why existing tools don't close them, and why now.
|
|
55
|
+
|
|
56
|
+
## The idea
|
|
57
|
+
|
|
58
|
+
Testing a data pipeline change today means cloning or rebuilding an entire dev
|
|
59
|
+
warehouse — even when your change touches three tables. Reble branches **just the
|
|
60
|
+
tables you're changing**:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install reble
|
|
64
|
+
reble init my-warehouse
|
|
65
|
+
# edit your models…
|
|
66
|
+
reble branch create fix-orders # scope + pins inferred from your changes —
|
|
67
|
+
# your edited models, their downstream
|
|
68
|
+
# cascade, and their upstream inputs
|
|
69
|
+
reble run # writes go to zero-copy Iceberg branch refs;
|
|
70
|
+
# inputs read prod as of the branch epoch
|
|
71
|
+
reble diff # schema + row-level diff vs your branch base
|
|
72
|
+
reble promote # atomic fast-forward to main, clean up
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Both git orders work: edit-first (scope inferred from the diff) or branch-first
|
|
76
|
+
(empty scope + frozen epoch; the scope grows automatically at first run, and reads
|
|
77
|
+
resolve as of the moment you branched).
|
|
78
|
+
|
|
79
|
+
- **Zero-copy branches** — branched tables use native Iceberg refs (copy-on-write);
|
|
80
|
+
a branch of a 10GB table costs ~nothing until you write.
|
|
81
|
+
- **Pinned inputs** — unbranched tables are read at their snapshot from branch-creation
|
|
82
|
+
time, so your test inputs don't drift while prod keeps ingesting.
|
|
83
|
+
- **Row-level diffs** — answer the question every reviewer actually has: *what rows
|
|
84
|
+
does this change?*
|
|
85
|
+
- **Column-level lineage & change detection** — inferred from your SQL via SQLGlot:
|
|
86
|
+
only changed models run (cosmetic edits don't count — hashing is on the canonical
|
|
87
|
+
AST), and downstream impact is shown before you apply.
|
|
88
|
+
- **No merge, ever** — branches are ephemeral: create, test, promote (fast-forward or
|
|
89
|
+
re-run) or discard. We refuse to build last-write-wins data merges.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Four days you've had
|
|
94
|
+
|
|
95
|
+
The same loop, in scenarios every data engineer has lived through. All CLI output
|
|
96
|
+
below is the real tool's output format. The example warehouse:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
raw.orders ← ingested hourly by Airbyte
|
|
100
|
+
raw.customers ← ingested nightly
|
|
101
|
+
stg_orders ← staging model
|
|
102
|
+
stg_customers ← staging model
|
|
103
|
+
fct_revenue_daily ← the table finance actually looks at
|
|
104
|
+
mart_exec_dashboard ← reads fct_revenue_daily
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 1. "Finance says revenue is wrong" — changing a metric definition
|
|
108
|
+
|
|
109
|
+
Cancelled orders are being counted as revenue. The fix is one line in `stg_orders` —
|
|
110
|
+
but `fct_revenue_daily` and `mart_exec_dashboard` are downstream, and finance will
|
|
111
|
+
ask exactly one question: *how much does this change the numbers?*
|
|
112
|
+
|
|
113
|
+
```mermaid
|
|
114
|
+
gitGraph
|
|
115
|
+
commit id: "prod (hourly ingest continues)"
|
|
116
|
+
branch fix-cancelled-revenue
|
|
117
|
+
commit id: "exclude cancelled orders"
|
|
118
|
+
commit id: "run + diff: -3.2% revenue"
|
|
119
|
+
checkout main
|
|
120
|
+
merge fix-cancelled-revenue id: "promote"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```console
|
|
124
|
+
$ vim models/stg_orders.sql # ... WHERE status != 'cancelled'
|
|
125
|
+
|
|
126
|
+
$ reble branch create fix-cancelled-revenue
|
|
127
|
+
Created branch fix-cancelled-revenue
|
|
128
|
+
scope (inferred from your changes): stg_orders, fct_revenue_daily, mart_exec_dashboard
|
|
129
|
+
pins (2): raw.customers, raw.orders
|
|
130
|
+
Switched to fix-cancelled-revenue
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Notice what you didn't do: enumerate the downstream cascade. Reble read it off the
|
|
134
|
+
model graph — your one-line edit touches three tables, and the two raw inputs are
|
|
135
|
+
pinned so hourly ingestion can't shift your numbers mid-analysis.
|
|
136
|
+
|
|
137
|
+
```console
|
|
138
|
+
$ reble run
|
|
139
|
+
Environment: fix_cancelled_revenue
|
|
140
|
+
mirrored inputs : raw.customers, raw.orders
|
|
141
|
+
models changed : stg_orders, fct_revenue_daily, mart_exec_dashboard
|
|
142
|
+
published : stg_orders, fct_revenue_daily, mart_exec_dashboard
|
|
143
|
+
|
|
144
|
+
$ reble diff
|
|
145
|
+
Branch fix-cancelled-revenue vs base:
|
|
146
|
+
|
|
147
|
+
stg_orders
|
|
148
|
+
rows: 1,204,331 -> 1,168,210
|
|
149
|
+
+0 added -36,121 removed ~0 changed
|
|
150
|
+
|
|
151
|
+
fct_revenue_daily
|
|
152
|
+
rows: 730 -> 730
|
|
153
|
+
+0 added -0 removed ~214 changed
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
There's finance's answer, before anything touched prod: **36,121 cancelled orders
|
|
157
|
+
excluded, revenue restated on 214 of 730 days.** Screenshot the diff, get the
|
|
158
|
+
sign-off, then:
|
|
159
|
+
|
|
160
|
+
```console
|
|
161
|
+
$ reble promote
|
|
162
|
+
Promoted branch fix-cancelled-revenue to main:
|
|
163
|
+
stg_orders
|
|
164
|
+
fct_revenue_daily
|
|
165
|
+
mart_exec_dashboard
|
|
166
|
+
Back on main
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Without branches:** you'd have run this in a shared dev schema (numbers drifting
|
|
170
|
+
under you with every hourly ingest), eyeballed two spreadsheet exports, and pushed to
|
|
171
|
+
prod hoping.
|
|
172
|
+
|
|
173
|
+
### 2. Building a brand-new mart (greenfield, branch-first)
|
|
174
|
+
|
|
175
|
+
You're starting `mart_weekly_retention`. Nothing downstream exists yet, so there's
|
|
176
|
+
nothing to diff against — the risks are different: your inputs drifting while you
|
|
177
|
+
iterate, and a half-finished table leaking into prod where the BI tool will find it.
|
|
178
|
+
|
|
179
|
+
Branch first, git-style, *before* writing any SQL:
|
|
180
|
+
|
|
181
|
+
```console
|
|
182
|
+
$ reble branch create weekly-retention
|
|
183
|
+
Created branch weekly-retention (branch-first: no changes yet)
|
|
184
|
+
scope: open — grows automatically when you edit models and `reble run`
|
|
185
|
+
reads: every table frozen as of this moment (the branch epoch)
|
|
186
|
+
Switched to weekly-retention
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Now iterate. Twenty runs over three days while prod ingests hourly — every run
|
|
190
|
+
computes against the same Tuesday-9am inputs, so when the retention curve changes,
|
|
191
|
+
it's because *your SQL* changed:
|
|
192
|
+
|
|
193
|
+
```console
|
|
194
|
+
$ vim models/mart_weekly_retention.sql
|
|
195
|
+
$ reble run
|
|
196
|
+
Environment: weekly_retention
|
|
197
|
+
models changed : mart_weekly_retention
|
|
198
|
+
published : mart_weekly_retention
|
|
199
|
+
|
|
200
|
+
$ reble diff
|
|
201
|
+
Branch weekly-retention vs base:
|
|
202
|
+
|
|
203
|
+
mart_weekly_retention (new table — profile)
|
|
204
|
+
rows: 52
|
|
205
|
+
cohort_week: date
|
|
206
|
+
customers: int64
|
|
207
|
+
retained_w1: double
|
|
208
|
+
retained_w4: double, 3 nulls
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
A profile, not a diff — there's no "before" for a new table. Those 3 nulls in
|
|
212
|
+
`retained_w4`? Caught here, not in the exec's dashboard. When it's right, `reble
|
|
213
|
+
promote` — and the moment it lands, the new mart is registered in the lineage graph,
|
|
214
|
+
so the *next* person who touches `stg_customers` gets warned that your mart reads it.
|
|
215
|
+
|
|
216
|
+
### 3. Two engineers, two branches, zero coordination
|
|
217
|
+
|
|
218
|
+
Priya is fixing order dedup in `stg_orders`. Marco is building `mart_customer_ltv`.
|
|
219
|
+
Neither knows what the other is doing. Neither needs to.
|
|
220
|
+
|
|
221
|
+
```mermaid
|
|
222
|
+
gitGraph
|
|
223
|
+
commit id: "prod"
|
|
224
|
+
branch priya/fix-dedup
|
|
225
|
+
commit id: "dedup fix + diff"
|
|
226
|
+
checkout main
|
|
227
|
+
branch marco/customer-ltv
|
|
228
|
+
commit id: "new LTV mart"
|
|
229
|
+
checkout main
|
|
230
|
+
merge priya/fix-dedup id: "promote #1"
|
|
231
|
+
merge marco/customer-ltv id: "promote #2 (rebase check passes)"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Their scopes are disjoint — Priya's refs on `stg_orders`+downstream, Marco's on his
|
|
235
|
+
new mart — so they work in parallel all week. Promotes go one at a time. Priya
|
|
236
|
+
promotes first. When Marco promotes, Reble checks: *do any of Marco's models read the
|
|
237
|
+
tables Priya changed?*
|
|
238
|
+
|
|
239
|
+
- **No** → Marco's promote fast-forwards, done.
|
|
240
|
+
- **Yes** (his LTV mart reads `stg_orders`) → promote refuses with instructions:
|
|
241
|
+
rerun against the new main, re-validate, then promote. Never a silent data merge.
|
|
242
|
+
|
|
243
|
+
The overlap case is caught even earlier — at *creation*:
|
|
244
|
+
|
|
245
|
+
```console
|
|
246
|
+
$ reble branch create also-touching-orders
|
|
247
|
+
...
|
|
248
|
+
warning: stg_orders is also scoped by branch 'priya/fix-dedup' —
|
|
249
|
+
second promote will require a rebase
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Without branches:** Priya and Marco share a dev schema, clobber each other's
|
|
253
|
+
tables, and coordinate via Slack messages that start with "hey, are you using...".
|
|
254
|
+
|
|
255
|
+
### 4. The save — a bad change that never reached prod
|
|
256
|
+
|
|
257
|
+
You "simplify" a join in `stg_orders`. The SQL looks obviously correct. A reviewer
|
|
258
|
+
would have approved it.
|
|
259
|
+
|
|
260
|
+
```console
|
|
261
|
+
$ reble branch create simplify-join
|
|
262
|
+
$ reble run
|
|
263
|
+
$ reble diff
|
|
264
|
+
Branch simplify-join vs base:
|
|
265
|
+
|
|
266
|
+
stg_orders
|
|
267
|
+
rows: 1,204,331 -> 1,983,507
|
|
268
|
+
+779,176 added -0 removed ~0 changed
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**A 65% row explosion.** The "simplified" join fans out on duplicate customer keys.
|
|
272
|
+
Caught on a laptop, on frozen inputs, in a branch nobody else can see:
|
|
273
|
+
|
|
274
|
+
```console
|
|
275
|
+
$ reble branch delete simplify-join
|
|
276
|
+
Deleted branch simplify-join
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Nothing to roll back, nothing to explain in the incident channel, no backfill. The
|
|
280
|
+
branch cost ~0 bytes to create and one command to destroy.
|
|
281
|
+
|
|
282
|
+
**Without branches:** this ships Friday, the weekend batch triples revenue, and
|
|
283
|
+
Monday starts with an incident review.
|
|
284
|
+
|
|
285
|
+
### The pattern
|
|
286
|
+
|
|
287
|
+
All four are the same loop:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
(edit ↔ branch, either order) → run → diff or profile → promote or discard
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Branches are metadata only — zero-copy Iceberg refs plus a frozen epoch. Creating one
|
|
294
|
+
is free; deleting one is guilt-free. Inputs never drift, prod is never at risk, and
|
|
295
|
+
the diff answers the question reviewers actually ask.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Measured, not promised
|
|
300
|
+
|
|
301
|
+
The design is validated by reproducible spikes in [`spikes/`](spikes/), including a
|
|
302
|
+
full-scale performance run — **140M rows / 10.22GB** on an Apple M4 Pro laptop
|
|
303
|
+
(pyiceberg 0.11.1, DuckDB 1.5.5):
|
|
304
|
+
|
|
305
|
+
| Operation at 10GB scale | Time |
|
|
306
|
+
|---|---|
|
|
307
|
+
| Create a branch of the 140M-row table | **< 10ms** (zero-copy, size-independent) |
|
|
308
|
+
| Pinned full-table scan → Arrow | 4.0s |
|
|
309
|
+
| Projected scan (2 of 6 columns) | 0.47s |
|
|
310
|
+
| Full diff — both refs scanned, added + changed rows | **5.9s** |
|
|
311
|
+
| Branch append (5M rows) | 1.3s |
|
|
312
|
+
| Bulk load throughput | ~3.5M rows/s |
|
|
313
|
+
|
|
314
|
+
Peak RAM 12.3GB, 3.5GB on disk (Parquet ≈ 2.9× compression). Details and the scripts
|
|
315
|
+
to reproduce: [spike 1 — branch lifecycle](spikes/01-pyiceberg-branches/RESULTS.md) ·
|
|
316
|
+
[spike 2 — performance](spikes/02-perf/RESULTS.md) ·
|
|
317
|
+
[spike 4 — the SQLGlot-direct core](spikes/04-sqlglot-direct/RESULTS.md).
|
|
318
|
+
|
|
319
|
+
## The killer workflow: branch-per-PR
|
|
320
|
+
|
|
321
|
+
A GitHub Action (coming next) that, on every pull request:
|
|
322
|
+
|
|
323
|
+
1. Creates a branch scoped to the changed models' tables
|
|
324
|
+
2. Runs only the changed models
|
|
325
|
+
3. Posts a PR comment: models changed, downstream impact, row-level diff stats
|
|
326
|
+
4. Promotes on merge, cleans up on close
|
|
327
|
+
|
|
328
|
+
Data PRs become reviewable like code PRs — scenario 1 above, fully automated.
|
|
329
|
+
|
|
330
|
+
## Local-first, zero services
|
|
331
|
+
|
|
332
|
+
Everything runs on a laptop: DuckDB embedded, Iceberg on local filesystem, SQLite
|
|
333
|
+
catalog, transforms in-process. No Docker, no daemons. The same project moves to team
|
|
334
|
+
mode (S3/MinIO + Postgres or REST catalog) via config.
|
|
335
|
+
|
|
336
|
+
## What Reble is not
|
|
337
|
+
|
|
338
|
+
- Not a query engine, storage engine, or table format — it composes DuckDB, Iceberg,
|
|
339
|
+
and SQLGlot and adds the branching layer and glue.
|
|
340
|
+
- Not a dbt/SQLMesh replacement you must migrate to all at once — importers
|
|
341
|
+
(`{{ ref('...') }}` and `MODEL(...)` translation) are on the roadmap.
|
|
342
|
+
- Not a Snowflake competitor — the target is small teams and the local/CI loop.
|
|
343
|
+
- Not a full-catalog branching system (see Nessie/lakeFS for that) — Reble branches
|
|
344
|
+
subsets over standard Iceberg catalogs, no migration required.
|
|
345
|
+
|
|
346
|
+
## Design docs
|
|
347
|
+
|
|
348
|
+
- [Why Reble exists](docs/why.md) — motivation and positioning
|
|
349
|
+
- [Architecture](docs/architecture.md)
|
|
350
|
+
- [Getting started](docs/getting-started.md)
|
|
351
|
+
- [Validated spikes](spikes/) — reproducible proof the core primitives work today
|
|
352
|
+
|
|
353
|
+
## Contributing
|
|
354
|
+
|
|
355
|
+
Feedback beats code right now — try the loop on your own models and open a
|
|
356
|
+
[Discussion](../../discussions) or an issue. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
357
|
+
|
|
358
|
+
## License
|
|
359
|
+
|
|
360
|
+
Apache 2.0 — see [LICENSE](LICENSE).
|