|
@@ -482,36 +482,34 @@ class ReflectedColumn implements \JsonSerializable
|
482
|
482
|
class ReflectedDatabase implements \JsonSerializable
|
483
|
483
|
{
|
484
|
484
|
private $name;
|
485
|
|
- private $tableNames;
|
|
485
|
+ private $tableTypes;
|
486
|
486
|
|
487
|
|
- public function __construct(String $name, array $tableNames)
|
|
487
|
+ public function __construct(String $name, array $tableTypes)
|
488
|
488
|
{
|
489
|
489
|
$this->name = $name;
|
490
|
|
- $this->tableNames = [];
|
491
|
|
- foreach ($tableNames as $tableName) {
|
492
|
|
- $this->tableNames[$tableName] = true;
|
493
|
|
- }
|
|
490
|
+ $this->tableTypes = $tableTypes;
|
494
|
491
|
}
|
495
|
492
|
|
496
|
493
|
public static function fromReflection(GenericReflection $reflection): ReflectedDatabase
|
497
|
494
|
{
|
498
|
495
|
$name = $reflection->getDatabaseName();
|
499
|
|
- $tableNames = [];
|
|
496
|
+ $tableTypes = [];
|
500
|
497
|
foreach ($reflection->getTables() as $table) {
|
501
|
498
|
$tableName = $table['TABLE_NAME'];
|
|
499
|
+ $tableType = $table['TABLE_TYPE'];
|
502
|
500
|
if (in_array($tableName, $reflection->getIgnoredTables())) {
|
503
|
501
|
continue;
|
504
|
502
|
}
|
505
|
|
- $tableNames[$tableName] = true;
|
|
503
|
+ $tableTypes[$tableName] = $tableType;
|
506
|
504
|
}
|
507
|
|
- return new ReflectedDatabase($name, array_keys($tableNames));
|
|
505
|
+ return new ReflectedDatabase($name, $tableTypes);
|
508
|
506
|
}
|
509
|
507
|
|
510
|
508
|
public static function fromJson( /* object */$json): ReflectedDatabase
|
511
|
509
|
{
|
512
|
510
|
$name = $json->name;
|
513
|
|
- $tableNames = $json->tables;
|
514
|
|
- return new ReflectedDatabase($name, $tableNames);
|
|
511
|
+ $tableTypes = (array) $json->tables;
|
|
512
|
+ return new ReflectedDatabase($name, $tableTypes);
|
515
|
513
|
}
|
516
|
514
|
|
517
|
515
|
public function getName(): String
|
|
@@ -521,20 +519,25 @@ class ReflectedDatabase implements \JsonSerializable
|
521
|
519
|
|
522
|
520
|
public function hasTable(String $tableName): bool
|
523
|
521
|
{
|
524
|
|
- return isset($this->tableNames[$tableName]);
|
|
522
|
+ return isset($this->tableTypes[$tableName]);
|
|
523
|
+ }
|
|
524
|
+
|
|
525
|
+ public function getType(String $tableName): String
|
|
526
|
+ {
|
|
527
|
+ return isset($this->tableTypes[$tableName]) ? $this->tableTypes[$tableName] : '';
|
525
|
528
|
}
|
526
|
529
|
|
527
|
530
|
public function getTableNames(): array
|
528
|
531
|
{
|
529
|
|
- return array_keys($this->tableNames);
|
|
532
|
+ return array_keys($this->tableTypes);
|
530
|
533
|
}
|
531
|
534
|
|
532
|
535
|
public function removeTable(String $tableName): bool
|
533
|
536
|
{
|
534
|
|
- if (!isset($this->tableNames[$tableName])) {
|
|
537
|
+ if (!isset($this->tableTypes[$tableName])) {
|
535
|
538
|
return false;
|
536
|
539
|
}
|
537
|
|
- unset($this->tableNames[$tableName]);
|
|
540
|
+ unset($this->tableTypes[$tableName]);
|
538
|
541
|
return true;
|
539
|
542
|
}
|
540
|
543
|
|
|
@@ -542,7 +545,7 @@ class ReflectedDatabase implements \JsonSerializable
|
542
|
545
|
{
|
543
|
546
|
return [
|
544
|
547
|
'name' => $this->name,
|
545
|
|
- 'tables' => array_keys($this->tableNames),
|
|
548
|
+ 'tables' => $this->tableTypes,
|
546
|
549
|
];
|
547
|
550
|
}
|
548
|
551
|
|
|
@@ -557,13 +560,15 @@ class ReflectedDatabase implements \JsonSerializable
|
557
|
560
|
class ReflectedTable implements \JsonSerializable
|
558
|
561
|
{
|
559
|
562
|
private $name;
|
|
563
|
+ private $type;
|
560
|
564
|
private $columns;
|
561
|
565
|
private $pk;
|
562
|
566
|
private $fks;
|
563
|
567
|
|
564
|
|
- public function __construct(String $name, array $columns)
|
|
568
|
+ public function __construct(String $name, String $type, array $columns)
|
565
|
569
|
{
|
566
|
570
|
$this->name = $name;
|
|
571
|
+ $this->type = $type;
|
567
|
572
|
$this->columns = [];
|
568
|
573
|
foreach ($columns as $column) {
|
569
|
574
|
$columnName = $column->getName();
|
|
@@ -585,10 +590,10 @@ class ReflectedTable implements \JsonSerializable
|
585
|
590
|
}
|
586
|
591
|
}
|
587
|
592
|
|
588
|
|
- public static function fromReflection(GenericReflection $reflection, String $name): ReflectedTable
|
|
593
|
+ public static function fromReflection(GenericReflection $reflection, String $name, String $type): ReflectedTable
|
589
|
594
|
{
|
590
|
595
|
$columns = [];
|
591
|
|
- foreach ($reflection->getTableColumns($name) as $tableColumn) {
|
|
596
|
+ foreach ($reflection->getTableColumns($name, $type) as $tableColumn) {
|
592
|
597
|
$column = ReflectedColumn::fromReflection($reflection, $tableColumn);
|
593
|
598
|
$columns[$column->getName()] = $column;
|
594
|
599
|
}
|
|
@@ -604,19 +609,20 @@ class ReflectedTable implements \JsonSerializable
|
604
|
609
|
foreach ($fks as $columnName => $table) {
|
605
|
610
|
$columns[$columnName]->setFk($table);
|
606
|
611
|
}
|
607
|
|
- return new ReflectedTable($name, array_values($columns));
|
|
612
|
+ return new ReflectedTable($name, $type, array_values($columns));
|
608
|
613
|
}
|
609
|
614
|
|
610
|
615
|
public static function fromJson( /* object */$json): ReflectedTable
|
611
|
616
|
{
|
612
|
617
|
$name = $json->name;
|
|
618
|
+ $type = $json->type;
|
613
|
619
|
$columns = [];
|
614
|
620
|
if (isset($json->columns) && is_array($json->columns)) {
|
615
|
621
|
foreach ($json->columns as $column) {
|
616
|
622
|
$columns[] = ReflectedColumn::fromJson($column);
|
617
|
623
|
}
|
618
|
624
|
}
|
619
|
|
- return new ReflectedTable($name, $columns);
|
|
625
|
+ return new ReflectedTable($name, $type, $columns);
|
620
|
626
|
}
|
621
|
627
|
|
622
|
628
|
public function hasColumn(String $columnName): bool
|
|
@@ -639,6 +645,11 @@ class ReflectedTable implements \JsonSerializable
|
639
|
645
|
return $this->name;
|
640
|
646
|
}
|
641
|
647
|
|
|
648
|
+ public function getType(): String
|
|
649
|
+ {
|
|
650
|
+ return $this->type;
|
|
651
|
+ }
|
|
652
|
+
|
642
|
653
|
public function columnNames(): array
|
643
|
654
|
{
|
644
|
655
|
return array_keys($this->columns);
|
|
@@ -673,6 +684,7 @@ class ReflectedTable implements \JsonSerializable
|
673
|
684
|
{
|
674
|
685
|
return [
|
675
|
686
|
'name' => $this->name,
|
|
687
|
+ 'type' => $this->type,
|
676
|
688
|
'columns' => array_values($this->columns),
|
677
|
689
|
];
|
678
|
690
|
}
|
|
@@ -871,7 +883,8 @@ class ReflectionService
|
871
|
883
|
if ($data != '') {
|
872
|
884
|
$table = ReflectedTable::fromJson(json_decode(gzuncompress($data)));
|
873
|
885
|
} else {
|
874
|
|
- $table = ReflectedTable::fromReflection($this->db->reflection(), $tableName);
|
|
886
|
+ $tableType = $this->database->getType($tableName);
|
|
887
|
+ $table = ReflectedTable::fromReflection($this->db->reflection(), $tableName, $tableType);
|
875
|
888
|
$data = gzcompress(json_encode($table, JSON_UNESCAPED_UNICODE));
|
876
|
889
|
$this->cache->set("ReflectedTable($tableName)", $data, $this->ttl);
|
877
|
890
|
}
|
|
@@ -893,6 +906,11 @@ class ReflectionService
|
893
|
906
|
return $this->database->hasTable($tableName);
|
894
|
907
|
}
|
895
|
908
|
|
|
909
|
+ public function getType(String $tableName): String
|
|
910
|
+ {
|
|
911
|
+ return $this->database->getType($tableName);
|
|
912
|
+ }
|
|
913
|
+
|
896
|
914
|
public function getTable(String $tableName): ReflectedTable
|
897
|
915
|
{
|
898
|
916
|
if (!isset($this->tables[$tableName])) {
|
|
@@ -1147,11 +1165,14 @@ class RecordController
|
1147
|
1165
|
public function read(Request $request): Response
|
1148
|
1166
|
{
|
1149
|
1167
|
$table = $request->getPathSegment(2);
|
1150
|
|
- $id = $request->getPathSegment(3);
|
1151
|
|
- $params = $request->getParams();
|
1152
|
1168
|
if (!$this->service->hasTable($table)) {
|
1153
|
1169
|
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
1154
|
1170
|
}
|
|
1171
|
+ if ($this->service->getType($table) != 'table') {
|
|
1172
|
+ return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
|
|
1173
|
+ }
|
|
1174
|
+ $id = $request->getPathSegment(3);
|
|
1175
|
+ $params = $request->getParams();
|
1155
|
1176
|
if (strpos($id, ',') !== false) {
|
1156
|
1177
|
$ids = explode(',', $id);
|
1157
|
1178
|
$result = [];
|
|
@@ -1171,14 +1192,17 @@ class RecordController
|
1171
|
1192
|
public function create(Request $request): Response
|
1172
|
1193
|
{
|
1173
|
1194
|
$table = $request->getPathSegment(2);
|
|
1195
|
+ if (!$this->service->hasTable($table)) {
|
|
1196
|
+ return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
|
1197
|
+ }
|
|
1198
|
+ if ($this->service->getType($table) != 'table') {
|
|
1199
|
+ return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
|
|
1200
|
+ }
|
1174
|
1201
|
$record = $request->getBody();
|
1175
|
1202
|
if ($record === null) {
|
1176
|
1203
|
return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
|
1177
|
1204
|
}
|
1178
|
1205
|
$params = $request->getParams();
|
1179
|
|
- if (!$this->service->hasTable($table)) {
|
1180
|
|
- return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
1181
|
|
- }
|
1182
|
1206
|
if (is_array($record)) {
|
1183
|
1207
|
$result = array();
|
1184
|
1208
|
foreach ($record as $r) {
|
|
@@ -1193,15 +1217,18 @@ class RecordController
|
1193
|
1217
|
public function update(Request $request): Response
|
1194
|
1218
|
{
|
1195
|
1219
|
$table = $request->getPathSegment(2);
|
|
1220
|
+ if (!$this->service->hasTable($table)) {
|
|
1221
|
+ return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
|
1222
|
+ }
|
|
1223
|
+ if ($this->service->getType($table) != 'table') {
|
|
1224
|
+ return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
|
|
1225
|
+ }
|
1196
|
1226
|
$id = $request->getPathSegment(3);
|
|
1227
|
+ $params = $request->getParams();
|
1197
|
1228
|
$record = $request->getBody();
|
1198
|
1229
|
if ($record === null) {
|
1199
|
1230
|
return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
|
1200
|
1231
|
}
|
1201
|
|
- $params = $request->getParams();
|
1202
|
|
- if (!$this->service->hasTable($table)) {
|
1203
|
|
- return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
1204
|
|
- }
|
1205
|
1232
|
$ids = explode(',', $id);
|
1206
|
1233
|
if (is_array($record)) {
|
1207
|
1234
|
if (count($ids) != count($record)) {
|
|
@@ -1223,11 +1250,14 @@ class RecordController
|
1223
|
1250
|
public function delete(Request $request): Response
|
1224
|
1251
|
{
|
1225
|
1252
|
$table = $request->getPathSegment(2);
|
1226
|
|
- $id = $request->getPathSegment(3);
|
1227
|
|
- $params = $request->getParams();
|
1228
|
1253
|
if (!$this->service->hasTable($table)) {
|
1229
|
1254
|
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
1230
|
1255
|
}
|
|
1256
|
+ if ($this->service->getType($table) != 'table') {
|
|
1257
|
+ return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
|
|
1258
|
+ }
|
|
1259
|
+ $id = $request->getPathSegment(3);
|
|
1260
|
+ $params = $request->getParams();
|
1231
|
1261
|
$ids = explode(',', $id);
|
1232
|
1262
|
if (count($ids) > 1) {
|
1233
|
1263
|
$result = array();
|
|
@@ -1243,15 +1273,18 @@ class RecordController
|
1243
|
1273
|
public function increment(Request $request): Response
|
1244
|
1274
|
{
|
1245
|
1275
|
$table = $request->getPathSegment(2);
|
|
1276
|
+ if (!$this->service->hasTable($table)) {
|
|
1277
|
+ return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
|
1278
|
+ }
|
|
1279
|
+ if ($this->service->getType($table) != 'table') {
|
|
1280
|
+ return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
|
|
1281
|
+ }
|
1246
|
1282
|
$id = $request->getPathSegment(3);
|
1247
|
1283
|
$record = $request->getBody();
|
1248
|
1284
|
if ($record === null) {
|
1249
|
1285
|
return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
|
1250
|
1286
|
}
|
1251
|
1287
|
$params = $request->getParams();
|
1252
|
|
- if (!$this->service->hasTable($table)) {
|
1253
|
|
- return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
|
1254
|
|
- }
|
1255
|
1288
|
$ids = explode(',', $id);
|
1256
|
1289
|
if (is_array($record)) {
|
1257
|
1290
|
if (count($ids) != count($record)) {
|
|
@@ -2406,7 +2439,7 @@ class GenericReflection
|
2406
|
2439
|
{
|
2407
|
2440
|
switch ($this->driver) {
|
2408
|
2441
|
case 'mysql':return [];
|
2409
|
|
- case 'pgsql':return ['spatial_ref_sys'];
|
|
2442
|
+ case 'pgsql':return ['spatial_ref_sys', 'raster_columns', 'raster_overviews', 'geography_columns', 'geometry_columns'];
|
2410
|
2443
|
case 'sqlsrv':return [];
|
2411
|
2444
|
}
|
2412
|
2445
|
}
|
|
@@ -2414,9 +2447,9 @@ class GenericReflection
|
2414
|
2447
|
private function getTablesSQL(): String
|
2415
|
2448
|
{
|
2416
|
2449
|
switch ($this->driver) {
|
2417
|
|
- case 'mysql':return 'SELECT "TABLE_NAME" FROM "INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" IN (\'BASE TABLE\') AND "TABLE_SCHEMA" = ? ORDER BY BINARY "TABLE_NAME"';
|
2418
|
|
- case 'pgsql':return 'SELECT c.relname as "TABLE_NAME" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (\'r\') AND n.nspname <> \'pg_catalog\' AND n.nspname <> \'information_schema\' AND n.nspname !~ \'^pg_toast\' AND pg_catalog.pg_table_is_visible(c.oid) AND \'\' <> ? ORDER BY "TABLE_NAME";';
|
2419
|
|
- case 'sqlsrv':return 'SELECT o.name as "TABLE_NAME" FROM sysobjects o WHERE o.xtype = \'U\' ORDER BY "TABLE_NAME"';
|
|
2450
|
+ case 'mysql':return 'SELECT "TABLE_NAME", "TABLE_TYPE" FROM "INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" IN (\'BASE TABLE\' , \'VIEW\') AND "TABLE_SCHEMA" = ? ORDER BY BINARY "TABLE_NAME"';
|
|
2451
|
+ case 'pgsql':return 'SELECT c.relname as "TABLE_NAME", c.relkind as "TABLE_TYPE" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (\'r\', \'v\') AND n.nspname <> \'pg_catalog\' AND n.nspname <> \'information_schema\' AND n.nspname !~ \'^pg_toast\' AND pg_catalog.pg_table_is_visible(c.oid) AND \'\' <> ? ORDER BY "TABLE_NAME";';
|
|
2452
|
+ case 'sqlsrv':return 'SELECT o.name as "TABLE_NAME", o.xtype as "TABLE_TYPE" FROM sysobjects o WHERE o.xtype IN (\'U\', \'V\') ORDER BY "TABLE_NAME"';
|
2420
|
2453
|
}
|
2421
|
2454
|
}
|
2422
|
2455
|
|
|
@@ -2455,13 +2488,36 @@ class GenericReflection
|
2455
|
2488
|
public function getTables(): array
|
2456
|
2489
|
{
|
2457
|
2490
|
$sql = $this->getTablesSQL();
|
2458
|
|
- return $this->query($sql, [$this->database]);
|
|
2491
|
+ $results = $this->query($sql, [$this->database]);
|
|
2492
|
+ foreach ($results as &$result) {
|
|
2493
|
+ switch ($this->driver) {
|
|
2494
|
+ case 'mysql':
|
|
2495
|
+ $map = ['BASE TABLE' => 'table', 'VIEW' => 'view'];
|
|
2496
|
+ $result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
|
|
2497
|
+ break;
|
|
2498
|
+ case 'pgsql':
|
|
2499
|
+ $map = ['r' => 'table', 'v' => 'view'];
|
|
2500
|
+ $result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
|
|
2501
|
+ break;
|
|
2502
|
+ case 'sqlsrv':
|
|
2503
|
+ $map = ['U' => 'table', 'V' => 'view'];
|
|
2504
|
+ $result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
|
|
2505
|
+ break;
|
|
2506
|
+ }
|
|
2507
|
+ }
|
|
2508
|
+ return $results;
|
2459
|
2509
|
}
|
2460
|
2510
|
|
2461
|
|
- public function getTableColumns(String $tableName): array
|
|
2511
|
+ public function getTableColumns(String $tableName, String $type): array
|
2462
|
2512
|
{
|
2463
|
2513
|
$sql = $this->getTableColumnsSQL();
|
2464
|
|
- return $this->query($sql, [$tableName, $this->database]);
|
|
2514
|
+ $results = $this->query($sql, [$tableName, $this->database]);
|
|
2515
|
+ if ($type == 'view') {
|
|
2516
|
+ foreach ($results as &$result) {
|
|
2517
|
+ $result['IS_NULLABLE'] = false;
|
|
2518
|
+ }
|
|
2519
|
+ }
|
|
2520
|
+ return $results;
|
2465
|
2521
|
}
|
2466
|
2522
|
|
2467
|
2523
|
public function getTablePrimaryKeys(String $tableName): array
|
|
@@ -3494,12 +3550,16 @@ class OpenApiBuilder
|
3494
|
3550
|
private function setPath(String $tableName) /*: void*/
|
3495
|
3551
|
{
|
3496
|
3552
|
$table = $this->reflection->getTable($tableName);
|
|
3553
|
+ $type = $table->getType($tableName);
|
3497
|
3554
|
$pk = $table->getPk();
|
3498
|
3555
|
$pkName = $pk ? $pk->getName() : '';
|
3499
|
3556
|
foreach ($this->operations as $operation => $method) {
|
3500
|
3557
|
if (!$pkName && $operation != 'list') {
|
3501
|
3558
|
continue;
|
3502
|
3559
|
}
|
|
3560
|
+ if ($type != 'table' && $operation != 'list') {
|
|
3561
|
+ continue;
|
|
3562
|
+ }
|
3503
|
3563
|
if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
|
3504
|
3564
|
continue;
|
3505
|
3565
|
}
|
|
@@ -4041,6 +4101,7 @@ class ErrorCode
|
4041
|
4101
|
const ACCESS_DENIED = 1012;
|
4042
|
4102
|
const INPUT_VALIDATION_FAILED = 1013;
|
4043
|
4103
|
const OPERATION_FORBIDDEN = 1014;
|
|
4104
|
+ const OPERATION_NOT_SUPPORTED = 1015;
|
4044
|
4105
|
|
4045
|
4106
|
private $values = [
|
4046
|
4107
|
9999 => ["%s", Response::INTERNAL_SERVER_ERROR],
|
|
@@ -4059,6 +4120,7 @@ class ErrorCode
|
4059
|
4120
|
1012 => ["Access denied for '%s'", Response::FORBIDDEN],
|
4060
|
4121
|
1013 => ["Input validation failed for '%s'", Response::UNPROCESSABLE_ENTITY],
|
4061
|
4122
|
1014 => ["Operation forbidden", Response::FORBIDDEN],
|
|
4123
|
+ 1015 => ["Operation '%s' not supported", Response::METHOD_NOT_ALLOWED],
|
4062
|
4124
|
];
|
4063
|
4125
|
|
4064
|
4126
|
public function __construct(int $code)
|
|
@@ -4370,6 +4432,11 @@ class RecordService
|
4370
|
4432
|
return $this->reflection->hasTable($table);
|
4371
|
4433
|
}
|
4372
|
4434
|
|
|
4435
|
+ public function getType(String $table): String
|
|
4436
|
+ {
|
|
4437
|
+ return $this->reflection->getType($table);
|
|
4438
|
+ }
|
|
4439
|
+
|
4373
|
4440
|
public function create(String $tableName, /* object */ $record, array $params)
|
4374
|
4441
|
{
|
4375
|
4442
|
$this->sanitizeRecord($tableName, $record, '');
|
|
@@ -5231,10 +5298,10 @@ class Response
|
5231
|
5298
|
const UNAUTHORIZED = 401;
|
5232
|
5299
|
const FORBIDDEN = 403;
|
5233
|
5300
|
const NOT_FOUND = 404;
|
|
5301
|
+ const METHOD_NOT_ALLOWED = 405;
|
5234
|
5302
|
const CONFLICT = 409;
|
5235
|
5303
|
const UNPROCESSABLE_ENTITY = 422;
|
5236
|
5304
|
const INTERNAL_SERVER_ERROR = 500;
|
5237
|
|
-
|
5238
|
5305
|
private $status;
|
5239
|
5306
|
private $headers;
|
5240
|
5307
|
private $body;
|