From cd86ccce97c94cd5e1bd74dd0f1ea10af852ace2 Mon Sep 17 00:00:00 2001 From: delchev Date: Tue, 4 Aug 2026 19:49:12 +0300 Subject: [PATCH] fix(numbering): a materializing partition inherits the base row's counter, not zero (#6517) On a fresh tenant with a partitioned series the operator seeded prefix + next on the base row (the only row that exists before the first allocation) - and the first issued document rendered ...0000000001: the prefix took effect, the counter did not. materializePartition copied only the shape and hardcoded the counter to zero, silently discarding the seed. The base row is the tenant's full template now: a partition row is born with the base shape AND the base counter, so seeding before first use works; the settings page's virtual rows preview the inherited value and the base-row hint says "inheriting this shape and its starting counter". Declared-series provisioning still starts at zero, and existing partition rows are untouched. NumberingSdkIT: seed base next=300, first allocation of a brand-new partition renders exactly T-0300 and continues (3 numbering ITs green locally). Co-Authored-By: Claude Fable 5 --- .../numbering/DocumentNumberEndpoint.java | 6 ++-- .../engine/numbering/DocumentNumberStore.java | 31 ++++++++++++------- .../application-core/i18n/bg-BG/shell.json | 2 +- .../application-core/i18n/en-US/shell.json | 2 +- .../application/views/_settings.html | 2 +- .../integration/tests/api/NumberingSdkIT.java | 21 +++++++++++++ 6 files changed, 47 insertions(+), 17 deletions(-) diff --git a/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberEndpoint.java b/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberEndpoint.java index 0951ee2705..6206c1e751 100644 --- a/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberEndpoint.java +++ b/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberEndpoint.java @@ -121,8 +121,10 @@ public ResponseEntity> list() { if (materialized.contains(entry.getKey() + "|" + value.getKey())) { continue; } - views.add(new SeriesView(entry.getKey(), value.getKey(), value.getValue(), base.prefix(), base.size(), 0, 1, - DocumentNumberService.render(base.prefix(), base.size(), 1), true, true)); + // A virtual row previews exactly what its first allocation will materialize: the + // base row's shape AND counter (the base is the tenant's seedable template). + views.add(new SeriesView(entry.getKey(), value.getKey(), value.getValue(), base.prefix(), base.size(), base.counter(), + base.counter() + 1, DocumentNumberService.render(base.prefix(), base.size(), base.counter() + 1), true, true)); } } return ResponseEntity.ok(views); diff --git a/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberStore.java b/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberStore.java index d242605223..88d9bb12b0 100644 --- a/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberStore.java +++ b/components/engine/engine-numbering/src/main/java/org/eclipse/dirigible/components/engine/numbering/DocumentNumberStore.java @@ -42,10 +42,12 @@ *
  • the management surface writes {@code PREFIX} / {@code SIZE} / the counter reset;
  • *
  • allocation writes only {@code COUNTER}, via {@code COUNTER = COUNTER + 1} - with one * deliberate exception: the first allocation for a NEW PARTITION of a declared series materializes - * that partition's row, copying the shape from the series' base ({@code ""}-partition) row. - * Partition values are data (company ids), so no artefact can pre-provision them; the base row is - * the tenant's configured default they inherit. The copy happens once, at row birth - it never - * overwrites anything.
  • + * that partition's row, copying the shape AND the counter from the series' base + * ({@code ""}-partition) row. Partition values are data (company ids), so no artefact can + * pre-provision them; the base row is the tenant's configured template they inherit - including the + * starting point, so seeding the base row's next value BEFORE the first document works (a + * zero-started partition silently ignored the seed, observed live). The copy happens once, at row + * birth - it never overwrites anything. * * *

    @@ -215,14 +217,18 @@ void provision(String series, String partition, String prefix, int size) throws if (exists(connection, series, partition)) { return; } - insertRow(connection, series, partition, prefix, size); + insertRow(connection, series, partition, prefix, size, 0L); } } /** - * Materializes a NEW partition row of a declared series, inheriting the shape of the series' base - * ({@code ""}-partition) row - the tenant's configured default. Partition values are data, so this - * is the only place a partition row can be born. + * Materializes a NEW partition row of a declared series, inheriting the shape AND the counter of + * the series' base ({@code ""}-partition) row - the tenant's configured template. The counter is + * inherited because the base row is the only thing an operator CAN seed before a partition's first + * allocation (on a fresh tenant nothing marks the series partitioned yet, so the settings page + * offers the base row's next value): starting the partition at zero instead silently discarded the + * seed and the first document rendered ...0001. Partition values are data, so this is the only + * place a partition row can be born. * * @param connection the connection (autocommit) * @param series the series identity @@ -236,11 +242,12 @@ private void materializePartition(Connection connection, String series, String p + "] is not declared for this tenant - declare it in a .numbers artefact"); } Allocation base = read(connection, series, ""); - insertRow(connection, series, partition, base.prefix(), base.size()); + insertRow(connection, series, partition, base.prefix(), base.size(), base.value()); } - /** Inserts one series row with a zero counter, tolerating a concurrent identical insert. */ - private void insertRow(Connection connection, String series, String partition, String prefix, int size) throws SQLException { + /** Inserts one series row with the given counter, tolerating a concurrent identical insert. */ + private void insertRow(Connection connection, String series, String partition, String prefix, int size, long counter) + throws SQLException { String sql = SqlFactory.getNative(connection) .insert() .into(TABLE_NAME) @@ -253,7 +260,7 @@ private void insertRow(Connection connection, String series, String partition, S try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setString(1, series); statement.setString(2, partition); - statement.setLong(3, 0L); + statement.setLong(3, counter); statement.setString(4, prefix == null ? "" : prefix); statement.setInt(5, size); statement.executeUpdate(); diff --git a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/bg-BG/shell.json b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/bg-BG/shell.json index 37063dce02..8c360d7e52 100644 --- a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/bg-BG/shell.json +++ b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/bg-BG/shell.json @@ -110,7 +110,7 @@ "numberingNext": "Следващ", "numberingExample": "Следващ номер:", "numberingSaved": "Записано", - "numberingShapeTemplate": "Броячите живеят в редовете за отделните партиции по-долу; нова партиция се появява при първата си употреба, наследявайки тази форма.", + "numberingShapeTemplate": "Броячите живеят в редовете за отделните партиции по-долу; нова партиция се появява при първата си употреба, наследявайки тази форма и началния ѝ брояч.", "tenantConfigLabels": { "name": "Име", "subtitle": "Подзаглавие", diff --git a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/en-US/shell.json b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/en-US/shell.json index 9429e0280a..821689315a 100644 --- a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/en-US/shell.json +++ b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/i18n/en-US/shell.json @@ -110,7 +110,7 @@ "numberingNext": "Next", "numberingExample": "Next number:", "numberingSaved": "Saved", - "numberingShapeTemplate": "Counters live on the per-partition rows below; a new partition appears on its first use, inheriting this shape.", + "numberingShapeTemplate": "Counters live on the per-partition rows below; a new partition appears on its first use, inheriting this shape and its starting counter.", "tenantConfigLabels": { "name": "Name", "subtitle": "Subtitle", diff --git a/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html b/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html index 2648f2d54f..eac0fd366f 100644 --- a/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html +++ b/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html @@ -217,7 +217,7 @@ diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/NumberingSdkIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/NumberingSdkIT.java index 9e3900099f..803442860b 100644 --- a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/NumberingSdkIT.java +++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/NumberingSdkIT.java @@ -267,6 +267,27 @@ void aDeclaredPartitionSourceLabelsRowsAndSeedsCountersBeforeFirstUse() throws E }, ASSERTION_TIMEOUT_SECONDS); } + @Test + void aFreshPartitionInheritsTheBaseRowsSeededCounter() { + publishDeclarationAndController(); + + restAssuredExecutor.execute(() -> { + // On a fresh tenant nothing marks the series partitioned yet, so the base row is the only + // thing an operator CAN seed before the first document. Seed it... + given().contentType(ContentType.JSON) + .body("{\"series\": \"" + SERIES + "\", \"partition\": \"\", \"next\": 300}") + .when() + .put("/services/core/numbering") + .then() + .statusCode(204); + // ...and the FIRST allocation of a brand-new partition must render exactly the seed - the + // partition materializes inheriting the base row's shape AND counter (a zero-started + // partition silently discarded the seed: the first document rendered ...0001). + assertEquals("T-0300", allocate("/next/FRESHSEED"), "a fresh partition's first number is the base row's seeded next value"); + assertEquals("T-0301", allocate("/next/FRESHSEED"), "and it continues from there"); + }, ASSERTION_TIMEOUT_SECONDS); + } + @Test void aNewTenantGetsTheDeclaredSeriesWithItsOwnSequence() throws Exception { publishDeclarationAndController();