Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Declarative architecture docs (English, present tense). These describe the syste
| Command surface | Two commands, `site` and `bench`, named for the situation rather than for the tool's internal steps; they differ in one thing, the memory tuning, which only `site` applies |
| Archive read access | `ALTER DEFAULT PRIVILEGES FOR ROLE scada_writer` set **before** `public.trends` is created, and before the writer first runs. The `semiplot_*` tables are granted table by table instead, since the superuser creates them |
| Objects we add | `semiplot_tags`, `semiplot_groups`, `semiplot_pen_groups`, `semiplot_meta`, the `semiplot_register_new_pens()` function the viewer calls, and the vendor-shaped `public.trends` — no triggers, scheduled jobs, extensions, or other functions; `messages` is not created, the SCADA makes it |
| SemiPlot schema version | `semiplot_meta.schema_version`, `1` in this release, written by `provision` alone. A floor, not an equality: a viewer refuses a database below the version it needs and accepts one above, because the versions this repository issues grow by addition. |
| SemiPlot schema version | `semiplot_meta.schema_version`, `1` in this release, written by `provision` alone. A floor, not an equality: a viewer refuses a database below the version it needs and accepts one above, because the versions this repository issues grow by addition. The rule starts with the first installation: until then a schema change, a rename included, edits the `CREATE TABLE` alone and keeps `1`. |
| Archive schema | Owned by Simple-Scada 2 and documented in the SemiPlot repository. This tool creates `public.trends` once, with the vendor's shape, connected as `scada_writer`, and never alters it afterwards; day partitions and every later change to the schema remain the vendor's |
| Distribution | Binaries as GitHub release assets; the Linux binary also as `ghcr.io/semiteq/semibase`, `FROM scratch`, one file, pushed after the release exists; benches track `:latest` and a prerelease never moves it |
| Backup method | `UNDECIDED` — commissioning-time, needs the customer's archive size |
Expand Down
8 changes: 5 additions & 3 deletions docs/architecture/provisioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ saves a pen through the same connection it reads history on, and a bug in it can
A `semiplot_tags` row is keyed by `id`, the SCADA variable number. SemiPlot owns the settings in the
row and none of the keys: its editor changes a pen and never adds one, deletes one or moves one onto
another variable. The grant on that table is therefore column-level,
`GRANT SELECT, UPDATE (name, unit, format, color, line_style, enabled_on_start, scale_min, scale_max)`,
`GRANT SELECT, UPDATE (name, unit, format, color, line_style, enabled_on_start, scale_min_on_start, scale_max_on_start)`,
and a table-level `UPDATE` would not do: it would let `UPDATE semiplot_tags SET id = ...` re-key a
pen. A column grant is not a table grant, so `has_table_privilege('semiplot', 'semiplot_tags',
'UPDATE')` answers false, and no check asks it. A pen row is added only by
Expand Down Expand Up @@ -235,7 +235,7 @@ checks a `LANGUAGE sql` body against the relations it names when the function is

| Table | Holds |
| --- | --- |
| `semiplot_tags` | One pen: `id` matching `trends.id`, `name`, `unit`, `format` as a .NET numeric format string the viewer applies and the server does not validate, `color` as `#RRGGBB`, `line_style` (0 interpolated, 1 stepped), `enabled_on_start`, and the `scale_min`/`scale_max` pair that bounds the pen's own Y axis. Both bounds `NULL` means autoscale |
| `semiplot_tags` | One pen: `id` matching `trends.id`, `name`, `unit`, `format` as a .NET numeric format string the viewer applies and the server does not validate, `color` as `#RRGGBB`, `line_style` (0 interpolated, 1 stepped), `enabled_on_start`, and the `scale_min_on_start`/`scale_max_on_start` pair that bounds the pen's own Y axis. Both bounds `NULL` means autoscale. A column with the `_on_start` suffix holds a start value: what the pen opens with in a new window and what the viewer's "Restore initial scale" returns to. Editing the pen on the chart changes the current view and never a start value, and a changed start value never changes a chart already open. Every other settings column is a live setting, which a running chart applies at its next catalogue read |
| `semiplot_groups` | A group name. `GENERATED ALWAYS AS IDENTITY` rather than `serial`, so `semiplot` needs no `USAGE` on a sequence to insert one |
| `semiplot_pen_groups` | Membership, `(pen_id, group_id)`. A pen may sit in several groups and in none: a pen with no row here is legal and the viewer shows it ungrouped |
| `semiplot_meta` | One row: `schema_version`. A `singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton)` admits no second row, and the value is written by one `INSERT ... ON CONFLICT (singleton) DO UPDATE`, so a failure cannot leave the table empty and a re-run cannot leave two rows |
Expand Down Expand Up @@ -318,7 +318,9 @@ inside the `semiplot` role's 30 s `statement_timeout` either way.
This release writes `1`. A viewer states the minimum version it needs and refuses a database
storing a **lower** one; a **higher** one is accepted, because the versions this repository issues
grow by addition and a database carrying more than a viewer needs still carries what it needs.
When `semiplot_markers` arrives it bumps the number to 2, and a viewer that reads only the pen
The rule starts with the first installation. Until a site runs a provisioned database, a schema
change, a column rename included, edits the `CREATE TABLE` alone, issues no `ALTER`, and leaves the
number at `1`. When `semiplot_markers` arrives it bumps the number to 2, and a viewer that reads only the pen
tables has to keep working against it, which an equality check would break.

