This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 3e5bbfaa14 [Fix][API] Correct nested row byte accounting (#12305)
3e5bbfaa14 is described below
commit 3e5bbfaa14bee630b5a60ad9887c304d48c544db
Author: Goutam Adwant <[email protected]>
AuthorDate: Sat Oct 24 15:46:19 2026 +0000
[Fix][API] Correct nested row byte accounting (#12305)
Signed-off-by: Goutam Adwant <[email protected]>
---
docs/en/introduction/configuration/speed-limit.md | 4 +
docs/zh/introduction/configuration/speed-limit.md | 4 +
.../seatunnel/api/table/type/SeaTunnelRow.java | 6 +
.../seatunnel/api/table/type/SeaTunnelRowTest.java | 90 ++++++++++++++
.../server/master/NestedRowAccountingTest.java | 56 +++++++++
.../SeaTunnelSourceCollectorRowAccountingTest.java | 136 +++++++++++++++++++++
.../resources/batch_nested_rows_to_console.conf | 46 +++++++
.../format/json/JsonRowDataSerDeSchemaTest.java | 48 ++++++++
8 files changed, 390 insertions(+)
diff --git a/docs/en/introduction/configuration/speed-limit.md
b/docs/en/introduction/configuration/speed-limit.md
index 87379e5b75..59f6c5082e 100644
--- a/docs/en/introduction/configuration/speed-limit.md
+++ b/docs/en/introduction/configuration/speed-limit.md
@@ -7,6 +7,10 @@ This functionality is essential when you need to ensure
efficient and controlled
The speed control is primarily governed by two key parameters:
`read_limit.rows_per_second` and `read_limit.bytes_per_second`.
This document will guide you through the usage of these parameters and how to
leverage them effectively.
+In Zeta, the byte limit uses SeaTunnel's row-size estimate, not the exact
serialized or network byte count.
+For arrays of maps or rows, null elements contribute zero and non-null
elements contribute their contents.
+Correcting previously omitted row-array contents can increase reported byte
metrics and reduce throughput under the same byte limit.
+
## Support Those Engines
> SeaTunnel Zeta<br/>
diff --git a/docs/zh/introduction/configuration/speed-limit.md
b/docs/zh/introduction/configuration/speed-limit.md
index 51007269dd..5e0f6f02a0 100644
--- a/docs/zh/introduction/configuration/speed-limit.md
+++ b/docs/zh/introduction/configuration/speed-limit.md
@@ -6,6 +6,10 @@ SeaTunnel提供了强大的速度控制功能允许你管理数据同步的速
速度控制主要由两个关键参数控制:`read_limit.rows_per_second` 和 `read_limit.bytes_per_second`。
本文档将指导您如何使用这些参数以及如何有效地利用它们。
+在 Zeta 中,字节限速使用 SeaTunnel 的行大小估算值,而不是精确的序列化大小或网络传输字节数。
+对于 Map 数组或 Row 数组,null 元素按零计算,非 null 元素按其内容计算。
+修正此前遗漏的 Row 数组内容后,报告的字节指标可能增大,在相同字节限速下吞吐量可能降低。
+
## 支持这些引擎
> SeaTunnel Zeta<br/>
diff --git
a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java
b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java
index e6b9d545a7..3acb5b2ed7 100644
---
a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java
+++
b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java
@@ -260,6 +260,8 @@ public final class SeaTunnelRow implements Serializable {
case DOUBLE:
return getArrayNotNullSize((Double[]) v) * 8;
case ARRAY:
+ case ROW:
+ // Nested elements need their declared type to account for
their contents.
int total = 0;
for (Object elem : (Object[]) v) {
total += getBytesForValue(elem, dataType);
@@ -287,6 +289,10 @@ public final class SeaTunnelRow implements Serializable {
int size = 0;
if (Objects.nonNull(v)) {
for (Map o : (Map[]) v) {
+ if (o == null) {
+ // Null array elements contribute no payload bytes, like
other nullable values.
+ continue;
+ }
for (Map.Entry<?, ?> entry : ((Map<?, ?>) o).entrySet()) {
size += getBytesForValue(entry.getKey()) +
getBytesForValue(entry.getValue());
}
diff --git
a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/type/SeaTunnelRowTest.java
b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/type/SeaTunnelRowTest.java
index 8dcb8b2672..3453c4e469 100644
---
a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/type/SeaTunnelRowTest.java
+++
b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/type/SeaTunnelRowTest.java
@@ -242,6 +242,96 @@ public class SeaTunnelRowTest {
Assertions.assertEquals(8, row.getBytesSize());
}
+ @Test
+ void testNullableMapArraySize() {
+ Map<String, Integer> map = new LinkedHashMap<>();
+ map.put("a", 1);
+ map.put("b", null);
+ map.put(null, 2);
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"items"},
+ new SeaTunnelDataType<?>[] {
+ new ArrayType<>(
+ Map[].class,
+ new MapType<>(BasicType.STRING_TYPE,
BasicType.INT_TYPE))
+ });
+
+ assertSizeInBothOrders(
+ new Object[] {new Map[] {null, map, null,
Collections.singletonMap("c", 3), null}},
+ rowType,
+ 15);
+ assertSizeInBothOrders(new Object[] {new Map[] {null, null}}, rowType,
0);
+ assertSizeInBothOrders(new Object[] {new Map[0]}, rowType, 0);
+ assertSizeInBothOrders(new Object[] {null}, rowType, 0);
+ }
+
+ @Test
+ void testNullableRowArraySize() {
+ SeaTunnelRowType elementType =
+ new SeaTunnelRowType(
+ new String[] {"value"}, new SeaTunnelDataType<?>[]
{BasicType.STRING_TYPE});
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"id", "items"},
+ new SeaTunnelDataType<?>[] {
+ BasicType.INT_TYPE, new
ArrayType<>(SeaTunnelRow[].class, elementType)
+ });
+
+ assertSizeInBothOrders(
+ new Object[] {
+ 7,
+ new SeaTunnelRow[] {
+ null,
+ new SeaTunnelRow(new Object[] {"abcd"}),
+ new SeaTunnelRow(new Object[] {null}),
+ null
+ }
+ },
+ rowType,
+ 8);
+ assertSizeInBothOrders(new Object[] {7, new SeaTunnelRow[] {null}},
rowType, 4);
+ assertSizeInBothOrders(new Object[] {7, new SeaTunnelRow[0]}, rowType,
4);
+ assertSizeInBothOrders(new Object[] {7, null}, rowType, 4);
+ }
+
+ @Test
+ void testNestedRowArraySize() {
+ SeaTunnelRowType elementType =
+ new SeaTunnelRowType(
+ new String[] {"items"},
+ new SeaTunnelDataType<?>[] {
+ new ArrayType<>(
+ Map[].class,
+ new MapType<>(BasicType.STRING_TYPE,
BasicType.INT_TYPE))
+ });
+ ArrayType<?, ?> rowArrayType = new ArrayType<>(SeaTunnelRow[].class,
elementType);
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"groups"},
+ new SeaTunnelDataType<?>[] {
+ new ArrayType<>(SeaTunnelRow[][].class,
rowArrayType)
+ });
+ SeaTunnelRow element =
+ new SeaTunnelRow(new Object[] {new Map[] {null,
Collections.singletonMap("a", 1)}});
+
+ assertSizeInBothOrders(
+ new Object[] {new SeaTunnelRow[][] {null, new SeaTunnelRow[]
{null, element}}},
+ rowType,
+ 5);
+ }
+
+ private static void assertSizeInBothOrders(
+ Object[] fields, SeaTunnelRowType rowType, int expectedSize) {
+ SeaTunnelRow typedFirst = new SeaTunnelRow(fields);
+ Assertions.assertEquals(expectedSize,
typedFirst.getBytesSize(rowType));
+ Assertions.assertEquals(expectedSize, typedFirst.getBytesSize());
+ SeaTunnelRow untypedFirst = new SeaTunnelRow(fields);
+ Assertions.assertEquals(expectedSize, untypedFirst.getBytesSize());
+ Assertions.assertEquals(expectedSize,
untypedFirst.getBytesSize(rowType));
+ Assertions.assertSame(fields, typedFirst.getFields());
+ }
+
@Test
void testWithMapInterface() {
Map<String, String> map = Collections.singletonMap("key", "value");
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/master/NestedRowAccountingTest.java
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/master/NestedRowAccountingTest.java
new file mode 100644
index 0000000000..d2d5d29668
--- /dev/null
+++
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/master/NestedRowAccountingTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.master;
+
+import org.apache.seatunnel.api.common.metrics.JobMetrics;
+import org.apache.seatunnel.engine.common.job.JobStatus;
+import org.apache.seatunnel.engine.server.AbstractSeaTunnelServerTest;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static
org.apache.seatunnel.api.common.metrics.MetricNames.SINK_WRITE_BYTES;
+import static
org.apache.seatunnel.api.common.metrics.MetricNames.SINK_WRITE_COUNT;
+import static
org.apache.seatunnel.api.common.metrics.MetricNames.SOURCE_RECEIVED_BYTES;
+import static
org.apache.seatunnel.api.common.metrics.MetricNames.SOURCE_RECEIVED_COUNT;
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class NestedRowAccountingTest extends
AbstractSeaTunnelServerTest<NestedRowAccountingTest> {
+
+ @Test
+ void testNullableMapArraysReachSinkWithCorrectByteMetrics() {
+ long jobId = System.currentTimeMillis();
+ startJob(jobId, "batch_nested_rows_to_console.conf", false);
+
+ await().atMost(60, TimeUnit.SECONDS)
+ .untilAsserted(
+ () -> {
+ assertEquals(
+ JobStatus.FINISHED,
+
server.getCoordinatorService().getJobStatus(jobId));
+ JobMetrics metrics =
+
server.getCoordinatorService().getJobMetrics(jobId);
+ assertEquals(3L,
metrics.get(SOURCE_RECEIVED_COUNT).get(0).value());
+ assertEquals(3L,
metrics.get(SINK_WRITE_COUNT).get(0).value());
+ assertEquals(17L,
metrics.get(SOURCE_RECEIVED_BYTES).get(0).value());
+ assertEquals(17L,
metrics.get(SINK_WRITE_BYTES).get(0).value());
+ });
+ }
+}
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/task/SeaTunnelSourceCollectorRowAccountingTest.java
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/task/SeaTunnelSourceCollectorRowAccountingTest.java
new file mode 100644
index 0000000000..a8ee41c8e1
--- /dev/null
+++
b/seatunnel-engine/seatunnel-engine-server/src/test/java/org/apache/seatunnel/engine/server/task/SeaTunnelSourceCollectorRowAccountingTest.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.engine.server.task;
+
+import org.apache.seatunnel.api.common.metrics.Counter;
+import org.apache.seatunnel.api.common.metrics.Meter;
+import org.apache.seatunnel.api.common.metrics.MetricsContext;
+import org.apache.seatunnel.api.table.catalog.TablePath;
+import org.apache.seatunnel.api.table.type.ArrayType;
+import org.apache.seatunnel.api.table.type.BasicType;
+import org.apache.seatunnel.api.table.type.MapType;
+import org.apache.seatunnel.api.table.type.MultipleRowType;
+import org.apache.seatunnel.api.table.type.Record;
+import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
+import org.apache.seatunnel.api.table.type.SeaTunnelRow;
+import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import org.apache.seatunnel.core.starter.flowcontrol.FlowControlStrategy;
+import org.apache.seatunnel.engine.common.config.EngineConfig;
+import org.apache.seatunnel.engine.server.execution.TaskGroupLocation;
+import org.apache.seatunnel.engine.server.execution.TaskLocation;
+import org.apache.seatunnel.engine.server.task.flow.OneInputFlowLifeCycle;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static
org.apache.seatunnel.api.common.metrics.MetricNames.SOURCE_RECEIVED_BYTES;
+import static
org.apache.seatunnel.api.common.metrics.MetricNames.SOURCE_RECEIVED_COUNT;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class SeaTunnelSourceCollectorRowAccountingTest {
+
+ @Test
+ void testNullableMapArrayCollection() throws Exception {
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"items"},
+ new SeaTunnelDataType<?>[] {
+ new ArrayType<>(
+ Map[].class,
+ new MapType<>(BasicType.STRING_TYPE,
BasicType.INT_TYPE))
+ });
+ assertCollected(
+ new Object[] {new Map[] {null, Collections.singletonMap("a",
1)}}, rowType, 5);
+ assertCollected(new Object[] {new Map[] {null}}, rowType, 0);
+ }
+
+ @Test
+ void testRowArrayCollection() throws Exception {
+ SeaTunnelRowType elementType =
+ new SeaTunnelRowType(
+ new String[] {"value"}, new SeaTunnelDataType<?>[]
{BasicType.STRING_TYPE});
+ SeaTunnelRowType rowType =
+ new SeaTunnelRowType(
+ new String[] {"id", "items"},
+ new SeaTunnelDataType<?>[] {
+ BasicType.INT_TYPE, new
ArrayType<>(SeaTunnelRow[].class, elementType)
+ });
+ assertCollected(
+ new Object[] {
+ 7, new SeaTunnelRow[] {null, new SeaTunnelRow(new Object[]
{"abcd"})}
+ },
+ rowType,
+ 8);
+ }
+
+ private void assertCollected(Object[] fields, SeaTunnelRowType rowType,
long expectedBytes)
+ throws Exception {
+ TablePath tablePath = TablePath.of("database", "table");
+ for (boolean multiTable : new boolean[] {false, true}) {
+ SeaTunnelRow row = new SeaTunnelRow(fields);
+ row.setTableId(tablePath.getFullName());
+ MetricsContext metrics = Mockito.mock(MetricsContext.class);
+ when(metrics.counter(Mockito.anyString()))
+ .thenAnswer(invocation -> Mockito.mock(Counter.class));
+ when(metrics.meter(Mockito.anyString()))
+ .thenAnswer(invocation -> Mockito.mock(Meter.class));
+ Counter bytes = Mockito.mock(Counter.class);
+ Counter count = Mockito.mock(Counter.class);
+ Counter tableBytes = Mockito.mock(Counter.class);
+ when(metrics.counter(SOURCE_RECEIVED_BYTES)).thenReturn(bytes);
+ when(metrics.counter(SOURCE_RECEIVED_COUNT)).thenReturn(count);
+ when(metrics.counter(SOURCE_RECEIVED_BYTES + "#" +
tablePath.getFullName()))
+ .thenReturn(tableBytes);
+ OneInputFlowLifeCycle<Record<?>> output =
Mockito.mock(OneInputFlowLifeCycle.class);
+ SeaTunnelTask sourceTask = Mockito.mock(SeaTunnelTask.class);
+ when(sourceTask.getTaskLocation())
+ .thenReturn(new TaskLocation(new TaskGroupLocation(1L, 1,
1L), 1L, 0));
+ SeaTunnelSourceCollector<SeaTunnelRow> collector =
+ new SeaTunnelSourceCollector<>(
+ new Object(),
+ Collections.singletonList(output),
+ metrics,
+ FlowControlStrategy.ofBytes(1000),
+ multiTable
+ ? new MultipleRowType(
+ Collections.singletonMap(
+ tablePath.getFullName(),
rowType))
+ : rowType,
+ multiTable
+ ? Collections.singletonList(tablePath)
+ : Collections.emptyList(),
+ sourceTask,
+ new EngineConfig(),
+ Collections.emptyMap(),
+ null);
+
+ collector.collect(row);
+
+ verify(bytes).inc(expectedBytes);
+ verify(count).inc();
+ if (multiTable) {
+ verify(tableBytes).inc(expectedBytes);
+ }
+ verify(output).received(Mockito.argThat(record -> record.getData()
== row));
+ }
+ }
+}
diff --git
a/seatunnel-engine/seatunnel-engine-server/src/test/resources/batch_nested_rows_to_console.conf
b/seatunnel-engine/seatunnel-engine-server/src/test/resources/batch_nested_rows_to_console.conf
new file mode 100644
index 0000000000..f6703f276b
--- /dev/null
+++
b/seatunnel-engine/seatunnel-engine-server/src/test/resources/batch_nested_rows_to_console.conf
@@ -0,0 +1,46 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+ read_limit.bytes_per_second = 1000
+}
+
+source {
+ FakeSource {
+ row.num = 3
+ split.num = 1
+ schema = {
+ fields {
+ id = "int"
+ maps = "array<map<string,int>>"
+ }
+ }
+ rows = [
+ { kind = INSERT, fields = { id = 1, maps = [null, {a = 1}] } },
+ { kind = INSERT, fields = { id = 2, maps = [null] } },
+ { kind = INSERT, fields = { id = 3, maps = [] } }
+ ]
+ }
+}
+
+sink {
+ Console {
+ log.print.data = false
+ }
+}
diff --git
a/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
b/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
index b719b5b814..8e333dd777 100644
---
a/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
+++
b/seatunnel-formats/seatunnel-format-json/src/test/java/org/apache/seatunnel/format/json/JsonRowDataSerDeSchemaTest.java
@@ -98,6 +98,54 @@ public class JsonRowDataSerDeSchemaTest {
new String[] {"id", "outer", "nested"},
new SeaTunnelDataType<?>[] {INT_TYPE, MAP_ARRAY_TYPE,
ARRAY_MAP_TYPE});
+ @Test
+ void testNullableMapArrayAccountingAfterDeserialization() throws Exception
{
+ SeaTunnelRowType schema =
+ new SeaTunnelRowType(
+ new String[] {"items"},
+ new SeaTunnelDataType<?>[] {
+ new ArrayType<>(Map[].class, new
MapType<>(STRING_TYPE, INT_TYPE))
+ });
+ JsonDeserializationSchema deserializer =
+ new JsonDeserializationSchema(
+ CatalogTableUtil.getCatalogTable("", "", "", "test",
schema), false, false);
+ byte[] json =
"{\"items\":[null,{\"a\":1}]}".getBytes(StandardCharsets.UTF_8);
+
+ SeaTunnelRow row = deserializer.deserialize(json);
+ Map<?, ?>[] items = (Map<?, ?>[]) row.getField(0);
+ assertNull(items[0]);
+ assertEquals(1, items[1].get("a"));
+ assertEquals(5, row.getBytesSize(schema));
+ assertEquals(5, row.getBytesSize());
+ assertEquals(5, deserializer.deserialize(json).getBytesSize());
+ }
+
+ @Test
+ void testRowArrayAccountingAfterDeserialization() throws Exception {
+ SeaTunnelRowType elementType =
+ new SeaTunnelRowType(
+ new String[] {"value"}, new SeaTunnelDataType<?>[]
{STRING_TYPE});
+ SeaTunnelRowType schema =
+ new SeaTunnelRowType(
+ new String[] {"id", "items"},
+ new SeaTunnelDataType<?>[] {
+ INT_TYPE, new ArrayType<>(SeaTunnelRow[].class,
elementType)
+ });
+ JsonDeserializationSchema deserializer =
+ new JsonDeserializationSchema(
+ CatalogTableUtil.getCatalogTable("", "", "", "test",
schema), false, false);
+ byte[] json =
+
"{\"id\":7,\"items\":[null,{\"value\":\"abcd\"}]}".getBytes(StandardCharsets.UTF_8);
+
+ SeaTunnelRow row = deserializer.deserialize(json);
+ SeaTunnelRow[] items = (SeaTunnelRow[]) row.getField(1);
+ assertNull(items[0]);
+ assertEquals("abcd", items[1].getField(0));
+ assertEquals(8, row.getBytesSize(schema));
+ assertEquals(8, row.getBytesSize());
+ assertEquals(8, deserializer.deserialize(json).getBytesSize());
+ }
+
@Test
public void testTimestampTzSerializedAsSessionWallClock() {
// Issue #10795: with serializeTimestampTzAsLocal=true (used by the
Doris