Validate.php 35 KB

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