A removal is not signalled by this number. An older viewer meeting a dropped column gets 42703,
Expand Down
4 changes: 2 additions & 2 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ SemiBase разворачивается одной утилитой — `semibas
не может — эту проверку утилита выполняет на каждом запуске.

Строка `semiplot_tags` привязана к номеру переменной SCADA. SemiPlot меняет в ней только
настройки пера: имя, единицы, формат, цвет, стиль линии, показ при старте и границы шкалы.
настройки пера: имя, единицы, формат, цвет, стиль линии, показ при старте и начальные границы шкалы.
Добавить перо напрямую, удалить его или сменить ему `id` роль не может, и утилита на каждом запуске
проверяет, что эти три команды ей запрещены. Новые перья добавляет только функция
`semiplot_register_new_pens()`, и только для переменных, которые SCADA уже записала в `trends`.
Expand Down Expand Up @@ -98,7 +98,7 @@ DDL. Права `CREATE` в схеме `public` у роли `semiplot` нет

| Таблица | Что хранит |
| --------------------- | ------------------------------------------------------------------------------------------- |
| `semiplot_tags` | Перо: `id` из `trends`, имя, единицы, формат числа, цвет `#RRGGBB`, стиль линии, показ при старте, границы шкалы |
| `semiplot_tags` | Перо: `id` из `trends`, имя, единицы, формат числа, цвет `#RRGGBB`, стиль линии, показ при старте, начальные границы шкалы |
| `semiplot_groups` | Имена групп перьев |
| `semiplot_pen_groups` | Принадлежность пера группам. Перо может входить в несколько групп и ни в одну |
| `semiplot_meta` | Одну строку: версию схемы |
Expand Down
8 changes: 4 additions & 4 deletions internal/provision/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func TestPlotWriteProbesCoverTheWritableTables(t *testing.T) {

func TestPlotTagsUpdateProbeWritesEverySettingsColumn(t *testing.T) {
want := "UPDATE semiplot_tags SET name = name, unit = unit, format = format, color = color, " +
"line_style = line_style, enabled_on_start = enabled_on_start, scale_min = scale_min, " +
"scale_max = scale_max WHERE id = -1"
"line_style = line_style, enabled_on_start = enabled_on_start, " +
"scale_min_on_start = scale_min_on_start, scale_max_on_start = scale_max_on_start WHERE id = -1"
probes := plotWriteProbes()
if got := probes[probePosition(t, probes, "semiplot_tags", "UPDATE")].statement; got != want {
t.Errorf("the semiplot_tags probe is %q, want %q", got, want)
Expand Down Expand Up @@ -92,8 +92,8 @@ func TestWriteProbeFailure(t *testing.T) {
}

tags := writeProbeFailure(plotWriteProbe{"semiplot_tags", "UPDATE", ""}, errors.New("permission denied"))
wantTags := "GRANT SELECT, UPDATE (name, unit, format, color, line_style, enabled_on_start, scale_min, " +
"scale_max) ON semiplot_tags TO semiplot"
wantTags := "GRANT SELECT, UPDATE (name, unit, format, color, line_style, enabled_on_start, " +
"scale_min_on_start, scale_max_on_start) ON semiplot_tags TO semiplot"
if !strings.Contains(tags.Error(), wantTags) {
t.Errorf("the semiplot_tags repair does not prescribe the column grant: %q", tags)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/provision/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ var plotEditableTagColumns = []string{
"color",
"line_style",
"enabled_on_start",
"scale_min",
"scale_max",
"scale_min_on_start",
"scale_max_on_start",
}

const (
Expand Down
6 changes: 3 additions & 3 deletions internal/provision/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ func TestSemiplotTagsColumns(t *testing.T) {
"color",
"line_style",
"enabled_on_start",
"scale_min",
"scale_max",
"scale_min_on_start",
"scale_max_on_start",
}
got := columnNamesOf(semibase.SemiplotTagsSQL)
if len(got) != len(want) {
Expand Down Expand Up @@ -150,7 +150,7 @@ func TestSemiplotGrantStatements(t *testing.T) {
statement string
}{
{"semiplot_tags", "GRANT SELECT, UPDATE (name, unit, format, color, line_style, enabled_on_start, " +
"scale_min, scale_max) ON semiplot_tags TO semiplot"},
"scale_min_on_start, scale_max_on_start) ON semiplot_tags TO semiplot"},
{"semiplot_groups", "GRANT SELECT, INSERT, UPDATE, DELETE ON semiplot_groups TO semiplot"},
{"semiplot_pen_groups", "GRANT SELECT, INSERT, UPDATE, DELETE ON semiplot_pen_groups TO semiplot"},
{"semiplot_meta", "GRANT SELECT ON semiplot_meta TO semiplot"},
Expand Down
22 changes: 11 additions & 11 deletions sql/semiplot_tags.sql
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
-- docs/architecture/provisioning.md#the-semiplot-configuration-schema

CREATE TABLE IF NOT EXISTS semiplot_tags (
id integer PRIMARY KEY,
name text NOT NULL,
unit text,
format text,
color text,
line_style smallint NOT NULL DEFAULT 0,
enabled_on_start boolean NOT NULL DEFAULT true,
scale_min double precision,
scale_max double precision,
id integer PRIMARY KEY,
name text NOT NULL,
unit text,
format text,
color text,
line_style smallint NOT NULL DEFAULT 0,
enabled_on_start boolean NOT NULL DEFAULT true,
scale_min_on_start double precision,
scale_max_on_start double precision,
CONSTRAINT semiplot_tags_scale_paired CHECK (
(scale_min IS NULL) = (scale_max IS NULL)
AND (scale_min IS NULL OR scale_min < scale_max)),
(scale_min_on_start IS NULL) = (scale_max_on_start IS NULL)
AND (scale_min_on_start IS NULL OR scale_min_on_start < scale_max_on_start)),
CONSTRAINT semiplot_tags_color_hex CHECK (color IS NULL OR color ~ '^#[0-9A-Fa-f]{6}$')
);
Loading