Validate.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. <?php /* vim: se et ts=4 sw=4 sts=4 fdm=marker tw=80: */
  2. /**
  3. * Copyright (c) 1998-2010 Manuel Lemos, Tomas V.V.Cox,
  4. * Stig. S. Bakken, Lukas Smith, Igor Feghali
  5. * All rights reserved.
  6. *
  7. * MDB2_Schema enables users to maintain RDBMS independant schema files
  8. * in XML that can be used to manipulate both data and database schemas
  9. * This LICENSE is in the BSD license style.
  10. *
  11. * Redistribution and use in source and binary forms, with or without
  12. * modification, are permitted provided that the following conditions
  13. * are met:
  14. *
  15. * Redistributions of source code must retain the above copyright
  16. * notice, this list of conditions and the following disclaimer.
  17. *
  18. * Redistributions in binary form must reproduce the above copyright
  19. * notice, this list of conditions and the following disclaimer in the
  20. * documentation and/or other materials provided with the distribution.
  21. *
  22. * Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,
  23. * Lukas Smith, Igor Feghali nor the names of his contributors may be
  24. * used to endorse or promote products derived from this software
  25. * without specific prior written permission.
  26. *
  27. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  28. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  29. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  30. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  31. * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  32. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  33. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  34. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  35. * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  36. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  37. * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  38. * POSSIBILITY OF SUCH DAMAGE.
  39. *
  40. * PHP version 5
  41. *
  42. * @category Database
  43. * @package MDB2_Schema
  44. * @author Christian Dickmann <dickmann@php.net>
  45. * @author Igor Feghali <ifeghali@php.net>
  46. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  47. * @version SVN: $Id$
  48. * @link http://pear.php.net/packages/MDB2_Schema
  49. */
  50. /**
  51. * Validates an XML schema file
  52. *
  53. * @category Database
  54. * @package MDB2_Schema
  55. * @author Igor Feghali <ifeghali@php.net>
  56. * @license BSD http://www.opensource.org/licenses/bsd-license.php
  57. * @link http://pear.php.net/packages/MDB2_Schema
  58. */
  59. class MDB2_Schema_Validate
  60. {
  61. // {{{ properties
  62. var $fail_on_invalid_names = true;
  63. var $valid_types = array();
  64. var $force_defaults = true;
  65. var $max_identifiers_length = null;
  66. // }}}
  67. // {{{ constructor
  68. /**
  69. * PHP 5 constructor
  70. *
  71. * @param bool $fail_on_invalid_names array with reserved words per RDBMS
  72. * @param array $valid_types information of all valid fields
  73. * types
  74. * @param bool $force_defaults if true sets a default value to
  75. * field when not explicit
  76. * @param int $max_identifiers_length maximum allowed size for entities
  77. * name
  78. *
  79. * @return void
  80. *
  81. * @access public
  82. * @static
  83. */
  84. function __construct($fail_on_invalid_names = true, $valid_types = array(),
  85. $force_defaults = true, $max_identifiers_length = null
  86. ) {
  87. if (empty($GLOBALS['_MDB2_Schema_Reserved'])) {
  88. $GLOBALS['_MDB2_Schema_Reserved'] = array();
  89. }
  90. if (is_array($fail_on_invalid_names)) {
  91. $this->fail_on_invalid_names = array_intersect($fail_on_invalid_names,
  92. array_keys($GLOBALS['_MDB2_Schema_Reserved']));
  93. } elseif ($fail_on_invalid_names === true) {
  94. $this->fail_on_invalid_names = array_keys($GLOBALS['_MDB2_Schema_Reserved']);
  95. } else {
  96. $this->fail_on_invalid_names = array();
  97. }
  98. $this->valid_types = $valid_types;
  99. $this->force_defaults = $force_defaults;
  100. $this->max_identifiers_length = $max_identifiers_length;
  101. }
  102. // }}}
  103. // {{{ raiseError()
  104. /**
  105. * Pushes a MDB2_Schema_Error into stack and returns it
  106. *
  107. * @param int $ecode MDB2_Schema's error code
  108. * @param string $msg textual message
  109. *
  110. * @return object
  111. * @access private
  112. * @static
  113. */
  114. function &raiseError($ecode, $msg = null)
  115. {
  116. $error = MDB2_Schema::raiseError($ecode, null, null, $msg);
  117. return $error;
  118. }
  119. // }}}
  120. // {{{ isBoolean()
  121. /**
  122. * Verifies if a given value can be considered boolean. If yes, set value
  123. * to true or false according to its actual contents and return true. If
  124. * not, keep its contents untouched and return false.
  125. *
  126. * @param mixed &$value value to be checked
  127. *
  128. * @return bool
  129. *
  130. * @access public
  131. * @static
  132. */
  133. function isBoolean(&$value)
  134. {
  135. if (is_bool($value)) {
  136. return true;
  137. }
  138. if ($value === 0 || $value === 1 || $value === '') {
  139. $value = (bool)$value;
  140. return true;
  141. }
  142. if (!is_string($value)) {
  143. return false;
  144. }
  145. switch ($value) {
  146. case '0':
  147. case 'N':
  148. case 'n':
  149. case 'no':
  150. case 'false':
  151. $value = false;
  152. break;
  153. case '1':
  154. case 'Y':
  155. case 'y':
  156. case 'yes':
  157. case 'true':
  158. $value = true;
  159. break;
  160. default:
  161. return false;
  162. }
  163. return true;
  164. }
  165. // }}}
  166. // {{{ validateTable()
  167. /* Definition */
  168. /**
  169. * Checks whether the definition of a parsed table is valid. Modify table
  170. * definition when necessary.
  171. *
  172. * @param array $tables multi dimensional array that contains the
  173. * tables of current database.
  174. * @param array &$table multi dimensional array that contains the
  175. * structure and optional data of the table.
  176. * @param string $table_name name of the parsed table
  177. *
  178. * @return bool|error object
  179. *
  180. * @access public
  181. */
  182. function validateTable($tables, &$table, $table_name)
  183. {
  184. /* Table name duplicated? */
  185. if (is_array($tables) && isset($tables[$table_name])) {
  186. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  187. 'table "'.$table_name.'" already exists');
  188. }
  189. /**
  190. * Valid name ?
  191. */
  192. $result = $this->validateIdentifier($table_name, 'table');
  193. if (PEAR::isError($result)) {
  194. return $result;
  195. }
  196. /* Was */
  197. if (empty($table['was'])) {
  198. $table['was'] = $table_name;
  199. }
  200. /* Have we got fields? */
  201. if (empty($table['fields']) || !is_array($table['fields'])) {
  202. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  203. 'tables need one or more fields');
  204. }
  205. /* Autoincrement */
  206. $autoinc = $primary = false;
  207. foreach ($table['fields'] as $field_name => $field) {
  208. if (!empty($field['autoincrement'])) {
  209. if ($autoinc) {
  210. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  211. 'there was already an autoincrement field in "'.$table_name.'" before "'.$field_name.'"');
  212. }
  213. $autoinc = $field_name;
  214. }
  215. }
  216. /*
  217. * Checking Indexes
  218. * this have to be done here otherwise we can't
  219. * guarantee that all table fields were already
  220. * defined in the moment we are parsing indexes
  221. */
  222. if (!empty($table['indexes']) && is_array($table['indexes'])) {
  223. foreach ($table['indexes'] as $name => $index) {
  224. $skip_index = false;
  225. if (!empty($index['primary'])) {
  226. /*
  227. * Lets see if we should skip this index since there is
  228. * already an auto increment on this field this implying
  229. * a primary key index.
  230. */
  231. if (count($index['fields']) == '1'
  232. && $autoinc
  233. && array_key_exists($autoinc, $index['fields'])) {
  234. $skip_index = true;
  235. } elseif ($autoinc || $primary) {
  236. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  237. 'there was already an primary index or autoincrement field in "'.$table_name.'" before "'.$name.'"');
  238. } else {
  239. $primary = true;
  240. }
  241. }
  242. if (!$skip_index && is_array($index['fields'])) {
  243. foreach ($index['fields'] as $field_name => $field) {
  244. if (!isset($table['fields'][$field_name])) {
  245. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  246. 'index field "'.$field_name.'" does not exist');
  247. }
  248. if (!empty($index['primary'])
  249. && !$table['fields'][$field_name]['notnull']
  250. ) {
  251. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  252. 'all primary key fields must be defined notnull in "'.$table_name.'"');
  253. }
  254. }
  255. } else {
  256. unset($table['indexes'][$name]);
  257. }
  258. }
  259. }
  260. return MDB2_OK;
  261. }
  262. // }}}
  263. // {{{ validateField()
  264. /**
  265. * Checks whether the definition of a parsed field is valid. Modify field
  266. * definition when necessary.
  267. *
  268. * @param array $fields multi dimensional array that contains the
  269. * fields of current table.
  270. * @param array &$field multi dimensional array that contains the
  271. * structure of the parsed field.
  272. * @param string $field_name name of the parsed field
  273. *
  274. * @return bool|error object
  275. *
  276. * @access public
  277. */
  278. function validateField($fields, &$field, $field_name)
  279. {
  280. /**
  281. * Valid name ?
  282. */
  283. $result = $this->validateIdentifier($field_name, 'field');
  284. if (PEAR::isError($result)) {
  285. return $result;
  286. }
  287. /* Field name duplicated? */
  288. if (is_array($fields) && isset($fields[$field_name])) {
  289. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  290. 'field "'.$field_name.'" already exists');
  291. }
  292. /* Type check */
  293. if (empty($field['type'])) {
  294. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  295. 'no field type specified');
  296. }
  297. if (!empty($this->valid_types) && !array_key_exists($field['type'], $this->valid_types)) {
  298. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  299. 'no valid field type ("'.$field['type'].'") specified');
  300. }
  301. /* Unsigned */
  302. if (array_key_exists('unsigned', $field) && !$this->isBoolean($field['unsigned'])) {
  303. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  304. 'unsigned has to be a boolean value');
  305. }
  306. /* Fixed */
  307. if (array_key_exists('fixed', $field) && !$this->isBoolean($field['fixed'])) {
  308. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  309. 'fixed has to be a boolean value');
  310. }
  311. /* Length */
  312. if (array_key_exists('length', $field) && $field['length'] <= 0) {
  313. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  314. 'length has to be an integer greater 0');
  315. }
  316. // if it's a DECIMAL datatype, check if a 'scale' value is provided:
  317. // <length>8,4</length> should be translated to DECIMAL(8,4)
  318. if (is_float($this->valid_types[$field['type']])
  319. && !empty($field['length'])
  320. && strpos($field['length'], ',') !== false
  321. ) {
  322. list($field['length'], $field['scale']) = explode(',', $field['length']);
  323. }
  324. /* Was */
  325. if (empty($field['was'])) {
  326. $field['was'] = $field_name;
  327. }
  328. /* Notnull */
  329. if (empty($field['notnull'])) {
  330. $field['notnull'] = false;
  331. }
  332. if (!$this->isBoolean($field['notnull'])) {
  333. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  334. 'field "notnull" has to be a boolean value');
  335. }
  336. /* Default */
  337. if ($this->force_defaults
  338. && !array_key_exists('default', $field)
  339. && $field['type'] != 'clob' && $field['type'] != 'blob'
  340. ) {
  341. $field['default'] = $this->valid_types[$field['type']];
  342. }
  343. if (array_key_exists('default', $field)) {
  344. if ($field['type'] == 'clob' || $field['type'] == 'blob') {
  345. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  346. '"'.$field['type'].'"-fields are not allowed to have a default value');
  347. }
  348. if ($field['default'] === '' && !$field['notnull']) {
  349. $field['default'] = null;
  350. }
  351. }
  352. if (isset($field['default'])
  353. && PEAR::isError($result = $this->validateDataFieldValue($field, $field['default'], $field_name))
  354. ) {
  355. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  356. 'default value of "'.$field_name.'" is incorrect: '.$result->getUserinfo());
  357. }
  358. /* Autoincrement */
  359. if (!empty($field['autoincrement'])) {
  360. if (!$field['notnull']) {
  361. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  362. 'all autoincrement fields must be defined notnull');
  363. }
  364. if (empty($field['default'])) {
  365. $field['default'] = '0';
  366. } elseif ($field['default'] !== '0' && $field['default'] !== 0) {
  367. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  368. 'all autoincrement fields must be defined default "0"');
  369. }
  370. }
  371. return MDB2_OK;
  372. }
  373. // }}}
  374. // {{{ validateIndex()
  375. /**
  376. * Checks whether a parsed index is valid. Modify index definition when
  377. * necessary.
  378. *
  379. * @param array $table_indexes multi dimensional array that contains the
  380. * indexes of current table.
  381. * @param array &$index multi dimensional array that contains the
  382. * structure of the parsed index.
  383. * @param string $index_name name of the parsed index
  384. *
  385. * @return bool|error object
  386. *
  387. * @access public
  388. */
  389. function validateIndex($table_indexes, &$index, $index_name)
  390. {
  391. /**
  392. * Valid name ?
  393. */
  394. $result = $this->validateIdentifier($index_name, 'index');
  395. if (PEAR::isError($result)) {
  396. return $result;
  397. }
  398. if (is_array($table_indexes) && isset($table_indexes[$index_name])) {
  399. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  400. 'index "'.$index_name.'" already exists');
  401. }
  402. if (array_key_exists('unique', $index) && !$this->isBoolean($index['unique'])) {
  403. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  404. 'field "unique" has to be a boolean value');
  405. }
  406. if (array_key_exists('primary', $index) && !$this->isBoolean($index['primary'])) {
  407. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  408. 'field "primary" has to be a boolean value');
  409. }
  410. /* Have we got fields? */
  411. if (empty($index['fields']) || !is_array($index['fields'])) {
  412. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  413. 'indexes need one or more fields');
  414. }
  415. if (empty($index['was'])) {
  416. $index['was'] = $index_name;
  417. }
  418. return MDB2_OK;
  419. }
  420. // }}}
  421. // {{{ validateIndexField()
  422. /**
  423. * Checks whether a parsed index-field is valid. Modify its definition when
  424. * necessary.
  425. *
  426. * @param array $index_fields multi dimensional array that contains the
  427. * fields of current index.
  428. * @param array &$field multi dimensional array that contains the
  429. * structure of the parsed index-field.
  430. * @param string $field_name name of the parsed index-field
  431. *
  432. * @return bool|error object
  433. *
  434. * @access public
  435. */
  436. function validateIndexField($index_fields, &$field, $field_name)
  437. {
  438. /**
  439. * Valid name ?
  440. */
  441. $result = $this->validateIdentifier($field_name, 'index field');
  442. if (PEAR::isError($result)) {
  443. return $result;
  444. }
  445. if (is_array($index_fields) && isset($index_fields[$field_name])) {
  446. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  447. 'index field "'.$field_name.'" already exists');
  448. }
  449. if (empty($field['sorting'])) {
  450. $field['sorting'] = 'ascending';
  451. } elseif ($field['sorting'] !== 'ascending' && $field['sorting'] !== 'descending') {
  452. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  453. 'sorting type unknown');
  454. }
  455. return MDB2_OK;
  456. }
  457. // }}}
  458. // {{{ validateConstraint()
  459. /**
  460. * Checks whether a parsed foreign key is valid. Modify its definition when
  461. * necessary.
  462. *
  463. * @param array $table_constraints multi dimensional array that contains the
  464. * constraints of current table.
  465. * @param array &$constraint multi dimensional array that contains the
  466. * structure of the parsed foreign key.
  467. * @param string $constraint_name name of the parsed foreign key
  468. *
  469. * @return bool|error object
  470. *
  471. * @access public
  472. */
  473. function validateConstraint($table_constraints, &$constraint, $constraint_name)
  474. {
  475. /**
  476. * Valid name ?
  477. */
  478. $result = $this->validateIdentifier($constraint_name, 'foreign key');
  479. if (PEAR::isError($result)) {
  480. return $result;
  481. }
  482. if (is_array($table_constraints) && isset($table_constraints[$constraint_name])) {
  483. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  484. 'foreign key "'.$constraint_name.'" already exists');
  485. }
  486. /* Have we got fields? */
  487. if (empty($constraint['fields']) || !is_array($constraint['fields'])) {
  488. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  489. 'foreign key "'.$constraint_name.'" need one or more fields');
  490. }
  491. /* Have we got referenced fields? */
  492. if (empty($constraint['references']) || !is_array($constraint['references'])) {
  493. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  494. 'foreign key "'.$constraint_name.'" need to reference one or more fields');
  495. }
  496. /* Have we got referenced table? */
  497. if (empty($constraint['references']['table'])) {
  498. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  499. 'foreign key "'.$constraint_name.'" need to reference a table');
  500. }
  501. if (empty($constraint['was'])) {
  502. $constraint['was'] = $constraint_name;
  503. }
  504. return MDB2_OK;
  505. }
  506. // }}}
  507. // {{{ validateConstraintField()
  508. /**
  509. * Checks whether a foreign-field is valid.
  510. *
  511. * @param array $constraint_fields multi dimensional array that contains the
  512. * fields of current foreign key.
  513. * @param string $field_name name of the parsed foreign-field
  514. *
  515. * @return bool|error object
  516. *
  517. * @access public
  518. */
  519. function validateConstraintField($constraint_fields, $field_name)
  520. {
  521. /**
  522. * Valid name ?
  523. */
  524. $result = $this->validateIdentifier($field_name, 'foreign key field');
  525. if (PEAR::isError($result)) {
  526. return $result;
  527. }
  528. if (is_array($constraint_fields) && isset($constraint_fields[$field_name])) {
  529. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  530. 'foreign field "'.$field_name.'" already exists');
  531. }
  532. return MDB2_OK;
  533. }
  534. // }}}
  535. // {{{ validateConstraintReferencedField()
  536. /**
  537. * Checks whether a foreign-referenced field is valid.
  538. *
  539. * @param array $referenced_fields multi dimensional array that contains the
  540. * fields of current foreign key.
  541. * @param string $field_name name of the parsed foreign-field
  542. *
  543. * @return bool|error object
  544. *
  545. * @access public
  546. */
  547. function validateConstraintReferencedField($referenced_fields, $field_name)
  548. {
  549. /**
  550. * Valid name ?
  551. */
  552. $result = $this->validateIdentifier($field_name, 'referenced foreign field');
  553. if (PEAR::isError($result)) {
  554. return $result;
  555. }
  556. if (is_array($referenced_fields) && isset($referenced_fields[$field_name])) {
  557. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  558. 'foreign field "'.$field_name.'" already referenced');
  559. }
  560. return MDB2_OK;
  561. }
  562. // }}}
  563. // {{{ validateSequence()
  564. /**
  565. * Checks whether the definition of a parsed sequence is valid. Modify
  566. * sequence definition when necessary.
  567. *
  568. * @param array $sequences multi dimensional array that contains the
  569. * sequences of current database.
  570. * @param array &$sequence multi dimensional array that contains the
  571. * structure of the parsed sequence.
  572. * @param string $sequence_name name of the parsed sequence
  573. *
  574. * @return bool|error object
  575. *
  576. * @access public
  577. */
  578. function validateSequence($sequences, &$sequence, $sequence_name)
  579. {
  580. /**
  581. * Valid name ?
  582. */
  583. $result = $this->validateIdentifier($sequence_name, 'sequence');
  584. if (PEAR::isError($result)) {
  585. return $result;
  586. }
  587. if (is_array($sequences) && isset($sequences[$sequence_name])) {
  588. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  589. 'sequence "'.$sequence_name.'" already exists');
  590. }
  591. if (is_array($this->fail_on_invalid_names)) {
  592. $name = strtoupper($sequence_name);
  593. foreach ($this->fail_on_invalid_names as $rdbms) {
  594. if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
  595. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  596. 'sequence name "'.$sequence_name.'" is a reserved word in: '.$rdbms);
  597. }
  598. }
  599. }
  600. if (empty($sequence['was'])) {
  601. $sequence['was'] = $sequence_name;
  602. }
  603. if (!empty($sequence['on'])
  604. && (empty($sequence['on']['table']) || empty($sequence['on']['field']))
  605. ) {
  606. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  607. 'sequence "'.$sequence_name.'" on a table was not properly defined');
  608. }
  609. return MDB2_OK;
  610. }
  611. // }}}
  612. // {{{ validateDatabase()
  613. /**
  614. * Checks whether a parsed database is valid. Modify its structure and
  615. * data when necessary.
  616. *
  617. * @param array &$database multi dimensional array that contains the
  618. * structure and optional data of the database.
  619. *
  620. * @return bool|error object
  621. *
  622. * @access public
  623. */
  624. function validateDatabase(&$database)
  625. {
  626. if (!is_array($database)) {
  627. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  628. 'something wrong went with database definition');
  629. }
  630. /**
  631. * Valid name ?
  632. */
  633. $result = $this->validateIdentifier($database['name'], 'database');
  634. if (PEAR::isError($result)) {
  635. return $result;
  636. }
  637. /* Create */
  638. if (isset($database['create'])
  639. && !$this->isBoolean($database['create'])
  640. ) {
  641. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  642. 'field "create" has to be a boolean value');
  643. }
  644. /* Overwrite */
  645. if (isset($database['overwrite'])
  646. && $database['overwrite'] !== ''
  647. && !$this->isBoolean($database['overwrite'])
  648. ) {
  649. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  650. 'field "overwrite" has to be a boolean value');
  651. }
  652. /*
  653. * This have to be done here otherwise we can't guarantee that all
  654. * tables were already defined in the moment we are parsing constraints
  655. */
  656. if (isset($database['tables'])) {
  657. foreach ($database['tables'] as $table_name => $table) {
  658. if (!empty($table['constraints'])) {
  659. foreach ($table['constraints'] as $constraint_name => $constraint) {
  660. $referenced_table_name = $constraint['references']['table'];
  661. if (!isset($database['tables'][$referenced_table_name])) {
  662. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  663. 'referenced table "'.$referenced_table_name.'" of foreign key "'.$constraint_name.'" of table "'.$table_name.'" does not exist');
  664. }
  665. if (empty($constraint['references']['fields'])) {
  666. $referenced_table = $database['tables'][$referenced_table_name];
  667. $primary = false;
  668. if (!empty($referenced_table['indexes'])) {
  669. foreach ($referenced_table['indexes'] as $index_name => $index) {
  670. if (array_key_exists('primary', $index)
  671. && $index['primary']
  672. ) {
  673. $primary = array();
  674. foreach ($index['fields'] as $field_name => $field) {
  675. $primary[$field_name] = '';
  676. }
  677. break;
  678. }
  679. }
  680. }
  681. if (!$primary) {
  682. foreach ($referenced_table['fields'] as $field_name => $field) {
  683. if (array_key_exists('autoincrement', $field)
  684. && $field['autoincrement']
  685. ) {
  686. $primary = array( $field_name => '' );
  687. break;
  688. }
  689. }
  690. }
  691. if (!$primary) {
  692. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  693. 'referenced table "'.$referenced_table_name.'" has no primary key and no referenced field was specified for foreign key "'.$constraint_name.'" of table "'.$table_name.'"');
  694. }
  695. $constraint['references']['fields'] = $primary;
  696. }
  697. /* the same number of referencing and referenced fields ? */
  698. if (count($constraint['fields']) != count($constraint['references']['fields'])) {
  699. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  700. 'The number of fields in the referenced key must match those of the foreign key "'.$constraint_name.'"');
  701. }
  702. $database['tables'][$table_name]['constraints'][$constraint_name]['references']['fields'] = $constraint['references']['fields'];
  703. }
  704. }
  705. }
  706. }
  707. /*
  708. * This have to be done here otherwise we can't guarantee that all
  709. * tables were already defined in the moment we are parsing sequences
  710. */
  711. if (isset($database['sequences'])) {
  712. foreach ($database['sequences'] as $seq_name => $seq) {
  713. if (!empty($seq['on'])
  714. && empty($database['tables'][$seq['on']['table']]['fields'][$seq['on']['field']])
  715. ) {
  716. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  717. 'sequence "'.$seq_name.'" was assigned on unexisting field/table');
  718. }
  719. }
  720. }
  721. return MDB2_OK;
  722. }
  723. // }}}
  724. // {{{ validateDataField()
  725. /* Data Manipulation */
  726. /**
  727. * Checks whether a parsed DML-field is valid. Modify its structure when
  728. * necessary. This is called when validating INSERT and
  729. * UPDATE.
  730. *
  731. * @param array $table_fields multi dimensional array that contains the
  732. * definition for current table's fields.
  733. * @param array $instruction_fields multi dimensional array that contains the
  734. * parsed fields of the current DML instruction.
  735. * @param string &$field array that contains the parsed instruction field
  736. *
  737. * @return bool|error object
  738. *
  739. * @access public
  740. */
  741. function validateDataField($table_fields, $instruction_fields, &$field)
  742. {
  743. /**
  744. * Valid name ?
  745. */
  746. $result = $this->validateIdentifier($field['name'], 'field');
  747. if (PEAR::isError($result)) {
  748. return $result;
  749. }
  750. if (is_array($instruction_fields) && isset($instruction_fields[$field['name']])) {
  751. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  752. 'field "'.$field['name'].'" already initialized');
  753. }
  754. if (is_array($table_fields) && !isset($table_fields[$field['name']])) {
  755. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  756. '"'.$field['name'].'" is not defined');
  757. }
  758. if (!isset($field['group']['type'])) {
  759. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  760. '"'.$field['name'].'" has no initial value');
  761. }
  762. if (isset($field['group']['data'])
  763. && $field['group']['type'] == 'value'
  764. && $field['group']['data'] !== ''
  765. && PEAR::isError($result = $this->validateDataFieldValue($table_fields[$field['name']], $field['group']['data'], $field['name']))
  766. ) {
  767. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  768. 'value of "'.$field['name'].'" is incorrect: '.$result->getUserinfo());
  769. }
  770. return MDB2_OK;
  771. }
  772. // }}}
  773. // {{{ validateDataFieldValue()
  774. /**
  775. * Checks whether a given value is compatible with a table field. This is
  776. * done when parsing a field for a INSERT or UPDATE instruction.
  777. *
  778. * @param array $field_def multi dimensional array that contains the
  779. * definition for current table's fields.
  780. * @param string &$field_value value to fill the parsed field
  781. * @param string $field_name name of the parsed field
  782. *
  783. * @return bool|error object
  784. *
  785. * @access public
  786. * @see MDB2_Schema_Validate::validateInsertField()
  787. */
  788. function validateDataFieldValue($field_def, &$field_value, $field_name)
  789. {
  790. switch ($field_def['type']) {
  791. case 'text':
  792. case 'clob':
  793. if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) {
  794. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  795. '"'.$field_value.'" is larger than "'.$field_def['length'].'"');
  796. }
  797. break;
  798. case 'blob':
  799. $field_value = pack('H*', $field_value);
  800. if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) {
  801. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  802. '"'.$field_value.'" is larger than "'.$field_def['type'].'"');
  803. }
  804. break;
  805. case 'integer':
  806. if ($field_value != ((int)$field_value)) {
  807. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  808. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  809. }
  810. //$field_value = (int)$field_value;
  811. if (!empty($field_def['unsigned']) && $field_def['unsigned'] && $field_value < 0) {
  812. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  813. '"'.$field_value.'" signed instead of unsigned');
  814. }
  815. break;
  816. case 'boolean':
  817. if (!$this->isBoolean($field_value)) {
  818. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  819. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  820. }
  821. break;
  822. case 'date':
  823. if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})/', $field_value)
  824. && $field_value !== 'CURRENT_DATE'
  825. ) {
  826. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  827. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  828. }
  829. break;
  830. case 'timestamp':
  831. if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', $field_value)
  832. && strcasecmp($field_value, 'now()') != 0
  833. && $field_value !== 'CURRENT_TIMESTAMP'
  834. ) {
  835. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  836. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  837. }
  838. break;
  839. case 'time':
  840. if (!preg_match("/([0-9]{2}):([0-9]{2}):([0-9]{2})/", $field_value)
  841. && $field_value !== 'CURRENT_TIME'
  842. ) {
  843. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  844. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  845. }
  846. break;
  847. case 'float':
  848. case 'double':
  849. if ($field_value != (double)$field_value) {
  850. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  851. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  852. }
  853. //$field_value = (double)$field_value;
  854. break;
  855. }
  856. return MDB2_OK;
  857. }
  858. // }}}
  859. // {{{ validateIdentifier()
  860. /**
  861. * Checks whether a given identifier is valid for current driver.
  862. *
  863. * @param string $id identifier to check
  864. * @param string $type whether identifier represents a table name, index, etc.
  865. *
  866. * @return bool|error object
  867. *
  868. * @access public
  869. */
  870. function validateIdentifier($id, $type)
  871. {
  872. $max_length = $this->max_identifiers_length;
  873. $cur_length = strlen($id);
  874. /**
  875. * Have we got a name?
  876. */
  877. if (!$id) {
  878. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  879. "a $type has to have a name");
  880. }
  881. /**
  882. * Supported length ?
  883. */
  884. if ($max_length !== null
  885. && $cur_length > $max_length
  886. ) {
  887. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  888. "$type name '$id' is too long for current driver");
  889. } elseif ($cur_length > 30) {
  890. // FIXME: find a way to issue a warning in MDB2_Schema object
  891. /* $this->warnings[] = "$type name '$id' might not be
  892. portable to other drivers"; */
  893. }
  894. /**
  895. * Reserved ?
  896. */
  897. if (is_array($this->fail_on_invalid_names)) {
  898. $name = strtoupper($id);
  899. foreach ($this->fail_on_invalid_names as $rdbms) {
  900. if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
  901. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  902. "$type name '$id' is a reserved word in: $rdbms");
  903. }
  904. }
  905. }
  906. return MDB2_OK;
  907. }
  908. // }}}
  909. }