Skip to content

Commit e156322

Browse files
committed
fix(dts): change tags from list to set for tencentcloud_dts_sync_job to handle unordered tags response
1 parent 5af95e1 commit e156322

13 files changed

Lines changed: 589 additions & 406 deletions

File tree

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ require (
5858
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dlc v1.3.182
5959
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.124
6060
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/domain v1.0.414
61-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.153
61+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.171
6262
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/eb v1.1.30
6363
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/emr v1.3.188
6464
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/es v1.1.13

‎go.sum‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,6 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.130/go.mod
987987
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.131/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
988988
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.136/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
989989
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.144/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
990-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.153/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
991990
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.154/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
992991
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.157/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
993992
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.162/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
@@ -1039,8 +1038,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.124 h1:atQm
10391038
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.3.124/go.mod h1:zxlW9EAn0Hqx6Eub6MMocnHZz/gHundkyI7CdbIhU3o=
10401039
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/domain v1.0.414 h1:egwjvOEUKBaxsoRVn/YSEhp2E8qdh77Ous9A/wftDo0=
10411040
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/domain v1.0.414/go.mod h1:LRWnBUNRxs/Ee8MzJJd3eHzydlqyB5qmia975Ca/Ox0=
1042-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.153 h1:CgdZiyjk9h+kcbiPchjiDL1R533c4jHqlV5jmkBVaa4=
1043-
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.153/go.mod h1:72IuHSGsSGDLZNRd1wDm4UBVIk6GABNCkXkCfpEvtI8=
1041+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.171 h1:jhJqrhIir/OOPTHSNR7CHfl0AA//yjiwXoGsRIOkAiE=
1042+
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dts v1.3.171/go.mod h1:pQzZ8iRCI6B4cevn8xGMpAY+NXjJOHo9lvPxu4xeegQ=
10441043
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/eb v1.1.30 h1:fuy1NhRyTbwMku0VS1/uFao77pK1HuLCqRRYVxow3Uk=
10451044
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/eb v1.1.30/go.mod h1:ORMs9Hzzlrmsq0gqKdw+XBE0uMWA026Bsy3jvpCWdFA=
10461045
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/emr v1.3.188 h1:GD6Vz/38zSGgRYZo8pY1IE8W+906Yddfqg4vj/3TuCM=
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-10-14
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
## Context
2+
3+
The `tencentcloud_dts_sync_job` resource defines its `tags` field as `schema.TypeList`. Tags are sent to the DTS cloud API via `CreateSyncJob` (as `[]*dts.TagItem`) and read back via `DescribeSyncJobs` (as `[]*dts.TagItem`). The cloud API does not guarantee tag ordering on read — the same set of tags may come back in a different order than what was sent. With `TypeList`, Terraform compares element-by-element by index, so any reordering produces a non-empty diff on every refresh. This is a well-known pattern in the codebase: other resources (e.g. MongoDB `availability_zone_list`, DTS sync_config sub-fields) already use `TypeSet` for unordered collections.
4+
5+
Constraints:
6+
- The `tags` field is `ForceNew` + `Computed` + `Optional`, so it is set only at create time and refreshed on read. There is no update path that needs changing.
7+
- The cloud API request/response types (`dts.TagItem`) are unchanged — only the Terraform-side collection type changes.
8+
- HCL block-list syntax is identical for `TypeList` and `TypeSet`, so user `.tf` files require no modification.
9+
10+
## Goals / Non-Goals
11+
12+
**Goals:**
13+
- Eliminate spurious diffs on the `tags` field caused by unordered cloud API responses.
14+
- Keep the nested element schema (`tag_key`, `tag_value`) unchanged.
15+
- Keep the HCL configuration syntax backwards-compatible (no change to user `.tf` files).
16+
17+
**Non-Goals:**
18+
- Do not change the cloud API calls (`CreateSyncJob`, `DescribeSyncJobs`).
19+
- Do not change the `dts.TagItem` SDK struct usage.
20+
- Do not modify other fields of `tencentcloud_dts_sync_job`.
21+
- Do not change the `tags` handling in the data source `tencentcloud_dts_sync_jobs` (it uses a different schema and is not affected by the reported issue).
22+
23+
## Decisions
24+
25+
### D1: TypeSet with default HashResource
26+
27+
**Decision**: Change `Type: schema.TypeList` to `Type: schema.TypeSet`, keeping `Elem: &schema.Resource{...}`. Use the default `Set` hash function (`schema.HashResource`), which hashes the full resource schema — i.e. the combination of `tag_key` + `tag_value`.
28+
29+
**Rationale**:
30+
- `tag_key` + `tag_value` together form a unique identity for a tag. The default `HashResource` hashes both fields, so duplicate tags are correctly de-duplicated and order is irrelevant.
31+
- An alternative is a custom `schema.HashResource` with a custom function, but that adds complexity for no benefit since the default already covers both fields.
32+
33+
**Alternatives considered**:
34+
- Custom hash function keyed only on `tag_key`: rejected because two tags with the same key but different values would collide; the cloud API allows the same key with different values in some contexts, so hashing both fields is safer.
35+
36+
### D2: Create handler reads Set via `(*schema.Set).List()`
37+
38+
**Decision**: In `resourceTencentCloudDtsSyncJobCreate`, change `v.([]interface{})` to `v.(*schema.Set).List()` when iterating the `tags` field. The iteration body that builds `dts.TagItem` is unchanged.
39+
40+
**Rationale**: `d.GetOk("tags")` returns `*schema.Set` for a TypeSet field. Calling `.List()` converts it to `[]interface{}`, which is the same type the existing loop already expects.
41+
42+
### D3: Read handler stays as `d.Set("tags", tagsList)` with `[]interface{}`
43+
44+
**Decision**: The Read handler already builds `tagsList` as `[]interface{}` of `map[string]interface{}` and calls `d.Set("tags", tagsList)`. This works correctly with `TypeSet` — Terraform SDK converts the slice into the Set using the same hash function. No change needed in the read body besides keeping the existing logic.
45+
46+
**Rationale**: `schema.ResourceData.Set` accepts `[]interface{}` for both TypeList and TypeSet. For TypeSet it hashes each element with the configured hash function. The existing code already produces the right shape.
47+
48+
### D4: Test assertions updated to Set semantics
49+
50+
**Decision**: In `resource_tc_dts_sync_job_test.go`, replace the `resource.TestCheckResourceAttrSet(..., "tags.#")` check with `resource.TestCheckTypeSetElemNestedAttrs` to verify the specific tag key/value pair regardless of order. The HCL config stays in block syntax (valid for Set).
51+
52+
## Risks / Trade-offs
53+
54+
- **[R1] State format change** → On first `terraform plan`/`apply` after upgrade, the state representation of `tags` changes from an ordered list to a set. Users see a one-time refresh diff; no actual resource is modified. This is the standard SDKv2 List→Set migration, well-tested in this codebase (MongoDB precedent). Mitigation: document the one-time refresh in the changelog.
55+
- **[R2] TypeSet hash collisions** → If two tags have identical `tag_key` + `tag_value`, they collapse into one Set element. The cloud API also treats identical tags as duplicates, so this matches API semantics. No risk.
56+
- **[R3] Import compatibility** → `tags` is `ForceNew`+`Computed`, not a read-from-id field, so import (`schema.ImportStatePassthrough`) is unaffected.
57+
58+
## Migration Plan
59+
60+
1. Apply the schema + Create handler change in `resource_tc_dts_sync_job.go`.
61+
2. Update test file assertions.
62+
3. Run `gofmt` on modified `.go` files (finalize step).
63+
4. Regenerate docs via `make doc` (finalize step).
64+
5. Rollback: `git revert` the change; the state automatically reverts to List representation on next refresh with the old provider version.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Why
2+
3+
The `tags` field of `tencentcloud_dts_sync_job` is currently defined as `schema.TypeList`, but the DTS cloud API `DescribeSyncJobs` returns tags in an unordered manner. This causes Terraform to detect a perpetual diff on every `terraform plan`/`refresh` — the tag order in state never matches the order returned by the API, so users see non-empty diffs even when the tag set is unchanged. Changing the field to `schema.TypeSet` eliminates order-sensitive diffs and matches the unordered semantics of the cloud API.
4+
5+
## What Changes
6+
7+
- **BREAKING** (state format only): Change the `tags` field in `tencentcloud_dts_sync_job` from `schema.TypeList` to `schema.TypeSet`. The nested `schema.Resource` element (with `tag_key` and `tag_value` fields) remains unchanged. Using `TypeSet` with the default `schema.HashResource` hashing, Terraform treats two tag sets with the same key/value pairs — regardless of order — as equal.
8+
- Update the Create handler to read the `tags` field via `v.(*schema.Set).List()` instead of `v.([]interface{})`.
9+
- Update the Read handler so that `d.Set("tags", tagsList)` correctly populates the Set (the existing `[]interface{}` of maps already works with TypeSet).
10+
- Update the test file `resource_tc_dts_sync_job_test.go` to use `resource.TestCheckTypeSetElemNestedAttrs` for tag verification and update the HCL config to use the set syntax.
11+
- Update the `.md` example doc to keep it consistent.
12+
13+
## Capabilities
14+
15+
### New Capabilities
16+
- `dts-sync-job-tags-set-type`: Defines that the `tags` field of `tencentcloud_dts_sync_job` SHALL use `schema.TypeSet` so that tag ordering differences between configuration and cloud API responses do not produce spurious diffs.
17+
18+
### Modified Capabilities
19+
<!-- (no existing capability spec to modify) -->
20+
21+
## Impact
22+
23+
- **Code**: `tencentcloud/services/dts/resource_tc_dts_sync_job.go` — schema definition (line 71–91), Create handler (line 168–180), Read handler (line 283–297).
24+
- **Tests**: `tencentcloud/services/dts/resource_tc_dts_sync_job_test.go` — update tag assertions and HCL config.
25+
- **Docs**: `tencentcloud/services/dts/resource_tc_dts_sync_job.md` — example syntax (tags block unchanged for users but doc regenerated via `make doc`).
26+
- **API**: No cloud API change. `CreateSyncJob` request and `DescribeSyncJobs` response schemas are unchanged — only the Terraform-side collection type changes from List to Set.
27+
- **State migration**: On the first `terraform plan` after provider upgrade, users will see a one-time state refresh converting the `tags` representation from List to Set. No actual cloud resource modification occurs. The HCL configuration syntax (block-list style) is identical for List and Set, so user `.tf` files require no changes.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## ADDED Requirements
2+
3+
### Requirement: DTS sync job tags field uses Set type
4+
5+
The `tags` field of the `tencentcloud_dts_sync_job` resource SHALL use `schema.TypeSet` with a nested `schema.Resource` element containing `tag_key` (string) and `tag_value` (string) sub-fields. The Set SHALL use the default `schema.HashResource` hash function so that two tag collections with identical key/value pairs — regardless of element order — are treated as equal. This eliminates spurious diffs caused by the DTS cloud API `DescribeSyncJobs` returning tags in an unordered manner.
6+
7+
#### Scenario: Tags are written to CreateSyncJob request from Set
8+
9+
- **WHEN** a user configures the `tags` block on `tencentcloud_dts_sync_job` and the provider executes the Create handler
10+
- **THEN** the provider SHALL read the `tags` field via `v.(*schema.Set).List()` and iterate the resulting elements to build `[]*dts.TagItem` for the `CreateSyncJob` request `Tags` parameter
11+
12+
#### Scenario: Tags are read back into Set state
13+
14+
- **WHEN** the provider reads the resource via `DescribeSyncJobs` and the returned `SyncJob.Tags` is non-nil
15+
- **THEN** the provider SHALL build a `[]interface{}` of `map[string]interface{}` (each containing `tag_key` and `tag_value`) and call `d.Set("tags", tagsList)` to populate the Set state
16+
17+
#### Scenario: Unordered tag response does not produce a diff
18+
19+
- **WHEN** the user's configuration contains tags `[{tag_key="a", tag_value="1"}, {tag_key="b", tag_value="2"}]` and the cloud API returns the same tags in reverse order `[{tag_key="b", tag_value="2"}, {tag_key="a", tag_value="1"}]`
20+
- **THEN** `terraform plan` SHALL report no diff on the `tags` field, because the Set type is order-insensitive
21+
22+
#### Scenario: Tags field is omitted from configuration
23+
24+
- **WHEN** a user does not set the `tags` field in the `tencentcloud_dts_sync_job` configuration
25+
- **THEN** the provider SHALL NOT set the `Tags` field on the `CreateSyncJob` request, preserving existing behavior
26+
27+
#### Scenario: Tags returned as nil from cloud API
28+
29+
- **WHEN** the provider reads the resource and the returned `SyncJob.Tags` is nil
30+
- **THEN** the provider SHALL NOT call `d.Set("tags", ...)`, leaving existing state untouched
31+
32+
#### Scenario: HCL configuration syntax compatibility
33+
34+
- **WHEN** a user writes tags using the existing block-list HCL syntax (repeated `tags { tag_key = ... tag_value = ... }` blocks)
35+
- **THEN** Terraform SHALL accept the configuration without error, because TypeList and TypeSet share the same HCL block syntax
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## 1. Schema 修改
2+
3+
- [x] 1.1 在 `tencentcloud/services/dts/resource_tc_dts_sync_job.go` 中,将 `tags` 字段的 `Type` 从 `schema.TypeList` 改为 `schema.TypeSet`(约第 72 行),保持 `Optional`/`Computed`/`ForceNew` 及 `Elem: &schema.Resource{...}`(含 `tag_key`、`tag_value`)不变
4+
- [x] 1.2 确认无需显式设置 `Set` 字段,使用默认 `schema.HashResource` 哈希 `tag_key` + `tag_value` 组合
5+
6+
## 2. Create handler 修改
7+
8+
- [x] 2.1 在 `resourceTencentCloudDtsSyncJobCreate` 函数中,将读取 `tags` 的类型断言从 `v.([]interface{})` 改为 `v.(*schema.Set).List()`(约第 169 行),循环体内构建 `dts.TagItem` 的逻辑不变
9+
10+
## 3. Read handler 确认
11+
12+
- [x] 3.1 确认 `resourceTencentCloudDtsSyncJobRead` 中的 `d.Set("tags", tagsList)` 逻辑(约第 283–297 行)对 TypeSet 兼容,`tagsList` 仍为 `[]interface{}` of `map[string]interface{}`,无需修改
13+
14+
## 4. 测试修改
15+
16+
- [x] 4.1 在 `tencentcloud/services/dts/resource_tc_dts_sync_job_test.go` 中,将 `resource.TestCheckResourceAttrSet("tencentcloud_dts_sync_job.sync_job", "tags.#")` 替换为 `resource.TestCheckTypeSetElemNestedAttrs`,验证 tag 的 `tag_key`/`tag_value` 键值对(与顺序无关)
17+
- [x] 4.2 确认 HCL 配置块 `testAccDtsSyncJob` 中的 `tags { ... }` 块语法无需修改(TypeList/TypeSet 共用块语法)
18+
19+
## 5. 文档更新
20+
21+
- [x] 5.1 确认 `tencentcloud/services/dts/resource_tc_dts_sync_job.md` 示例中的 `tags` 块语法保持不变(Set 与 List 语法相同,无需修改)
22+
23+
## 6. 收尾(tfpacer-finalize 阶段执行)
24+
25+
- [ ] 6.1 对修改的 `.go` 文件执行 `gofmt` 格式化
26+
- [ ] 6.2 执行 `make doc` 生成 `website/docs/` 下的文档
27+
- [ ] 6.3 创建 `.changelog/` 条目记录本次变更
28+
29+
## 7. 验证
30+
31+
- [x] 7.1 确认 `tags` 字段在 schema 中为 TypeSet
32+
- [x] 7.2 确认 Create handler 使用 `(*schema.Set).List()` 读取 tags
33+
- [x] 7.3 确认无其他文件引用 `tags` 字段的 List 类型断言
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# dts-sync-job-tags-set-type Specification
2+
3+
## Purpose
4+
TBD - created by archiving change fix-dts-sync-job-tags-list-to-set. Update Purpose after archive.
5+
## Requirements
6+
### Requirement: DTS sync job tags field uses Set type
7+
8+
The `tags` field of the `tencentcloud_dts_sync_job` resource SHALL use `schema.TypeSet` with a nested `schema.Resource` element containing `tag_key` (string) and `tag_value` (string) sub-fields. The Set SHALL use the default `schema.HashResource` hash function so that two tag collections with identical key/value pairs — regardless of element order — are treated as equal. This eliminates spurious diffs caused by the DTS cloud API `DescribeSyncJobs` returning tags in an unordered manner.
9+
10+
#### Scenario: Tags are written to CreateSyncJob request from Set
11+
12+
- **WHEN** a user configures the `tags` block on `tencentcloud_dts_sync_job` and the provider executes the Create handler
13+
- **THEN** the provider SHALL read the `tags` field via `v.(*schema.Set).List()` and iterate the resulting elements to build `[]*dts.TagItem` for the `CreateSyncJob` request `Tags` parameter
14+
15+
#### Scenario: Tags are read back into Set state
16+
17+
- **WHEN** the provider reads the resource via `DescribeSyncJobs` and the returned `SyncJob.Tags` is non-nil
18+
- **THEN** the provider SHALL build a `[]interface{}` of `map[string]interface{}` (each containing `tag_key` and `tag_value`) and call `d.Set("tags", tagsList)` to populate the Set state
19+
20+
#### Scenario: Unordered tag response does not produce a diff
21+
22+
- **WHEN** the user's configuration contains tags `[{tag_key="a", tag_value="1"}, {tag_key="b", tag_value="2"}]` and the cloud API returns the same tags in reverse order `[{tag_key="b", tag_value="2"}, {tag_key="a", tag_value="1"}]`
23+
- **THEN** `terraform plan` SHALL report no diff on the `tags` field, because the Set type is order-insensitive
24+
25+
#### Scenario: Tags field is omitted from configuration
26+
27+
- **WHEN** a user does not set the `tags` field in the `tencentcloud_dts_sync_job` configuration
28+
- **THEN** the provider SHALL NOT set the `Tags` field on the `CreateSyncJob` request, preserving existing behavior
29+
30+
#### Scenario: Tags returned as nil from cloud API
31+
32+
- **WHEN** the provider reads the resource and the returned `SyncJob.Tags` is nil
33+
- **THEN** the provider SHALL NOT call `d.Set("tags", ...)`, leaving existing state untouched
34+
35+
#### Scenario: HCL configuration syntax compatibility
36+
37+
- **WHEN** a user writes tags using the existing block-list HCL syntax (repeated `tags { tag_key = ... tag_value = ... }` blocks)
38+
- **THEN** Terraform SHALL accept the configuration without error, because TypeList and TypeSet share the same HCL block syntax
39+

‎tencentcloud/services/dts/resource_tc_dts_sync_job.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func ResourceTencentCloudDtsSyncJob() *schema.Resource {
6969
},
7070

7171
"tags": {
72-
Type: schema.TypeList,
72+
Type: schema.TypeSet,
7373
Optional: true,
7474
Computed: true,
7575
ForceNew: true,
@@ -166,7 +166,7 @@ func resourceTencentCloudDtsSyncJobCreate(d *schema.ResourceData, meta interface
166166
}
167167

168168
if v, ok := d.GetOk("tags"); ok {
169-
for _, item := range v.([]interface{}) {
169+
for _, item := range v.(*schema.Set).List() {
170170
dMap := item.(map[string]interface{})
171171
tagItem := dts.TagItem{}
172172
if v, ok := dMap["tag_key"]; ok {

‎tencentcloud/services/dts/resource_tc_dts_sync_job_test.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ func TestAccTencentCloudDtsSyncJobResource_basic(t *testing.T) {
7676
resource.TestCheckResourceAttr("tencentcloud_dts_sync_job.sync_job", "dst_database_type", "cynosdbmysql"),
7777
resource.TestCheckResourceAttr("tencentcloud_dts_sync_job.sync_job", "dst_region", "ap-guangzhou"),
7878
resource.TestCheckResourceAttrSet("tencentcloud_dts_sync_job.sync_job", "tags.#"),
79+
resource.TestCheckTypeSetElemNestedAttrs("tencentcloud_dts_sync_job.sync_job", "tags.*", map[string]string{
80+
"tag_key": "aaa",
81+
"tag_value": "bbb",
82+
}),
7983
resource.TestCheckResourceAttr("tencentcloud_dts_sync_job.sync_job", "auto_renew", "0"),
8084
resource.TestCheckResourceAttr("tencentcloud_dts_sync_job.sync_job", "instance_class", "micro"),
8185
),

0 commit comments

Comments
 (0)