Source for file DibiDatabaseInfo.php

Documentation is available at DibiDatabaseInfo.php

  1. 1: <?php
  2. 2:  
  3. 3: /**
  4. 4:  * dibi - tiny'n'smart database abstraction layer
  5. 5:  * ----------------------------------------------
  6. 6:  *
  7. 7:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  8. 8:  * @license    http://dibiphp.com/license  dibi license
  9. 9:  * @link       http://dibiphp.com
  10. 10:  * @package    dibi
  11. 11:  */
  12. 12:  
  13. 13:  
  14. 14:  
  15. 15: /**
  16. 16:  * Reflection metadata class for a database.
  17. 17:  *
  18. 18:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  19. 19:  * @package    dibi
  20. 20:  *
  21. 21:  * @property-read string $name 
  22. 22:  * @property-read array $tables 
  23. 23:  * @property-read array $tableNames 
  24. 24:  */
  25. 25: class DibiDatabaseInfo extends DibiObject
  26. 26: {
  27. 27:     /** @var IDibiReflector */
  28. 28:     private $reflector;
  29. 29:  
  30. 30:     /** @var string */
  31. 31:     private $name;
  32. 32:  
  33. 33:     /** @var array */
  34. 34:     private $tables;
  35. 35:  
  36. 36:  
  37. 37:  
  38. 38:     public function __construct(IDibiReflector $reflector$name)
  39. 39:     {
  40. 40:         $this->reflector $reflector;
  41. 41:         $this->name $name;
  42. 42:     }
  43. 43:  
  44. 44:  
  45. 45:  
  46. 46:     /**
  47. 47:      * @return string 
  48. 48:      */
  49. 49:     public function getName()
  50. 50:     {
  51. 51:         return $this->name;
  52. 52:     }
  53. 53:  
  54. 54:  
  55. 55:  
  56. 56:     /**
  57. 57:      * @return array of DibiTableInfo
  58. 58:      */
  59. 59:     public function getTables()
  60. 60:     {
  61. 61:         $this->init();
  62. 62:         return array_values($this->tables);
  63. 63:     }
  64. 64:  
  65. 65:  
  66. 66:  
  67. 67:     /**
  68. 68:      * @return array of string
  69. 69:      */
  70. 70:     public function getTableNames()
  71. 71:     {
  72. 72:         $this->init();
  73. 73:         $res array();
  74. 74:         foreach ($this->tables as $table{
  75. 75:             $res[$table->getName();
  76. 76:         }
  77. 77:         return $res;
  78. 78:     }
  79. 79:  
  80. 80:  
  81. 81:  
  82. 82:     /**
  83. 83:      * @param  string 
  84. 84:      * @return DibiTableInfo 
  85. 85:      */
  86. 86:     public function getTable($name)
  87. 87:     {
  88. 88:         $name DibiTranslator::substitute($name);
  89. 89:         $this->init();
  90. 90:         $l strtolower($name);
  91. 91:         if (isset($this->tables[$l])) {
  92. 92:             return $this->tables[$l];
  93. 93:  
  94. 94:         else {
  95. 95:             throw new DibiException("Database '$this->name' has no table '$name'.");
  96. 96:         }
  97. 97:     }
  98. 98:  
  99. 99:  
  100. 100:  
  101. 101:     /**
  102. 102:      * @param  string 
  103. 103:      * @return bool 
  104. 104:      */
  105. 105:     public function hasTable($name)
  106. 106:     {
  107. 107:         $name DibiTranslator::substitute($name);
  108. 108:         $this->init();
  109. 109:         return isset($this->tables[strtolower($name)]);
  110. 110:     }
  111. 111:  
  112. 112:  
  113. 113:  
  114. 114:     /**
  115. 115:      * @return void 
  116. 116:      */
  117. 117:     protected function init()
  118. 118:     {
  119. 119:         if ($this->tables === NULL{
  120. 120:             $this->tables array();
  121. 121:             foreach ($this->reflector->getTables(as $info{
  122. 122:                 $this->tables[strtolower($info['name'])new DibiTableInfo($this->reflector$info);
  123. 123:             }
  124. 124:         }
  125. 125:     }
  126. 126:  
  127. 128:  
  128. 129:  
  129. 130:  
  130. 131:  
  131. 132: /**
  132. 133:  * Reflection metadata class for a database table.
  133. 134:  *
  134. 135:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  135. 136:  * @package    dibi
  136. 137:  *
  137. 138:  * @property-read string $name 
  138. 139:  * @property-read bool $view 
  139. 140:  * @property-read array $columns 
  140. 141:  * @property-read array $columnNames 
  141. 142:  * @property-read array $foreignKeys 
  142. 143:  * @property-read array $indexes 
  143. 144:  * @property-read DibiIndexInfo $primaryKey 
  144. 145:  */
  145. 146: class DibiTableInfo extends DibiObject
  146. 148:     /** @var IDibiReflector */
  147. 149:     private $reflector;
  148. 150:  
  149. 151:     /** @var string */
  150. 152:     private $name;
  151. 153:  
  152. 154:     /** @var bool */
  153. 155:     private $view;
  154. 156:  
  155. 157:     /** @var array */
  156. 158:     private $columns;
  157. 159:  
  158. 160:     /** @var array */
  159. 161:     private $foreignKeys;
  160. 162:  
  161. 163:     /** @var array */
  162. 164:     private $indexes;
  163. 165:  
  164. 166:     /** @var DibiIndexInfo */
  165. 167:     private $primaryKey;
  166. 168:  
  167. 169:  
  168. 170:  
  169. 171:     public function __construct(IDibiReflector $reflectorarray $info)
  170. 172:     {
  171. 173:         $this->reflector $reflector;
  172. 174:         $this->name $info['name'];
  173. 175:         $this->view !empty($info['view']);
  174. 176:     }
  175. 177:  
  176. 178:  
  177. 179:  
  178. 180:     /**
  179. 181:      * @return string 
  180. 182:      */
  181. 183:     public function getName()
  182. 184:     {
  183. 185:         return $this->name;
  184. 186:     }
  185. 187:  
  186. 188:  
  187. 189:  
  188. 190:     /**
  189. 191:      * @return bool 
  190. 192:      */
  191. 193:     public function isView()
  192. 194:     {
  193. 195:         return $this->view;
  194. 196:     }
  195. 197:  
  196. 198:  
  197. 199:  
  198. 200:     /**
  199. 201:      * @return array of DibiColumnInfo
  200. 202:      */
  201. 203:     public function getColumns()
  202. 204:     {
  203. 205:         $this->initColumns();
  204. 206:         return array_values($this->columns);
  205. 207:     }
  206. 208:  
  207. 209:  
  208. 210:  
  209. 211:     /**
  210. 212:      * @return array of string
  211. 213:      */
  212. 214:     public function getColumnNames()
  213. 215:     {
  214. 216:         $this->initColumns();
  215. 217:         $res array();
  216. 218:         foreach ($this->columns as $column{
  217. 219:             $res[$column->getName();
  218. 220:         }
  219. 221:         return $res;
  220. 222:     }
  221. 223:  
  222. 224:  
  223. 225:  
  224. 226:     /**
  225. 227:      * @param  string 
  226. 228:      * @return DibiColumnInfo 
  227. 229:      */
  228. 230:     public function getColumn($name)
  229. 231:     {
  230. 232:         $name DibiTranslator::substitute($name);
  231. 233:         $this->initColumns();
  232. 234:         $l strtolower($name);
  233. 235:         if (isset($this->columns[$l])) {
  234. 236:             return $this->columns[$l];
  235. 237:  
  236. 238:         else {
  237. 239:             throw new DibiException("Table '$this->name' has no column '$name'.");
  238. 240:         }
  239. 241:     }
  240. 242:  
  241. 243:  
  242. 244:  
  243. 245:     /**
  244. 246:      * @param  string 
  245. 247:      * @return bool 
  246. 248:      */
  247. 249:     public function hasColumn($name)
  248. 250:     {
  249. 251:         $name DibiTranslator::substitute($name);
  250. 252:         $this->initColumns();
  251. 253:         return isset($this->columns[strtolower($name)]);
  252. 254:     }
  253. 255:  
  254. 256:  
  255. 257:  
  256. 258:     /**
  257. 259:      * @return array of DibiForeignKeyInfo
  258. 260:      */
  259. 261:     public function getForeignKeys()
  260. 262:     {
  261. 263:         $this->initForeignKeys();
  262. 264:         return $this->foreignKeys;
  263. 265:     }
  264. 266:  
  265. 267:  
  266. 268:  
  267. 269:     /**
  268. 270:      * @return array of DibiIndexInfo
  269. 271:      */
  270. 272:     public function getIndexes()
  271. 273:     {
  272. 274:         $this->initIndexes();
  273. 275:         return $this->indexes;
  274. 276:     }
  275. 277:  
  276. 278:  
  277. 279:  
  278. 280:     /**
  279. 281:      * @return DibiIndexInfo 
  280. 282:      */
  281. 283:     public function getPrimaryKey()
  282. 284:     {
  283. 285:         $this->initIndexes();
  284. 286:         return $this->primaryKey;
  285. 287:     }
  286. 288:  
  287. 289:  
  288. 290:  
  289. 291:     /**
  290. 292:      * @return void 
  291. 293:      */
  292. 294:     protected function initColumns()
  293. 295:     {
  294. 296:         if ($this->columns === NULL{
  295. 297:             $this->columns array();
  296. 298:             foreach ($this->reflector->getColumns($this->nameas $info{
  297. 299:                 $this->columns[strtolower($info['name'])new DibiColumnInfo($this->reflector$info);
  298. 300:             }
  299. 301:         }
  300. 302:     }
  301. 303:  
  302. 304:  
  303. 305:  
  304. 306:     /**
  305. 307:      * @return void 
  306. 308:      */
  307. 309:     protected function initIndexes()
  308. 310:     {
  309. 311:         if ($this->indexes === NULL{
  310. 312:             $this->initColumns();
  311. 313:             $this->indexes array();
  312. 314:             foreach ($this->reflector->getIndexes($this->nameas $info{
  313. 315:                 foreach ($info['columns'as $key => $name{
  314. 316:                     $info['columns'][$key$this->columns[strtolower($name)];
  315. 317:                 }
  316. 318:                 $this->indexes[strtolower($info['name'])new DibiIndexInfo($info);
  317. 319:                 if (!empty($info['primary'])) {
  318. 320:                     $this->primaryKey $this->indexes[strtolower($info['name'])];
  319. 321:                 }
  320. 322:             }
  321. 323:         }
  322. 324:     }
  323. 325:  
  324. 326:  
  325. 327:  
  326. 328:     /**
  327. 329:      * @return void 
  328. 330:      */
  329. 331:     protected function initForeignKeys()
  330. 332:     {
  331. 333:         throw new NotImplementedException;
  332. 334:     }
  333. 335:  
  334. 337:  
  335. 338:  
  336. 339:  
  337. 340:  
  338. 341: /**
  339. 342:  * Reflection metadata class for a result set.
  340. 343:  *
  341. 344:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  342. 345:  * @package    dibi
  343. 346:  *
  344. 347:  * @property-read array $columns 
  345. 348:  * @property-read array $columnNames 
  346. 349:  */
  347. 350: class DibiResultInfo extends DibiObject
  348. 352:     /** @var IDibiResultDriver */
  349. 353:     private $driver;
  350. 354:  
  351. 355:     /** @var array */
  352. 356:     private $columns;
  353. 357:  
  354. 358:     /** @var array */
  355. 359:     private $names;
  356. 360:  
  357. 361:  
  358. 362:  
  359. 363:     public function __construct(IDibiResultDriver $driver)
  360. 364:     {
  361. 365:         $this->driver $driver;
  362. 366:     }
  363. 367:  
  364. 368:  
  365. 369:  
  366. 370:     /**
  367. 371:      * @return array of DibiColumnInfo
  368. 372:      */
  369. 373:     public function getColumns()
  370. 374:     {
  371. 375:         $this->initColumns();
  372. 376:         return array_values($this->columns);
  373. 377:     }
  374. 378:  
  375. 379:  
  376. 380:  
  377. 381:     /**
  378. 382:      * @param  bool 
  379. 383:      * @return array of string
  380. 384:      */
  381. 385:     public function getColumnNames($fullNames FALSE)
  382. 386:     {
  383. 387:         $this->initColumns();
  384. 388:         $res array();
  385. 389:         foreach ($this->columns as $column{
  386. 390:             $res[$fullNames $column->getFullName($column->getName();
  387. 391:         }
  388. 392:         return $res;
  389. 393:     }
  390. 394:  
  391. 395:  
  392. 396:  
  393. 397:     /**
  394. 398:      * @param  string 
  395. 399:      * @return DibiColumnInfo 
  396. 400:      */
  397. 401:     public function getColumn($name)
  398. 402:     {
  399. 403:         $name DibiTranslator::substitute($name);
  400. 404:         $this->initColumns();
  401. 405:         $l strtolower($name);
  402. 406:         if (isset($this->names[$l])) {
  403. 407:             return $this->names[$l];
  404. 408:  
  405. 409:         else {
  406. 410:             throw new DibiException("Result set has no column '$name'.");
  407. 411:         }
  408. 412:     }
  409. 413:  
  410. 414:  
  411. 415:  
  412. 416:     /**
  413. 417:      * @param  string 
  414. 418:      * @return bool 
  415. 419:      */
  416. 420:     public function hasColumn($name)
  417. 421:     {
  418. 422:         $name DibiTranslator::substitute($name);
  419. 423:         $this->initColumns();
  420. 424:         return isset($this->names[strtolower($name)]);
  421. 425:     }
  422. 426:  
  423. 427:  
  424. 428:  
  425. 429:     /**
  426. 430:      * @return void 
  427. 431:      */
  428. 432:     protected function initColumns()
  429. 433:     {
  430. 434:         if ($this->columns === NULL{
  431. 435:             $this->columns array();
  432. 436:             $reflector $this->driver instanceof IDibiReflector $this->driver NULL;
  433. 437:             foreach ($this->driver->getResultColumns(as $info{
  434. 438:                 $this->columns[$this->names[$info['name']] new DibiColumnInfo($reflector$info);
  435. 439:             }
  436. 440:         }
  437. 441:     }
  438. 442:  
  439. 444:  
  440. 445:  
  441. 446:  
  442. 447:  
  443. 448: /**
  444. 449:  * Reflection metadata class for a table or result set column.
  445. 450:  *
  446. 451:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  447. 452:  * @package    dibi
  448. 453:  *
  449. 454:  * @property-read string $name 
  450. 455:  * @property-read string $fullName 
  451. 456:  * @property-read DibiTableInfo $table 
  452. 457:  * @property-read string $type 
  453. 458:  * @property-read mixed $nativeType 
  454. 459:  * @property-read int $size 
  455. 460:  * @property-read bool $unsigned 
  456. 461:  * @property-read bool $nullable 
  457. 462:  * @property-read bool $autoIncrement 
  458. 463:  * @property-read mixed $default 
  459. 464:  */
  460. 465: class DibiColumnInfo extends DibiObject
  461. 467:     /** @var array */
  462. 468:     private static $types;
  463. 469:  
  464. 470:     /** @var IDibiReflector|NULLwhen created by DibiResultInfo */
  465. 471:     private $reflector;
  466. 472:  
  467. 473:     /** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
  468. 474:     private $info;
  469. 475:  
  470. 476:  
  471. 477:  
  472. 478:     public function __construct(IDibiReflector $reflector NULLarray $info)
  473. 479:     {
  474. 480:         $this->reflector $reflector;
  475. 481:         $this->info $info;
  476. 482:     }
  477. 483:  
  478. 484:  
  479. 485:  
  480. 486:     /**
  481. 473: /**
  482. 474:      * @return string 
  483. 488:      */
  484. 489:     public function getName()
  485. 490:     {
  486. 491:         return $this->info['name'];
  487. 492:     }
  488. 493:  
  489. 494:  
  490. 495:  
  491. 496:     /**
  492. 497:      * @return string 
  493. 498:      */
  494. 499:     public function getFullName()
  495. 500:     {
  496. 501:         return isset($this->info['fullname']$this->info['fullname'NULL;
  497. 502:     }
  498. 503:  
  499. 504:  
  500. 505:  
  501. 506:     /**
  502. 507:      * @return bool 
  503. 508:      */
  504. 509:     public function hasTable()
  505. 510:     {
  506. 511:         return !empty($this->info['table']);
  507. 512:     }
  508. 513:  
  509. 514:  
  510. 515:  
  511. 516:     /**
  512. 517:      * @return DibiTableInfo 
  513. 518:      */
  514. 519:     public function getTable()
  515. 520:     {
  516. 521:         if (empty($this->info['table']|| !$this->reflector{
  517. 522:             throw new DibiException("Table is unknown or not available.");
  518. 523:         }
  519. 524:         return new DibiTableInfo($this->reflectorarray('name' => $this->info['table']));
  520. 525:     }
  521. 526:  
  522. 527:  
  523. 528:  
  524. 529:     /**
  525. 530:      * @return string 
  526. 531:      */
  527. 532:     public function getTableName()
  528. 533:     {
  529. 534:         return isset($this->info['table']$this->info['table'NULL;
  530. 535:     }
  531. 536:  
  532. 537:  
  533. 538:  
  534. 539:     /**
  535. 540:      * @return string 
  536. 541:      */
  537. 542:     public function getType()
  538. 543:     {
  539. 544:         if (self::$types === NULL{
  540. 545:             self::$types new DibiLazyStorage(array(__CLASS__'detectType'));
  541. 546:         }
  542. 547:         return self::$types->{$this->info['nativetype']};
  543. 548:     }
  544. 549:  
  545. 550:  
  546. 551:  
  547. 552:     /**
  548. 553:      * @return mixed 
  549. 554:      */
  550. 555:     public function getNativeType()
  551. 556:     {
  552. 557:         return $this->info['nativetype'];
  553. 558:     }
  554. 559:  
  555. 560:  
  556. 561:  
  557. 562:     /**
  558. 563:      * @return int 
  559. 564:      */
  560. 565:     public function getSize()
  561. 566:     {
  562. 567:         return isset($this->info['size']? (int) $this->info['size'NULL;
  563. 568:     }
  564. 569:  
  565. 570:  
  566. 571:  
  567. 572:     /**
  568. 573:      * @return bool 
  569. 574:      */
  570. 575:     public function isUnsigned()
  571. 576:     {
  572. 577:         return isset($this->info['unsigned']? (bool) $this->info['unsigned'NULL;
  573. 578:     }
  574. 579:  
  575. 580:  
  576. 581:  
  577. 582:     /**
  578. 583:      * @return bool 
  579. 584:      */
  580. 585:     public function isNullable()
  581. 586:     {
  582. 587:         return isset($this->info['nullable']? (bool) $this->info['nullable'NULL;
  583. 588:     }
  584. 589:  
  585. 590:  
  586. 591:  
  587. 592:     /**
  588. 593:      * @return bool 
  589. 594:      */
  590. 595:     public function isAutoIncrement()
  591. 596:     {
  592. 597:         return isset($this->info['autoincrement']? (bool) $this->info['autoincrement'NULL;
  593. 598:     }
  594. 599:  
  595. 600:  
  596. 601:  
  597. 602:     /**
  598. 603:      * @return mixed 
  599. 604:      */
  600. 605:     public function getDefault()
  601. 606:     {
  602. 607:         return isset($this->info['default']$this->info['default'NULL;
  603. 608:     }
  604. 609:  
  605. 610:  
  606. 611:  
  607. 612:     /**
  608. 613:      * @param  string 
  609. 614:      * @return mixed 
  610. 615:      */
  611. 616:     public function getVendorInfo($key)
  612. 617:     {
  613. 618:         return isset($this->info['vendor'][$key]$this->info['vendor'][$keyNULL;
  614. 619:     }
  615. 620:  
  616. 621:  
  617. 622:  
  618. 623:     /**
  619. 624:      * Heuristic type detection.
  620. 625:      * @param  string 
  621. 626:      * @return string 
  622. 627:      * @ignore internal
  623. 628:      */
  624. 629:     public static function detectType($type)
  625. 630:     {
  626. 631:         static $patterns array(
  627. 632:             'BYTEA|BLOB|BIN' => dibi::BINARY,
  628. 633:             'TEXT|CHAR|BIGINT|LONGLONG' => dibi::TEXT,
  629. 634:             'BYTE|COUNTER|SERIAL|INT|LONG' => dibi::INTEGER,
  630. 635:             'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => dibi::FLOAT,
  631. 636:             '^TIME$' => dibi::TIME,
  632. 637:             'TIME' => dibi::DATETIME// DATETIME, TIMESTAMP
  633. 638:             'YEAR|DATE' => dibi::DATE,
  634. 639:             'BOOL|BIT' => dibi::BOOL,
  635. 640:         );
  636. 641:  
  637. 642:         foreach ($patterns as $s => $val{
  638. 643:             if (preg_match("#$s#i"$type)) {
  639. 644:                 return $val;
  640. 645:             }
  641. 646:         }
  642. 647:         return dibi::TEXT;
  643. 648:     }
  644. 649:  
  645. 651:  
  646. 652:  
  647. 653:  
  648. 654:  
  649. 655: /**
  650. 656:  * Reflection metadata class for a foreign key.
  651. 657:  *
  652. 658:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  653. 659:  * @package    dibi
  654. 660:  * @todo
  655. 661:  *
  656. 662:  * @property-read string $name 
  657. 663:  * @property-read array $references 
  658. 664:  */
  659. 667:     /** @var string */
  660. 668:     private $name;
  661. 669:  
  662. 670:     /** @var array of array(local, foreign, onDelete, onUpdate) */
  663. 671:     private $references;
  664. 672:  
  665. 673:  
  666. 674:  
  667. 675:     public function __construct($namearray $references)
  668. 676:     {
  669. 677:         $this->name $name;
  670. 678:         $this->references $references;
  671. 679:     }
  672. 680:  
  673. 681:  
  674. 682:  
  675. 683:     /**
  676. 670: /**
  677. 671:      * @return string 
  678. 685:      */
  679. 686:     public function getName()
  680. 687:     {
  681. 688:         return $this->name;
  682. 689:     }
  683. 690:  
  684. 691:  
  685. 692:  
  686. 693:     /**
  687. 694:      * @return array 
  688. 695:      */
  689. 696:     public function getReferences()
  690. 697:     {
  691. 698:         return $this->references;
  692. 699:     }
  693. 700:  
  694. 702:  
  695. 703:  
  696. 704:  
  697. 705:  
  698. 706: /**
  699. 707:  * Reflection metadata class for a index or primary key.
  700. 708:  *
  701. 709:  * @copyright  Copyright (c) 2005, 2010 David Grudl
  702. 710:  * @package    dibi
  703. 711:  *
  704. 712:  * @property-read string $name 
  705. 713:  * @property-read array $columns 
  706. 714:  * @property-read bool $unique 
  707. 715:  * @property-read bool $primary 
  708. 716:  */
  709. 717: class DibiIndexInfo extends DibiObject
  710. 719:     /** @var array (name, columns, [unique], [primary]) */
  711. 720:     private $info;
  712. 721:  
  713. 722:  
  714. 723:     public function __construct(array $info)
  715. 724:     {
  716. 725:         $this->info $info;
  717. 726:     }
  718. 727:  
  719. 728:  
  720. 729:  
  721. 730:     /**
  722. 719: /**
  723. 720:      * @return string 
  724. 732:      */
  725. 733:     public function getName()
  726. 734:     {
  727. 735:         return $this->info['name'];
  728. 736:     }
  729. 737:  
  730. 738:  
  731. 739:  
  732. 740:     /**
  733. 741:      * @return array 
  734. 742:      */
  735. 743:     public function getColumns()
  736. 744:     {
  737. 745:         return $this->info['columns'];
  738. 746:     }
  739. 747:  
  740. 748:  
  741. 749:  
  742. 750:     /**
  743. 751:      * @return bool 
  744. 752:      */
  745. 753:     public function isUnique()
  746. 754:     {
  747. 755:         return !empty($this->info['unique']);
  748. 756:     }
  749. 757:  
  750. 758:  
  751. 759:  
  752. 760:     /**
  753. 761:      * @return bool 
  754. 762:      */
  755. 763:     public function isPrimary()
  756. 764:     {
  757. 765:         return !empty($this->info['primary']);
  758. 766:     }
  759. 767: