Skip to content

Commit e6ca739

Browse files
n00bcrackernshestakov
authored andcommitted
Added scheme cache sysview path type processing (ydb-platform#18223)
1 parent 37478cb commit e6ca739

File tree

6 files changed

+143
-41
lines changed

6 files changed

+143
-41
lines changed

ydb/core/protos/sys_view_types.proto

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,36 @@ option java_package = "ru.yandex.kikimr.proto";
44

55
enum ESysViewType {
66
EPartitionStats = 1;
7+
ENodes = 2;
8+
ETopQueriesByDurationOneMinute = 3;
9+
ETopQueriesByDurationOneHour = 4;
10+
ETopQueriesByReadBytesOneMinute = 5;
11+
ETopQueriesByReadBytesOneHour = 6;
12+
ETopQueriesByCpuTimeOneMinute = 7;
13+
ETopQueriesByCpuTimeOneHour = 8;
14+
ETopQueriesByRequestUnitsOneMinute = 9;
15+
ETopQueriesByRequestUnitsOneHour = 10;
16+
EQuerySessions = 11;
17+
EPDisks = 12;
18+
EVSlots = 13;
19+
EGroups = 14;
20+
EStoragePools = 15;
21+
EStorageStats = 16;
22+
ETablets = 17;
23+
EQueryMetricsOneMinute = 18;
24+
ETopPartitionsByCpuOneMinute = 19;
25+
ETopPartitionsByCpuOneHour = 20;
26+
ETopPartitionsByTliOneMinute = 21;
27+
ETopPartitionsByTliOneHour = 22;
28+
EResourcePoolClassifiers = 23;
29+
EResourcePools = 24;
30+
EAuthUsers = 25;
31+
EAuthGroups = 26;
32+
EAuthGroupMembers = 27;
33+
EAuthOwners = 28;
34+
EAuthPermissions = 29;
35+
EAuthEffectivePermissions = 30;
36+
EPgTables = 31;
37+
EInformationSchemaTables = 32;
38+
EPgClass = 33;
739
}

ydb/core/sys_view/common/schema.cpp

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
#include <ydb/core/base/appdata.h>
44
#include <yql/essentials/parser/pg_catalog/catalog.h>
55

6+
namespace {
7+
using NKikimrSysView::ESysViewType;
8+
}
9+
610
namespace NKikimr {
711
namespace NSysView {
812

@@ -170,6 +174,11 @@ class TSystemViewResolver : public ISystemViewResolver {
170174
return view ? TMaybe<TSchema>(*view) : Nothing();
171175
}
172176

177+
TMaybe<TSchema> GetSystemViewSchema(ESysViewType viewType) const override final {
178+
const TSchema* view = SystemViews.FindPtr(viewType);
179+
return view ? TMaybe<TSchema>(*view) : Nothing();
180+
}
181+
173182
TVector<TString> GetSystemViewNames(ETarget target) const override {
174183
TVector<TString> result;
175184
switch (target) {
@@ -210,41 +219,50 @@ class TSystemViewResolver : public ISystemViewResolver {
210219

211220
private:
212221
void RegisterPgTablesSystemViews() {
213-
auto registerView = [&](TStringBuf tableName, const TVector<Schema::PgColumn>& columns) {
222+
auto registerView = [&](TStringBuf tableName, ESysViewType type, const TVector<Schema::PgColumn>& columns) {
214223
auto& dsv = DomainSystemViews[tableName];
215224
auto& sdsv = SubDomainSystemViews[tableName];
225+
auto& sv = SystemViews[type];
216226
for (const auto& column : columns) {
217227
dsv.Columns[column._ColumnId + 1] = TSysTables::TTableColumnInfo(
218228
column._ColumnName, column._ColumnId + 1, column._ColumnTypeInfo, "", -1
219229
);
220230
sdsv.Columns[column._ColumnId + 1] = TSysTables::TTableColumnInfo(
221231
column._ColumnName, column._ColumnId + 1, column._ColumnTypeInfo, "", -1
222232
);
233+
sv.Columns[column._ColumnId + 1] = TSysTables::TTableColumnInfo(
234+
column._ColumnName, column._ColumnId + 1, column._ColumnTypeInfo, "", -1
235+
);
223236
}
224237
};
225238
registerView(
226239
PgTablesName,
240+
ESysViewType::EPgTables,
227241
Singleton<Schema::PgTablesSchemaProvider>()->GetColumns(PgTablesName)
228242
);
229243
registerView(
230244
InformationSchemaTablesName,
245+
ESysViewType::EInformationSchemaTables,
231246
Singleton<Schema::PgTablesSchemaProvider>()->GetColumns(InformationSchemaTablesName)
232247
);
233248
registerView(
234249
PgClassName,
250+
ESysViewType::EPgClass,
235251
Singleton<Schema::PgTablesSchemaProvider>()->GetColumns(PgClassName)
236252
);
237253
}
238254

239255
template <typename Table>
240-
void RegisterSystemView(const TStringBuf& name) {
256+
void RegisterSystemView(const TStringBuf& name, ESysViewType type) {
241257
TSchemaFiller<Table>::Fill(DomainSystemViews[name]);
242258
TSchemaFiller<Table>::Fill(SubDomainSystemViews[name]);
259+
TSchemaFiller<Table>::Fill(SystemViews[type]);
243260
}
244261

245262
template <typename Table>
246-
void RegisterDomainSystemView(const TStringBuf& name) {
263+
void RegisterDomainSystemView(const TStringBuf& name, ESysViewType type) {
247264
TSchemaFiller<Table>::Fill(DomainSystemViews[name]);
265+
TSchemaFiller<Table>::Fill(SystemViews[type]);
248266
}
249267

250268
template <typename Table>
@@ -258,29 +276,29 @@ class TSystemViewResolver : public ISystemViewResolver {
258276
}
259277

260278
void RegisterSystemViews() {
261-
RegisterSystemView<Schema::PartitionStats>(PartitionStatsName);
279+
RegisterSystemView<Schema::PartitionStats>(PartitionStatsName, ESysViewType::EPartitionStats);
262280

263-
RegisterSystemView<Schema::Nodes>(NodesName);
281+
RegisterSystemView<Schema::Nodes>(NodesName, ESysViewType::ENodes);
264282

265-
RegisterSystemView<Schema::QueryStats>(TopQueriesByDuration1MinuteName);
266-
RegisterSystemView<Schema::QueryStats>(TopQueriesByDuration1HourName);
267-
RegisterSystemView<Schema::QueryStats>(TopQueriesByReadBytes1MinuteName);
268-
RegisterSystemView<Schema::QueryStats>(TopQueriesByReadBytes1HourName);
269-
RegisterSystemView<Schema::QueryStats>(TopQueriesByCpuTime1MinuteName);
270-
RegisterSystemView<Schema::QueryStats>(TopQueriesByCpuTime1HourName);
271-
RegisterSystemView<Schema::QueryStats>(TopQueriesByRequestUnits1MinuteName);
272-
RegisterSystemView<Schema::QueryStats>(TopQueriesByRequestUnits1HourName);
273-
RegisterSystemView<Schema::QuerySessions>(QuerySessions);
283+
RegisterSystemView<Schema::QueryStats>(TopQueriesByDuration1MinuteName, ESysViewType::ETopQueriesByDurationOneMinute);
284+
RegisterSystemView<Schema::QueryStats>(TopQueriesByDuration1HourName, ESysViewType::ETopQueriesByDurationOneHour);
285+
RegisterSystemView<Schema::QueryStats>(TopQueriesByReadBytes1MinuteName, ESysViewType::ETopQueriesByReadBytesOneMinute);
286+
RegisterSystemView<Schema::QueryStats>(TopQueriesByReadBytes1HourName, ESysViewType::ETopQueriesByReadBytesOneHour);
287+
RegisterSystemView<Schema::QueryStats>(TopQueriesByCpuTime1MinuteName, ESysViewType::ETopQueriesByCpuTimeOneMinute);
288+
RegisterSystemView<Schema::QueryStats>(TopQueriesByCpuTime1HourName, ESysViewType::ETopQueriesByCpuTimeOneHour);
289+
RegisterSystemView<Schema::QueryStats>(TopQueriesByRequestUnits1MinuteName, ESysViewType::ETopQueriesByRequestUnitsOneMinute);
290+
RegisterSystemView<Schema::QueryStats>(TopQueriesByRequestUnits1HourName, ESysViewType::ETopQueriesByRequestUnitsOneHour);
291+
RegisterSystemView<Schema::QuerySessions>(QuerySessions, ESysViewType::EQuerySessions);
274292

275-
RegisterDomainSystemView<Schema::PDisks>(PDisksName);
276-
RegisterDomainSystemView<Schema::VSlots>(VSlotsName);
277-
RegisterDomainSystemView<Schema::Groups>(GroupsName);
278-
RegisterDomainSystemView<Schema::StoragePools>(StoragePoolsName);
279-
RegisterDomainSystemView<Schema::StorageStats>(StorageStatsName);
293+
RegisterDomainSystemView<Schema::PDisks>(PDisksName, ESysViewType::EPDisks);
294+
RegisterDomainSystemView<Schema::VSlots>(VSlotsName, ESysViewType::EVSlots);
295+
RegisterDomainSystemView<Schema::Groups>(GroupsName, ESysViewType::EGroups);
296+
RegisterDomainSystemView<Schema::StoragePools>(StoragePoolsName, ESysViewType::EStoragePools);
297+
RegisterDomainSystemView<Schema::StorageStats>(StorageStatsName, ESysViewType::EStorageStats);
280298

281-
RegisterDomainSystemView<Schema::Tablets>(TabletsName);
299+
RegisterDomainSystemView<Schema::Tablets>(TabletsName, ESysViewType::ETablets);
282300

283-
RegisterSystemView<Schema::QueryMetrics>(QueryMetricsName);
301+
RegisterSystemView<Schema::QueryMetrics>(QueryMetricsName, ESysViewType::EQueryMetricsOneMinute);
284302

285303
RegisterOlapStoreSystemView<Schema::PrimaryIndexStats>(StorePrimaryIndexStatsName);
286304
RegisterOlapStoreSystemView<Schema::PrimaryIndexPortionStats>(StorePrimaryIndexPortionStatsName);
@@ -291,24 +309,24 @@ class TSystemViewResolver : public ISystemViewResolver {
291309
RegisterColumnTableSystemView<Schema::PrimaryIndexGranuleStats>(TablePrimaryIndexGranuleStatsName);
292310
RegisterColumnTableSystemView<Schema::PrimaryIndexOptimizerStats>(TablePrimaryIndexOptimizerStatsName);
293311

294-
RegisterSystemView<Schema::TopPartitions>(TopPartitionsByCpu1MinuteName);
295-
RegisterSystemView<Schema::TopPartitions>(TopPartitionsByCpu1HourName);
296-
RegisterSystemView<Schema::TopPartitionsTli>(TopPartitionsByTli1MinuteName);
297-
RegisterSystemView<Schema::TopPartitionsTli>(TopPartitionsByTli1HourName);
312+
RegisterSystemView<Schema::TopPartitions>(TopPartitionsByCpu1MinuteName, ESysViewType::ETopPartitionsByCpuOneMinute);
313+
RegisterSystemView<Schema::TopPartitions>(TopPartitionsByCpu1HourName, ESysViewType::ETopPartitionsByCpuOneHour);
314+
RegisterSystemView<Schema::TopPartitionsTli>(TopPartitionsByTli1MinuteName, ESysViewType::ETopPartitionsByTliOneMinute);
315+
RegisterSystemView<Schema::TopPartitionsTli>(TopPartitionsByTli1HourName, ESysViewType::ETopPartitionsByTliOneHour);
298316

299317
RegisterPgTablesSystemViews();
300318

301-
RegisterSystemView<Schema::ResourcePoolClassifiers>(ResourcePoolClassifiersName);
302-
RegisterSystemView<Schema::ResourcePools>(ResourcePoolsName);
319+
RegisterSystemView<Schema::ResourcePoolClassifiers>(ResourcePoolClassifiersName, ESysViewType::EResourcePoolClassifiers);
320+
RegisterSystemView<Schema::ResourcePools>(ResourcePoolsName, ESysViewType::EResourcePools);
303321

304322
{
305323
using namespace NAuth;
306-
RegisterSystemView<Schema::AuthUsers>(UsersName);
307-
RegisterSystemView<Schema::AuthGroups>(NAuth::GroupsName);
308-
RegisterSystemView<Schema::AuthGroupMembers>(GroupMembersName);
309-
RegisterSystemView<Schema::AuthOwners>(OwnersName);
310-
RegisterSystemView<Schema::AuthPermissions>(PermissionsName);
311-
RegisterSystemView<Schema::AuthPermissions>(EffectivePermissionsName);
324+
RegisterSystemView<Schema::AuthUsers>(UsersName, ESysViewType::EAuthUsers);
325+
RegisterSystemView<Schema::AuthGroups>(NAuth::GroupsName, ESysViewType::EAuthGroups);
326+
RegisterSystemView<Schema::AuthGroupMembers>(GroupMembersName, ESysViewType::EAuthGroupMembers);
327+
RegisterSystemView<Schema::AuthOwners>(OwnersName, ESysViewType::EAuthOwners);
328+
RegisterSystemView<Schema::AuthPermissions>(PermissionsName, ESysViewType::EAuthPermissions);
329+
RegisterSystemView<Schema::AuthPermissions>(EffectivePermissionsName, ESysViewType::EAuthEffectivePermissions);
312330
}
313331
}
314332

@@ -317,6 +335,7 @@ class TSystemViewResolver : public ISystemViewResolver {
317335
THashMap<TString, TSchema> SubDomainSystemViews;
318336
THashMap<TString, TSchema> OlapStoreSystemViews;
319337
THashMap<TString, TSchema> ColumnTableSystemViews;
338+
THashMap<ESysViewType, TSchema> SystemViews;
320339
};
321340

322341
class TSystemViewRewrittenResolver : public ISystemViewResolver {
@@ -346,6 +365,11 @@ class TSystemViewRewrittenResolver : public ISystemViewResolver {
346365
return view ? TMaybe<TSchema>(*view) : Nothing();
347366
}
348367

368+
TMaybe<TSchema> GetSystemViewSchema(ESysViewType sysViewType) const override final {
369+
Y_UNUSED(sysViewType);
370+
return Nothing();
371+
}
372+
349373
TVector<TString> GetSystemViewNames(ETarget target) const override {
350374
Y_UNUSED(target);
351375
return {};

ydb/core/sys_view/common/schema.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "path.h"
44

5+
#include <ydb/core/protos/sys_view_types.pb.h>
56
#include <ydb/core/tablet_flat/flat_cxx_database.h>
67
#include <ydb/core/tx/locks/sys_tables.h>
78
#include <yql/essentials/parser/pg_catalog/catalog.h>
@@ -827,6 +828,8 @@ class ISystemViewResolver {
827828

828829
virtual TMaybe<TSchema> GetSystemViewSchema(const TStringBuf viewName, ETarget target) const = 0;
829830

831+
virtual TMaybe<TSchema> GetSystemViewSchema(NKikimrSysView::ESysViewType viewType) const = 0;
832+
830833
virtual bool IsSystemView(const TStringBuf viewName) const = 0;
831834

832835
virtual TVector<TString> GetSystemViewNames(ETarget target) const = 0;

ydb/core/tx/scheme_board/cache.cpp

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ namespace {
240240
entry.BlockStoreVolumeInfo.Drop();
241241
entry.FileStoreInfo.Drop();
242242
entry.BackupCollectionInfo.Drop();
243+
entry.SysViewInfo.Drop();
243244
}
244245

245246
static void SetErrorAndClear(TResolveContext* context, TResolve::TEntry& entry, const bool isDescribeDenied) {
@@ -759,6 +760,7 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
759760
ViewInfo.Drop();
760761
ResourcePoolInfo.Drop();
761762
BackupCollectionInfo.Drop();
763+
SysViewInfo.Drop();
762764
}
763765

764766
void FillTableInfo(const NKikimrSchemeOp::TPathDescription& pathDesc) {
@@ -873,6 +875,18 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
873875
}
874876
}
875877

878+
void FillSystemViewInfo(const NKikimrSchemeOp::TPathDescription& pathDesc) {
879+
if (auto schema = Owner->SystemViewResolver->GetSystemViewSchema(pathDesc.GetSysViewDescription().GetType())) {
880+
Columns = std::move(schema->Columns);
881+
KeyColumnTypes = std::move(schema->KeyColumnTypes);
882+
for (const auto& [id, column] : Columns) {
883+
if (column.IsNotNullColumn) {
884+
NotNullColumns.insert(column.Name);
885+
}
886+
}
887+
}
888+
}
889+
876890
static TResolve::EKind PathSubTypeToTableKind(NKikimrSchemeOp::EPathSubType subType) {
877891
switch (subType) {
878892
case NKikimrSchemeOp::EPathSubTypeSyncIndexImplTable:
@@ -1283,6 +1297,7 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
12831297
DESCRIPTION_PART(ViewInfo);
12841298
DESCRIPTION_PART(ResourcePoolInfo);
12851299
DESCRIPTION_PART(BackupCollectionInfo);
1300+
DESCRIPTION_PART(SysViewInfo);
12861301

12871302
#undef DESCRIPTION_PART
12881303

@@ -1624,6 +1639,12 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
16241639
FillInfo(Kind, BackupCollectionInfo, std::move(*pathDesc.MutableBackupCollectionDescription()));
16251640
break;
16261641
case NKikimrSchemeOp::EPathTypeSysView:
1642+
Kind = TNavigate::KindSysView;
1643+
if (Created) {
1644+
FillSystemViewInfo(pathDesc);
1645+
}
1646+
FillInfo(Kind, SysViewInfo, std::move(*pathDesc.MutableSysViewDescription()));
1647+
break;
16271648
case NKikimrSchemeOp::EPathTypeInvalid:
16281649
Y_DEBUG_ABORT("Invalid path type");
16291650
break;
@@ -1704,6 +1725,8 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
17041725
ListNodeEntry->Children.emplace_back(name, pathId, TNavigate::KindBackupCollection);
17051726
break;
17061727
case NKikimrSchemeOp::EPathTypeSysView:
1728+
ListNodeEntry->Children.emplace_back(name, pathId, TNavigate::KindSysView);
1729+
break;
17071730
case NKikimrSchemeOp::EPathTypeTableIndex:
17081731
case NKikimrSchemeOp::EPathTypeInvalid:
17091732
Y_DEBUG_ABORT("Invalid path type");
@@ -1829,7 +1852,7 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
18291852
return SetError(context, entry, TNavigate::EStatus::LookupError);
18301853
}
18311854

1832-
if (!entry.TableId.SysViewInfo.empty()) {
1855+
if (!entry.TableId.SysViewInfo.empty() && Kind != TNavigate::KindSysView) {
18331856
if (Kind == TNavigate::KindPath) {
18341857
auto split = SplitPath(Path);
18351858
if (split.size() == 1) {
@@ -1858,12 +1881,17 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
18581881
|| Kind == TNavigate::KindView;
18591882
const bool isTopic = Kind == TNavigate::KindTopic
18601883
|| Kind == TNavigate::KindCdcStream;
1884+
const bool isSysView = Kind == TNavigate::KindSysView;
18611885

1862-
if (entry.Operation == TNavigate::OpTable && !isTable) {
1886+
if (entry.Operation == TNavigate::OpTable && !(isTable || isSysView)) {
18631887
return SetError(context, entry, TNavigate::EStatus::PathNotTable);
18641888
}
18651889

1866-
if (!Created && (isTable || isTopic)) {
1890+
if (!Created && (isTable || isTopic || isSysView)) {
1891+
return SetError(context, entry, TNavigate::EStatus::PathErrorUnknown);
1892+
}
1893+
1894+
if (isSysView && !Columns) {
18671895
return SetError(context, entry, TNavigate::EStatus::PathErrorUnknown);
18681896
}
18691897

@@ -1888,6 +1916,9 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
18881916
if (entry.RequestType == TNavigate::TEntry::ERequestType::ByPath) {
18891917
if (Kind == TNavigate::KindTable || Kind == TNavigate::KindColumnTable) {
18901918
entry.TableId = TTableId(PathId.OwnerId, PathId.LocalPathId, SchemaVersion);
1919+
} else if (Kind == TNavigate::KindSysView) {
1920+
entry.TableId.PathId.OwnerId = PathId.OwnerId;
1921+
entry.TableId.PathId.LocalPathId = PathId.LocalPathId;
18911922
} else {
18921923
entry.TableId = TTableId(PathId.OwnerId, PathId.LocalPathId);
18931924
}
@@ -1927,6 +1958,7 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
19271958
entry.ViewInfo = ViewInfo;
19281959
entry.ResourcePoolInfo = ResourcePoolInfo;
19291960
entry.BackupCollectionInfo = BackupCollectionInfo;
1961+
entry.SysViewInfo = SysViewInfo;
19301962
}
19311963

19321964
bool CheckColumns(TResolveContext* context, TResolve::TEntry& entry,
@@ -2042,7 +2074,7 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
20422074
return SetError(context, entry, TResolve::EStatus::LookupError, TKeyDesc::EStatus::NotExists);
20432075
}
20442076

2045-
if (!keyDesc.TableId.SysViewInfo.empty()) {
2077+
if (!keyDesc.TableId.SysViewInfo.empty() && Kind != TNavigate::KindSysView) {
20462078
if (Kind == TNavigate::KindPath) {
20472079
auto split = SplitPath(Path);
20482080
if (split.size() == 1) {
@@ -2124,7 +2156,8 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
21242156
}
21252157
}
21262158

2127-
if (keyDesc.GetPartitions().empty()) {
2159+
const bool isSysView = Kind == TNavigate::KindSysView;
2160+
if (keyDesc.GetPartitions().empty() && !isSysView) {
21282161
entry.Status = TResolve::EStatus::TypeCheckError;
21292162
keyDesc.Status = TKeyDesc::EStatus::OperationNotSupported;
21302163
}
@@ -2228,6 +2261,10 @@ class TSchemeCache: public TMonitorableActor<TSchemeCache> {
22282261

22292262
// BackupCollection specific
22302263
TIntrusivePtr<TNavigate::TBackupCollectionInfo> BackupCollectionInfo;
2264+
2265+
// SysView specific
2266+
TIntrusivePtr<TNavigate::TSysViewInfo> SysViewInfo;
2267+
22312268
}; // TCacheItem
22322269

22332270
struct TMerger {

ydb/core/tx/scheme_cache/scheme_cache.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,11 @@ struct TSchemeCacheNavigate {
316316
NKikimrSchemeOp::TBackupCollectionDescription Description;
317317
};
318318

319+
struct TSysViewInfo : public TAtomicRefCount<TSysViewInfo> {
320+
EKind Kind = KindUnknown;
321+
NKikimrSchemeOp::TSysViewDescription Description;
322+
};
323+
319324
struct TEntry {
320325
enum class ERequestType : ui8 {
321326
ByPath,
@@ -370,6 +375,7 @@ struct TSchemeCacheNavigate {
370375
TIntrusiveConstPtr<TViewInfo> ViewInfo;
371376
TIntrusiveConstPtr<TResourcePoolInfo> ResourcePoolInfo;
372377
TIntrusiveConstPtr<TBackupCollectionInfo> BackupCollectionInfo;
378+
TIntrusiveConstPtr<TSysViewInfo> SysViewInfo;
373379

374380
TString ToString() const;
375381
TString ToString(const NScheme::TTypeRegistry& typeRegistry) const;

0 commit comments

Comments
 (0)