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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! # MeterStore
//!
//! Hot/cold tiered store for metering time series: PostgreSQL holds the recent
//! interval window, Apache Iceberg holds the history, and a single explicit
//! timestamp — the *tiering watermark* — separates them.
//!
//! MeterStore is the persistence layer of the [mako](https://github.com/hupe1980/mako)
//! platform. It stores the types defined by the [`metering`] crate; it does not
//! redefine them and it does not compute with them.
//!
//! ## What this crate owns
//!
//! Exactly three things beyond storage mechanics:
//!
//! 1. **Correction versioning** — [`version`], implementing MSCONS's rule that
//! versions are only comparable within a (network operator, month) scope.
//! 2. **The transaction-time axis** — `recorded_at`, giving bitemporality
//! alongside `metering`'s valid time (`from`/`to`).
//! 3. **The tiering boundary** — [`watermark`].
//!
//! Everything else — DST calendars, unit conversion, quality semantics,
//! validation, Ersatzwertbildung — belongs to [`metering`]. Duplicating any of
//! it here would create a second implementation to keep correct, and it would
//! drift.
//!
//! [`planner`] re-exports `metering::calendar` for convenience, so callers get
//! DST-correct local days without depending on both crates directly. The
//! implementation is upstream and there is only one of it. Since `metering` 0.19
//! the *choice* between the two calendars is upstream too, as
//! [`DayBoundary`](metering::calendar::DayBoundary) — so the one thing
//! [`planner::calendar`] adds is neither arithmetic nor the choice, but a
//! storage fact: a stored row carries its `sparte`, so the store knows which
//! boundary applies to it. That mapping is [`planner::day_boundary`], written
//! once.
//!
//! ## The five things a caller usually wants
//!
//! - [`MeterStore::sql`] — DataFusion's own `DataFrame` over a table that spans
//! both tiers. [`MeterStore::query`] returns the same rows plus the boundary
//! they were computed against (P1), and [`MeterStore::stream`] returns that
//! boundary *before* the rows, for a result too large to hold.
//! - [`MeterStore::series`] — one measuring point as a `metering`
//! `MeasurementSeries`, version-resolved and tier-split.
//! - [`MeterStore::as_of`] — the same queries against a pinned Iceberg snapshot
//! and an optional version ceiling, for a settlement rerun.
//! - [`MeterStore::completeness`] — whether a range holds what the DST-aware
//! calendar says it should, because a missing interval is information rather
//! than an empty set.
//! - [`MeterCatalog`] — several tables in one session, when a deployment holds
//! more than one stream and needs a statement that mentions both.
//!
//! Two more that a service exposing SQL will want: [`MeterStore::scoped`]
//! confines a session to one identity value, and
//! [`MeterCatalog::isolated`] confines it to one table — both by
//! injection into the plan, so caller-supplied SQL cannot step past them.
//! [`MeterStore::append_authoritative`] is the write path for a value the
//! operator authors rather than receives.
//!
//! ## What is stored
//!
//! **Two shapes**, declared per table by [`TimeModel`]. A *Lastgang* is energy
//! over `[from, to)`; a *Zählerstandsgang* is a cumulative register value at an
//! instant, which BK6-24-174 has made a primary record as voluminous as the
//! Lastgang derived from it. They are never the same table, because `value`
//! would mean two things in one column and no aggregate could tell them apart.
//!
//! The shape also decides what *names* a reading. A Marktlokation may be
//! measured by several Messlokationen, and both meters carry the same OBIS
//! register at the same instants. A load profile belongs to the market location,
//! so `melo_id` labels it; a register belongs to the meter, so `melo_id` names
//! it and joins the merge key. A point table does that by default;
//! [`TableConfig::identify_by_melo`] pins it either way.
//!
//! [`TableConfig::identify_by_melo`]: crate::config::TableConfig::identify_by_melo
//!
//! All four Sparten. `value` carries the quantity and `unit` its dimension,
//! because water is metered *and billed* in m³ and gas may sit on either side of
//! the Brennwert conversion — a column named for kilowatt-hours would be wrong
//! for half of them. A unit the commodity cannot be expressed in is refused at
//! the write.
//!
//! Any declared resolution, not only the quarter-hour: completeness asks
//! `metering`'s calendar per day, so one-minute data expects 1 440 intervals on
//! an ordinary day and 1 500 on the 25-hour autumn one.
//!
//! And **two kinds of day**. Electricity, heat and water are balanced on the
//! Berlin calendar day; gas is balanced on the *Gastag*, 06:00 to 06:00 local.
//! Grouping a gas Lastgang by the calendar day books its 00:00–06:00 draw into
//! the neighbouring Bilanzierungstag — six hours a day, every day — so the
//! bucketing and the expected interval count both follow the row's Sparte, in
//! SQL through `meter_balancing_day` and in Rust through
//! [`planner::balancing_day`].
//!
//! The boundary carries up to the **month**, which is the market's own rule:
//! EDI@Energy *Allgemeine Festlegungen* v6.1c, Kap. 3.1 defines the gas
//! Bilanzierungsmonat as 01.06 06:00 to 01.07 06:00. So a version scope — which
//! is `(network operator, month)` — is cut at 06:00 for gas too, and every
//! [`VersionScope`] constructor takes a `Sparte` for that reason.
//!
//! An external engine has neither function, and SQL dialects differ on timestamp
//! arithmetic — so the answer is stored. Every row carries a `balancing_day`
//! column derived once by the encoder, and reading the Iceberg files directly
//! needs a `GROUP BY` and no calendar reasoning. It is the single derived value
//! this crate persists; [`encode::schema`] argues the exception.
//!
//! Identifiers are parsed rather than trusted. `malo_id` and `melo_id` are
//! `metering`'s [`MaloId`] and [`MeloId`] on both sides of the encoding: a
//! MaLo-ID carries a check digit so that a transposition is detectable, and a
//! store that accepted eleven arbitrary digits would throw that away at the one
//! point it still mattered.
//!
//! [`MaloId`]: metering::ids::MaloId
//! [`MeloId`]: metering::ids::MeloId
//!
//! ## Tiers and surfaces
//!
//! The cold tier accepts any `Arc<dyn Catalog>`; three are built for you —
//! [`IcebergSqlCatalog`] over the same PostgreSQL as the hot tier,
//! [`cold::IcebergRestCatalog`] over Polaris/Lakekeeper/Nessie/Gravitino
//! (`rest-catalog`, on by default), and [`cold::S3TablesCatalog`] over an AWS S3
//! Tables table bucket (`s3tables`). [`Settings::connect`] builds whichever a
//! configuration file names, along with the pool and every validated table.
//!
//! Two serving surfaces: a read-only Iceberg REST façade (`catalog-facade`) for
//! SQL-catalog deployments, and Flight SQL (`flight`) for the unified hot + cold
//! view — the one thing an external client cannot assemble for itself. Flight
//! serves any [`SqlSurface`], so a whole [`MeterCatalog`] goes on a socket as
//! readily as one table.
//!
//! [`Settings::connect`]: crate::settings::Settings::connect
//!
//! ## Status
//!
//! Pre-alpha, and **unpublished on purpose**: the API is still settling, and
//! integrating against a real workload is what settles it. What is implemented,
//! what is measured and what is not yet done are on the
//! [status page](https://hupe1980.github.io/meterstore); [`testkit`] is the
//! reference the store is checked against, and is public so a deployment can run
//! it over its own configuration.
//!
//! Full documentation: <https://hupe1980.github.io/meterstore>
//!
//! [`MeterStore::sql`]: crate::session::MeterStore::sql
//! [`MeterStore::query`]: crate::session::MeterStore::query
//! [`MeterStore::stream`]: crate::session::MeterStore::stream
//! [`MeterStore::series`]: crate::session::MeterStore::series
//! [`MeterStore::as_of`]: crate::session::MeterStore::as_of
//! [`MeterStore::completeness`]: crate::session::MeterStore::completeness
//! [`MeterCatalog`]: crate::session::MeterCatalog
//! [`MeterCatalog::isolated`]: crate::session::MeterCatalog::isolated
//! [`MeterStore::scoped`]: crate::session::MeterStore::scoped
//! [`MeterStore::append_authoritative`]: crate::session::MeterStore::append_authoritative
//! [`SqlSurface`]: crate::session::SqlSurface
//! [`TimeModel`]: crate::config::TimeModel
// Doc comments throughout cite `§N`. Those are cross-references between the
// design notes this crate's maintainers keep, not links a reader needs to
// follow: everything required to *use* the crate is in the documentation here,
// and the reasoning behind each decision is written out at
// <https://hupe1980.github.io/meterstore>. The markers exist so that changing a
// behaviour is traceable to the argument it rested on.
/// Arrow, re-exported from DataFusion.
///
/// Sourced through `datafusion` rather than as a direct dependency so the graph
/// cannot contain two incompatible `arrow` versions — a mismatch would make
/// `RecordBatch` and `TableProvider` types mutually unusable. Every module uses
/// `crate::arrow`, never a direct `arrow::` path.
pub use arrow;
pub use IcebergRestCatalog;
pub use S3TablesCatalog;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use PostgresHot;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
/// Common imports for working with MeterStore.