From be3834c518ca21eeda2e327efc62af2cafe5a1f2 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 17 Jul 2026 11:45:40 +0200 Subject: [PATCH 1/2] feat(database): Apply Data Collection query policy Suppress SQL statement descriptions when database query data collection is disabled while retaining database system, name, timing, status, and other structural span metadata. Preserve existing statement collection when Data Collection is absent. Co-Authored-By: Claude --- .../sentry/android/sqlite/OpenHelperSpans.kt | 13 +++++++++-- .../main/java/io/sentry/sqlite/DriverSpans.kt | 5 +++- .../android/sqlite/OpenHelperSpansTest.kt | 22 ++++++++++++++++++ .../java/io/sentry/sqlite/DriverSpansTest.kt | 22 ++++++++++++++++++ .../sentry/jdbc/SentryJdbcEventListener.java | 6 ++++- .../jdbc/SentryJdbcEventListenerTest.kt | 23 +++++++++++++++++++ sentry/api/sentry.api | 1 + .../io/sentry/DataCollectionResolver.java | 4 ++++ .../io/sentry/DataCollectionResolverTest.kt | 11 +++++++++ 9 files changed, 103 insertions(+), 4 deletions(-) diff --git a/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt b/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt index 059eb1bb1b5..4fe75ef4d28 100644 --- a/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt +++ b/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt @@ -6,6 +6,7 @@ import io.sentry.IScopes import io.sentry.ISpan import io.sentry.Instrumenter import io.sentry.ScopesAdapter +import io.sentry.SentryDate import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryStackTraceFactory import io.sentry.SpanDataConvention @@ -46,12 +47,12 @@ internal class OpenHelperSpans( if (result is CrossProcessCursor) { return SentryCrossProcessCursor(result, this, sql) as T } - span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) + span = startSpan(sql, startTimestamp) span?.spanContext?.origin = TRACE_ORIGIN span?.status = SpanStatus.OK result } catch (e: Throwable) { - span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) + span = startSpan(sql, startTimestamp) span?.spanContext?.origin = TRACE_ORIGIN span?.status = SpanStatus.INTERNAL_ERROR span?.throwable = e @@ -76,4 +77,12 @@ internal class OpenHelperSpans( } } } + + private fun startSpan(sql: String, startTimestamp: SentryDate): ISpan? = + scopes.span?.startChild( + "db.sql.query", + sql.takeIf { scopes.options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways }, + startTimestamp, + Instrumenter.SENTRY, + ) } diff --git a/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt b/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt index b3c0eb7c713..fe2b15a33bb 100644 --- a/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt +++ b/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt @@ -50,7 +50,10 @@ internal class DriverSpans(private val scopes: IScopes, private val dbMetadata: val startTimestamp = SentryLongDate(startTimestampNanos) val endTimestamp = SentryLongDate(startTimestampNanos + durationNanos) - parent.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY).apply { + val description = sql.takeIf { + scopes.options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways + } + parent.startChild("db.sql.query", description, startTimestamp, Instrumenter.SENTRY).apply { spanContext.origin = SQLITE_TRACE_ORIGIN throwable?.let { this.throwable = it } diff --git a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt index 0552094838e..8b442c59ee5 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt @@ -66,6 +66,28 @@ class OpenHelperSpansTest { assertTrue(span.isFinished) } + @Test + fun `performSql omits description when database query data is disabled`() { + val sut = fixture.getSut() + fixture.options.dataCollection.setDatabaseQueryData(false) + + sut.performSql("SELECT secret FROM users") {} + + val span = fixture.sentryTracer.children.first() + assertNull(span.description) + assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY]) + } + + @Test + fun `performSql keeps description in legacy mode`() { + val sut = fixture.getSut() + fixture.options.isSendDefaultPii = false + + sut.performSql("SELECT secret FROM users") {} + + assertEquals("SELECT secret FROM users", fixture.sentryTracer.children.first().description) + } + @Test fun `performSql does not create a span if no span is running`() { val sut = fixture.getSut(isSpanActive = false) diff --git a/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt index 319fc20d7ce..2265d10aa75 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt @@ -129,6 +129,28 @@ class DriverSpansTest { assertTrue(span.isFinished) } + @Test + fun `record method omits description when database query data is disabled`() { + val sut = fixture.getSut() + fixture.options.dataCollection.setDatabaseQueryData(false) + + sut.record("SELECT secret FROM users", sut.startTimestamp(), 1_000_000, SpanStatus.OK) + + val span = fixture.sentryTracer.children.first() + assertNull(span.description) + assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY]) + } + + @Test + fun `record method keeps description in legacy mode`() { + val sut = fixture.getSut() + fixture.options.isSendDefaultPii = false + + sut.record("SELECT secret FROM users", sut.startTimestamp(), 1_000_000, SpanStatus.OK) + + assertEquals("SELECT secret FROM users", fixture.sentryTracer.children.first().description) + } + @Test fun `record method sets finishDate equal to startDate + durationNanos`() { val sut = fixture.getSut() diff --git a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java index 4206de18002..59e50efae26 100644 --- a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java +++ b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java @@ -47,7 +47,11 @@ public SentryJdbcEventListener() { @Override public void onBeforeAnyExecute(final @NotNull StatementInformation statementInformation) { - startSpan(CURRENT_QUERY_SPAN, "db.query", statementInformation.getSql()); + final @Nullable String description = + scopes.getOptions().getDataCollectionResolver().isDatabaseQueryDataWithLegacyAlways() + ? statementInformation.getSql() + : null; + startSpan(CURRENT_QUERY_SPAN, "db.query", description); } @Override diff --git a/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt b/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt index 22ee97e5d47..436bc4abf62 100644 --- a/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt +++ b/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt @@ -90,6 +90,29 @@ class SentryJdbcEventListenerTest { assertEquals("INSERT INTO foo VALUES (2)", fixture.tx.children[1].description) } + @Test + fun `omits query description when database query data is disabled`() { + val sut = fixture.getSut() + fixture.options.dataCollection.setDatabaseQueryData(false) + + sut.connection.use { it.prepareStatement("INSERT INTO foo VALUES (1)").executeUpdate() } + + assertEquals(1, fixture.tx.children.size) + assertEquals(null, fixture.tx.children.first().description) + assertEquals("hsqldb", fixture.tx.children.first().data[DB_SYSTEM_KEY]) + assertEquals("testdb", fixture.tx.children.first().data[DB_NAME_KEY]) + } + + @Test + fun `legacy mode keeps query description when sendDefaultPii is false`() { + val sut = fixture.getSut() + fixture.options.isSendDefaultPii = false + + sut.connection.use { it.prepareStatement("INSERT INTO foo VALUES (1)").executeUpdate() } + + assertEquals("INSERT INTO foo VALUES (1)", fixture.tx.children.first().description) + } + @Test fun `creates spans for calls resulting in error`() { val sut = fixture.getSut(existingRow = 1) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 4ab82709159..db9547700dc 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -424,6 +424,7 @@ public final class io/sentry/DataCollectionResolver { public fun getQueryParams ()Lio/sentry/KeyValueCollectionBehavior; public fun isDataCollectionConfigured ()Z public fun isDatabaseQueryData ()Z + public fun isDatabaseQueryDataWithLegacyAlways ()Z public fun isGraphqlDocument ()Z public fun isGraphqlDocumentWithLegacyAlways ()Z public fun isGraphqlDocumentWithLegacyBodyGate ()Z diff --git a/sentry/src/main/java/io/sentry/DataCollectionResolver.java b/sentry/src/main/java/io/sentry/DataCollectionResolver.java index cdfb0649188..a293614eb2b 100644 --- a/sentry/src/main/java/io/sentry/DataCollectionResolver.java +++ b/sentry/src/main/java/io/sentry/DataCollectionResolver.java @@ -31,6 +31,10 @@ public boolean isDatabaseQueryData() { return explicitOrSendDefaultPii(options.getDataCollection().getDatabaseQueryData(), true); } + public boolean isDatabaseQueryDataWithLegacyAlways() { + return explicitOrDefault(options.getDataCollection().getDatabaseQueryData(), true, true); + } + public boolean isGraphqlDocument() { return explicitOrSendDefaultPii(options.getDataCollection().getGraphql().getDocument(), true); } diff --git a/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt b/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt index 47f637f0a08..fb525ce43d2 100644 --- a/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt +++ b/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt @@ -78,6 +78,17 @@ class DataCollectionResolverTest { assertThat(options.dataCollectionResolver.isDatabaseQueryData).isFalse() } + @Test + fun `database query data legacy always variant preserves collection when namespace is absent`() { + val options = SentryOptions().apply { isSendDefaultPii = false } + + assertThat(options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways).isTrue() + + options.dataCollection.setDatabaseQueryData(false) + + assertThat(options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways).isFalse() + } + @Test fun `GraphQL document falls back to sendDefaultPii and override takes precedence`() { val options = SentryOptions().apply { isSendDefaultPii = true } From f1f23e18c06c602f3bd0f751a1b4dbdce1324c95 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 28 Aug 2026 09:40:34 +0200 Subject: [PATCH 2/2] fix(database): Preserve query descriptions Keep sanitized or parameterized query text independent of databaseQueryData. The option only controls bound parameters, write payloads, and result data, which the current JDBC and SQLite integrations do not collect. Remove the unused legacy resolver path and its policy-specific tests. Co-Authored-By: Claude --- .../sentry/android/sqlite/OpenHelperSpans.kt | 13 ++--------- .../main/java/io/sentry/sqlite/DriverSpans.kt | 5 +--- .../android/sqlite/OpenHelperSpansTest.kt | 22 ------------------ .../java/io/sentry/sqlite/DriverSpansTest.kt | 22 ------------------ .../sentry/jdbc/SentryJdbcEventListener.java | 6 +---- .../jdbc/SentryJdbcEventListenerTest.kt | 23 ------------------- sentry/api/sentry.api | 1 - .../io/sentry/DataCollectionResolver.java | 4 ---- .../io/sentry/DataCollectionResolverTest.kt | 11 --------- 9 files changed, 4 insertions(+), 103 deletions(-) diff --git a/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt b/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt index 4fe75ef4d28..059eb1bb1b5 100644 --- a/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt +++ b/sentry-android-sqlite/src/main/java/io/sentry/android/sqlite/OpenHelperSpans.kt @@ -6,7 +6,6 @@ import io.sentry.IScopes import io.sentry.ISpan import io.sentry.Instrumenter import io.sentry.ScopesAdapter -import io.sentry.SentryDate import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryStackTraceFactory import io.sentry.SpanDataConvention @@ -47,12 +46,12 @@ internal class OpenHelperSpans( if (result is CrossProcessCursor) { return SentryCrossProcessCursor(result, this, sql) as T } - span = startSpan(sql, startTimestamp) + span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) span?.spanContext?.origin = TRACE_ORIGIN span?.status = SpanStatus.OK result } catch (e: Throwable) { - span = startSpan(sql, startTimestamp) + span = scopes.span?.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY) span?.spanContext?.origin = TRACE_ORIGIN span?.status = SpanStatus.INTERNAL_ERROR span?.throwable = e @@ -77,12 +76,4 @@ internal class OpenHelperSpans( } } } - - private fun startSpan(sql: String, startTimestamp: SentryDate): ISpan? = - scopes.span?.startChild( - "db.sql.query", - sql.takeIf { scopes.options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways }, - startTimestamp, - Instrumenter.SENTRY, - ) } diff --git a/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt b/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt index fe2b15a33bb..b3c0eb7c713 100644 --- a/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt +++ b/sentry-android-sqlite/src/main/java/io/sentry/sqlite/DriverSpans.kt @@ -50,10 +50,7 @@ internal class DriverSpans(private val scopes: IScopes, private val dbMetadata: val startTimestamp = SentryLongDate(startTimestampNanos) val endTimestamp = SentryLongDate(startTimestampNanos + durationNanos) - val description = sql.takeIf { - scopes.options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways - } - parent.startChild("db.sql.query", description, startTimestamp, Instrumenter.SENTRY).apply { + parent.startChild("db.sql.query", sql, startTimestamp, Instrumenter.SENTRY).apply { spanContext.origin = SQLITE_TRACE_ORIGIN throwable?.let { this.throwable = it } diff --git a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt index 8b442c59ee5..0552094838e 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/android/sqlite/OpenHelperSpansTest.kt @@ -66,28 +66,6 @@ class OpenHelperSpansTest { assertTrue(span.isFinished) } - @Test - fun `performSql omits description when database query data is disabled`() { - val sut = fixture.getSut() - fixture.options.dataCollection.setDatabaseQueryData(false) - - sut.performSql("SELECT secret FROM users") {} - - val span = fixture.sentryTracer.children.first() - assertNull(span.description) - assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY]) - } - - @Test - fun `performSql keeps description in legacy mode`() { - val sut = fixture.getSut() - fixture.options.isSendDefaultPii = false - - sut.performSql("SELECT secret FROM users") {} - - assertEquals("SELECT secret FROM users", fixture.sentryTracer.children.first().description) - } - @Test fun `performSql does not create a span if no span is running`() { val sut = fixture.getSut(isSpanActive = false) diff --git a/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt b/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt index 2265d10aa75..319fc20d7ce 100644 --- a/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt +++ b/sentry-android-sqlite/src/test/java/io/sentry/sqlite/DriverSpansTest.kt @@ -129,28 +129,6 @@ class DriverSpansTest { assertTrue(span.isFinished) } - @Test - fun `record method omits description when database query data is disabled`() { - val sut = fixture.getSut() - fixture.options.dataCollection.setDatabaseQueryData(false) - - sut.record("SELECT secret FROM users", sut.startTimestamp(), 1_000_000, SpanStatus.OK) - - val span = fixture.sentryTracer.children.first() - assertNull(span.description) - assertEquals("in-memory", span.data[SpanDataConvention.DB_SYSTEM_KEY]) - } - - @Test - fun `record method keeps description in legacy mode`() { - val sut = fixture.getSut() - fixture.options.isSendDefaultPii = false - - sut.record("SELECT secret FROM users", sut.startTimestamp(), 1_000_000, SpanStatus.OK) - - assertEquals("SELECT secret FROM users", fixture.sentryTracer.children.first().description) - } - @Test fun `record method sets finishDate equal to startDate + durationNanos`() { val sut = fixture.getSut() diff --git a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java index 59e50efae26..4206de18002 100644 --- a/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java +++ b/sentry-jdbc/src/main/java/io/sentry/jdbc/SentryJdbcEventListener.java @@ -47,11 +47,7 @@ public SentryJdbcEventListener() { @Override public void onBeforeAnyExecute(final @NotNull StatementInformation statementInformation) { - final @Nullable String description = - scopes.getOptions().getDataCollectionResolver().isDatabaseQueryDataWithLegacyAlways() - ? statementInformation.getSql() - : null; - startSpan(CURRENT_QUERY_SPAN, "db.query", description); + startSpan(CURRENT_QUERY_SPAN, "db.query", statementInformation.getSql()); } @Override diff --git a/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt b/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt index 436bc4abf62..22ee97e5d47 100644 --- a/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt +++ b/sentry-jdbc/src/test/kotlin/io/sentry/jdbc/SentryJdbcEventListenerTest.kt @@ -90,29 +90,6 @@ class SentryJdbcEventListenerTest { assertEquals("INSERT INTO foo VALUES (2)", fixture.tx.children[1].description) } - @Test - fun `omits query description when database query data is disabled`() { - val sut = fixture.getSut() - fixture.options.dataCollection.setDatabaseQueryData(false) - - sut.connection.use { it.prepareStatement("INSERT INTO foo VALUES (1)").executeUpdate() } - - assertEquals(1, fixture.tx.children.size) - assertEquals(null, fixture.tx.children.first().description) - assertEquals("hsqldb", fixture.tx.children.first().data[DB_SYSTEM_KEY]) - assertEquals("testdb", fixture.tx.children.first().data[DB_NAME_KEY]) - } - - @Test - fun `legacy mode keeps query description when sendDefaultPii is false`() { - val sut = fixture.getSut() - fixture.options.isSendDefaultPii = false - - sut.connection.use { it.prepareStatement("INSERT INTO foo VALUES (1)").executeUpdate() } - - assertEquals("INSERT INTO foo VALUES (1)", fixture.tx.children.first().description) - } - @Test fun `creates spans for calls resulting in error`() { val sut = fixture.getSut(existingRow = 1) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index cd99b769e03..1885b42e6f0 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -422,7 +422,6 @@ public final class io/sentry/DataCollectionResolver { public fun getUrlQueryParams ()Lio/sentry/KeyValueCollectionBehavior; public fun isDataCollectionConfigured ()Z public fun isDatabaseQueryData ()Z - public fun isDatabaseQueryDataWithLegacyAlways ()Z public fun isGraphqlDocument ()Z public fun isGraphqlDocumentWithLegacyAlways ()Z public fun isGraphqlDocumentWithLegacyBodyGate ()Z diff --git a/sentry/src/main/java/io/sentry/DataCollectionResolver.java b/sentry/src/main/java/io/sentry/DataCollectionResolver.java index e3c05a91a5a..32f642cd871 100644 --- a/sentry/src/main/java/io/sentry/DataCollectionResolver.java +++ b/sentry/src/main/java/io/sentry/DataCollectionResolver.java @@ -31,10 +31,6 @@ public boolean isDatabaseQueryData() { return explicitOrSendDefaultPii(options.getDataCollection().getDatabaseQueryData(), true); } - public boolean isDatabaseQueryDataWithLegacyAlways() { - return explicitOrDefault(options.getDataCollection().getDatabaseQueryData(), true, true); - } - public boolean isGraphqlDocument() { return explicitOrSendDefaultPii(options.getDataCollection().getGraphql().getDocument(), true); } diff --git a/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt b/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt index 4c755db6296..3080ea86226 100644 --- a/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt +++ b/sentry/src/test/java/io/sentry/DataCollectionResolverTest.kt @@ -78,17 +78,6 @@ class DataCollectionResolverTest { assertThat(options.dataCollectionResolver.isDatabaseQueryData).isFalse() } - @Test - fun `database query data legacy always variant preserves collection when namespace is absent`() { - val options = SentryOptions().apply { isSendDefaultPii = false } - - assertThat(options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways).isTrue() - - options.dataCollection.setDatabaseQueryData(false) - - assertThat(options.dataCollectionResolver.isDatabaseQueryDataWithLegacyAlways).isFalse() - } - @Test fun `GraphQL document falls back to sendDefaultPii and override takes precedence`() { val options = SentryOptions().apply { isSendDefaultPii = true }