Validate.php 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  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. * PHP 4 compatible constructor
  104. *
  105. * @param bool $fail_on_invalid_names array with reserved words per RDBMS
  106. * @param array $valid_types information of all valid fields
  107. * types
  108. * @param bool $force_defaults if true sets a default value to
  109. * field when not explicit
  110. * @param int $max_identifiers_length maximum allowed size for entities
  111. * name
  112. *
  113. * @return void
  114. *
  115. * @access public
  116. * @static
  117. */
  118. function MDB2_Schema_Validate($fail_on_invalid_names = true, $valid_types = array(),
  119. $force_defaults = true, $max_identifiers_length = null
  120. ) {
  121. $this->__construct($fail_on_invalid_names, $valid_types, $force_defaults);
  122. }
  123. // }}}
  124. // {{{ raiseError()
  125. /**
  126. * Pushes a MDB2_Schema_Error into stack and returns it
  127. *
  128. * @param int $ecode MDB2_Schema's error code
  129. * @param string $msg textual message
  130. *
  131. * @return object
  132. * @access private
  133. * @static
  134. */
  135. function &raiseError($ecode, $msg = null)
  136. {
  137. $error = MDB2_Schema::raiseError($ecode, null, null, $msg);
  138. return $error;
  139. }
  140. // }}}
  141. // {{{ isBoolean()
  142. /**
  143. * Verifies if a given value can be considered boolean. If yes, set value
  144. * to true or false according to its actual contents and return true. If
  145. * not, keep its contents untouched and return false.
  146. *
  147. * @param mixed &$value value to be checked
  148. *
  149. * @return bool
  150. *
  151. * @access public
  152. * @static
  153. */
  154. function isBoolean(&$value)
  155. {
  156. if (is_bool($value)) {
  157. return true;
  158. }
  159. if ($value === 0 || $value === 1 || $value === '') {
  160. $value = (bool)$value;
  161. return true;
  162. }
  163. if (!is_string($value)) {
  164. return false;
  165. }
  166. switch ($value) {
  167. case '0':
  168. case 'N':
  169. case 'n':
  170. case 'no':
  171. case 'false':
  172. $value = false;
  173. break;
  174. case '1':
  175. case 'Y':
  176. case 'y':
  177. case 'yes':
  178. case 'true':
  179. $value = true;
  180. break;
  181. default:
  182. return false;
  183. }
  184. return true;
  185. }
  186. // }}}
  187. // {{{ validateTable()
  188. /* Definition */
  189. /**
  190. * Checks whether the definition of a parsed table is valid. Modify table
  191. * definition when necessary.
  192. *
  193. * @param array $tables multi dimensional array that contains the
  194. * tables of current database.
  195. * @param array &$table multi dimensional array that contains the
  196. * structure and optional data of the table.
  197. * @param string $table_name name of the parsed table
  198. *
  199. * @return bool|error object
  200. *
  201. * @access public
  202. */
  203. function validateTable($tables, &$table, $table_name)
  204. {
  205. /* Table name duplicated? */
  206. if (is_array($tables) && isset($tables[$table_name])) {
  207. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  208. 'table "'.$table_name.'" already exists');
  209. }
  210. /**
  211. * Valid name ?
  212. */
  213. $result = $this->validateIdentifier($table_name, 'table');
  214. if (PEAR::isError($result)) {
  215. return $result;
  216. }
  217. /* Was */
  218. if (empty($table['was'])) {
  219. $table['was'] = $table_name;
  220. }
  221. /* Have we got fields? */
  222. if (empty($table['fields']) || !is_array($table['fields'])) {
  223. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  224. 'tables need one or more fields');
  225. }
  226. /* Autoincrement */
  227. $autoinc = $primary = false;
  228. foreach ($table['fields'] as $field_name => $field) {
  229. if (!empty($field['autoincrement'])) {
  230. if ($autoinc) {
  231. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  232. 'there was already an autoincrement field in "'.$table_name.'" before "'.$field_name.'"');
  233. }
  234. $autoinc = $field_name;
  235. }
  236. }
  237. /*
  238. * Checking Indexes
  239. * this have to be done here otherwise we can't
  240. * guarantee that all table fields were already
  241. * defined in the moment we are parsing indexes
  242. */
  243. if (!empty($table['indexes']) && is_array($table['indexes'])) {
  244. foreach ($table['indexes'] as $name => $index) {
  245. $skip_index = false;
  246. if (!empty($index['primary'])) {
  247. /*
  248. * Lets see if we should skip this index since there is
  249. * already an auto increment on this field this implying
  250. * a primary key index.
  251. */
  252. if (count($index['fields']) == '1'
  253. && $autoinc
  254. && array_key_exists($autoinc, $index['fields'])) {
  255. $skip_index = true;
  256. } elseif ($autoinc || $primary) {
  257. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  258. 'there was already an primary index or autoincrement field in "'.$table_name.'" before "'.$name.'"');
  259. } else {
  260. $primary = true;
  261. }
  262. }
  263. if (!$skip_index && is_array($index['fields'])) {
  264. foreach ($index['fields'] as $field_name => $field) {
  265. if (!isset($table['fields'][$field_name])) {
  266. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  267. 'index field "'.$field_name.'" does not exist');
  268. }
  269. if (!empty($index['primary'])
  270. && !$table['fields'][$field_name]['notnull']
  271. ) {
  272. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  273. 'all primary key fields must be defined notnull in "'.$table_name.'"');
  274. }
  275. }
  276. } else {
  277. unset($table['indexes'][$name]);
  278. }
  279. }
  280. }
  281. return MDB2_OK;
  282. }
  283. // }}}
  284. // {{{ validateField()
  285. /**
  286. * Checks whether the definition of a parsed field is valid. Modify field
  287. * definition when necessary.
  288. *
  289. * @param array $fields multi dimensional array that contains the
  290. * fields of current table.
  291. * @param array &$field multi dimensional array that contains the
  292. * structure of the parsed field.
  293. * @param string $field_name name of the parsed field
  294. *
  295. * @return bool|error object
  296. *
  297. * @access public
  298. */
  299. function validateField($fields, &$field, $field_name)
  300. {
  301. /**
  302. * Valid name ?
  303. */
  304. $result = $this->validateIdentifier($field_name, 'field');
  305. if (PEAR::isError($result)) {
  306. return $result;
  307. }
  308. /* Field name duplicated? */
  309. if (is_array($fields) && isset($fields[$field_name])) {
  310. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  311. 'field "'.$field_name.'" already exists');
  312. }
  313. /* Type check */
  314. if (empty($field['type'])) {
  315. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  316. 'no field type specified');
  317. }
  318. if (!empty($this->valid_types) && !array_key_exists($field['type'], $this->valid_types)) {
  319. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  320. 'no valid field type ("'.$field['type'].'") specified');
  321. }
  322. /* Unsigned */
  323. if (array_key_exists('unsigned', $field) && !$this->isBoolean($field['unsigned'])) {
  324. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  325. 'unsigned has to be a boolean value');
  326. }
  327. /* Fixed */
  328. if (array_key_exists('fixed', $field) && !$this->isBoolean($field['fixed'])) {
  329. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  330. 'fixed has to be a boolean value');
  331. }
  332. /* Length */
  333. if (array_key_exists('length', $field) && $field['length'] <= 0) {
  334. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  335. 'length has to be an integer greater 0');
  336. }
  337. // if it's a DECIMAL datatype, check if a 'scale' value is provided:
  338. // <length>8,4</length> should be translated to DECIMAL(8,4)
  339. if (is_float($this->valid_types[$field['type']])
  340. && !empty($field['length'])
  341. && strpos($field['length'], ',') !== false
  342. ) {
  343. list($field['length'], $field['scale']) = explode(',', $field['length']);
  344. }
  345. /* Was */
  346. if (empty($field['was'])) {
  347. $field['was'] = $field_name;
  348. }
  349. /* Notnull */
  350. if (empty($field['notnull'])) {
  351. $field['notnull'] = false;
  352. }
  353. if (!$this->isBoolean($field['notnull'])) {
  354. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  355. 'field "notnull" has to be a boolean value');
  356. }
  357. /* Default */
  358. if ($this->force_defaults
  359. && !array_key_exists('default', $field)
  360. && $field['type'] != 'clob' && $field['type'] != 'blob'
  361. ) {
  362. $field['default'] = $this->valid_types[$field['type']];
  363. }
  364. if (array_key_exists('default', $field)) {
  365. if ($field['type'] == 'clob' || $field['type'] == 'blob') {
  366. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  367. '"'.$field['type'].'"-fields are not allowed to have a default value');
  368. }
  369. if ($field['default'] === '' && !$field['notnull']) {
  370. $field['default'] = null;
  371. }
  372. }
  373. if (isset($field['default'])
  374. && PEAR::isError($result = $this->validateDataFieldValue($field, $field['default'], $field_name))
  375. ) {
  376. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  377. 'default value of "'.$field_name.'" is incorrect: '.$result->getUserinfo());
  378. }
  379. /* Autoincrement */
  380. if (!empty($field['autoincrement'])) {
  381. if (!$field['notnull']) {
  382. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  383. 'all autoincrement fields must be defined notnull');
  384. }
  385. if (empty($field['default'])) {
  386. $field['default'] = '0';
  387. } elseif ($field['default'] !== '0' && $field['default'] !== 0) {
  388. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  389. 'all autoincrement fields must be defined default "0"');
  390. }
  391. }
  392. return MDB2_OK;
  393. }
  394. // }}}
  395. // {{{ validateIndex()
  396. /**
  397. * Checks whether a parsed index is valid. Modify index definition when
  398. * necessary.
  399. *
  400. * @param array $table_indexes multi dimensional array that contains the
  401. * indexes of current table.
  402. * @param array &$index multi dimensional array that contains the
  403. * structure of the parsed index.
  404. * @param string $index_name name of the parsed index
  405. *
  406. * @return bool|error object
  407. *
  408. * @access public
  409. */
  410. function validateIndex($table_indexes, &$index, $index_name)
  411. {
  412. /**
  413. * Valid name ?
  414. */
  415. $result = $this->validateIdentifier($index_name, 'index');
  416. if (PEAR::isError($result)) {
  417. return $result;
  418. }
  419. if (is_array($table_indexes) && isset($table_indexes[$index_name])) {
  420. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  421. 'index "'.$index_name.'" already exists');
  422. }
  423. if (array_key_exists('unique', $index) && !$this->isBoolean($index['unique'])) {
  424. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  425. 'field "unique" has to be a boolean value');
  426. }
  427. if (array_key_exists('primary', $index) && !$this->isBoolean($index['primary'])) {
  428. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  429. 'field "primary" has to be a boolean value');
  430. }
  431. /* Have we got fields? */
  432. if (empty($index['fields']) || !is_array($index['fields'])) {
  433. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  434. 'indexes need one or more fields');
  435. }
  436. if (empty($index['was'])) {
  437. $index['was'] = $index_name;
  438. }
  439. return MDB2_OK;
  440. }
  441. // }}}
  442. // {{{ validateIndexField()
  443. /**
  444. * Checks whether a parsed index-field is valid. Modify its definition when
  445. * necessary.
  446. *
  447. * @param array $index_fields multi dimensional array that contains the
  448. * fields of current index.
  449. * @param array &$field multi dimensional array that contains the
  450. * structure of the parsed index-field.
  451. * @param string $field_name name of the parsed index-field
  452. *
  453. * @return bool|error object
  454. *
  455. * @access public
  456. */
  457. function validateIndexField($index_fields, &$field, $field_name)
  458. {
  459. /**
  460. * Valid name ?
  461. */
  462. $result = $this->validateIdentifier($field_name, 'index field');
  463. if (PEAR::isError($result)) {
  464. return $result;
  465. }
  466. if (is_array($index_fields) && isset($index_fields[$field_name])) {
  467. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  468. 'index field "'.$field_name.'" already exists');
  469. }
  470. if (empty($field['sorting'])) {
  471. $field['sorting'] = 'ascending';
  472. } elseif ($field['sorting'] !== 'ascending' && $field['sorting'] !== 'descending') {
  473. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  474. 'sorting type unknown');
  475. }
  476. return MDB2_OK;
  477. }
  478. // }}}
  479. // {{{ validateConstraint()
  480. /**
  481. * Checks whether a parsed foreign key is valid. Modify its definition when
  482. * necessary.
  483. *
  484. * @param array $table_constraints multi dimensional array that contains the
  485. * constraints of current table.
  486. * @param array &$constraint multi dimensional array that contains the
  487. * structure of the parsed foreign key.
  488. * @param string $constraint_name name of the parsed foreign key
  489. *
  490. * @return bool|error object
  491. *
  492. * @access public
  493. */
  494. function validateConstraint($table_constraints, &$constraint, $constraint_name)
  495. {
  496. /**
  497. * Valid name ?
  498. */
  499. $result = $this->validateIdentifier($constraint_name, 'foreign key');
  500. if (PEAR::isError($result)) {
  501. return $result;
  502. }
  503. if (is_array($table_constraints) && isset($table_constraints[$constraint_name])) {
  504. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  505. 'foreign key "'.$constraint_name.'" already exists');
  506. }
  507. /* Have we got fields? */
  508. if (empty($constraint['fields']) || !is_array($constraint['fields'])) {
  509. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  510. 'foreign key "'.$constraint_name.'" need one or more fields');
  511. }
  512. /* Have we got referenced fields? */
  513. if (empty($constraint['references']) || !is_array($constraint['references'])) {
  514. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  515. 'foreign key "'.$constraint_name.'" need to reference one or more fields');
  516. }
  517. /* Have we got referenced table? */
  518. if (empty($constraint['references']['table'])) {
  519. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  520. 'foreign key "'.$constraint_name.'" need to reference a table');
  521. }
  522. if (empty($constraint['was'])) {
  523. $constraint['was'] = $constraint_name;
  524. }
  525. return MDB2_OK;
  526. }
  527. // }}}
  528. // {{{ validateConstraintField()
  529. /**
  530. * Checks whether a foreign-field is valid.
  531. *
  532. * @param array $constraint_fields multi dimensional array that contains the
  533. * fields of current foreign key.
  534. * @param string $field_name name of the parsed foreign-field
  535. *
  536. * @return bool|error object
  537. *
  538. * @access public
  539. */
  540. function validateConstraintField($constraint_fields, $field_name)
  541. {
  542. /**
  543. * Valid name ?
  544. */
  545. $result = $this->validateIdentifier($field_name, 'foreign key field');
  546. if (PEAR::isError($result)) {
  547. return $result;
  548. }
  549. if (is_array($constraint_fields) && isset($constraint_fields[$field_name])) {
  550. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  551. 'foreign field "'.$field_name.'" already exists');
  552. }
  553. return MDB2_OK;
  554. }
  555. // }}}
  556. // {{{ validateConstraintReferencedField()
  557. /**
  558. * Checks whether a foreign-referenced field is valid.
  559. *
  560. * @param array $referenced_fields multi dimensional array that contains the
  561. * fields of current foreign key.
  562. * @param string $field_name name of the parsed foreign-field
  563. *
  564. * @return bool|error object
  565. *
  566. * @access public
  567. */
  568. function validateConstraintReferencedField($referenced_fields, $field_name)
  569. {
  570. /**
  571. * Valid name ?
  572. */
  573. $result = $this->validateIdentifier($field_name, 'referenced foreign field');
  574. if (PEAR::isError($result)) {
  575. return $result;
  576. }
  577. if (is_array($referenced_fields) && isset($referenced_fields[$field_name])) {
  578. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  579. 'foreign field "'.$field_name.'" already referenced');
  580. }
  581. return MDB2_OK;
  582. }
  583. // }}}
  584. // {{{ validateSequence()
  585. /**
  586. * Checks whether the definition of a parsed sequence is valid. Modify
  587. * sequence definition when necessary.
  588. *
  589. * @param array $sequences multi dimensional array that contains the
  590. * sequences of current database.
  591. * @param array &$sequence multi dimensional array that contains the
  592. * structure of the parsed sequence.
  593. * @param string $sequence_name name of the parsed sequence
  594. *
  595. * @return bool|error object
  596. *
  597. * @access public
  598. */
  599. function validateSequence($sequences, &$sequence, $sequence_name)
  600. {
  601. /**
  602. * Valid name ?
  603. */
  604. $result = $this->validateIdentifier($sequence_name, 'sequence');
  605. if (PEAR::isError($result)) {
  606. return $result;
  607. }
  608. if (is_array($sequences) && isset($sequences[$sequence_name])) {
  609. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  610. 'sequence "'.$sequence_name.'" already exists');
  611. }
  612. if (is_array($this->fail_on_invalid_names)) {
  613. $name = strtoupper($sequence_name);
  614. foreach ($this->fail_on_invalid_names as $rdbms) {
  615. if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
  616. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  617. 'sequence name "'.$sequence_name.'" is a reserved word in: '.$rdbms);
  618. }
  619. }
  620. }
  621. if (empty($sequence['was'])) {
  622. $sequence['was'] = $sequence_name;
  623. }
  624. if (!empty($sequence['on'])
  625. && (empty($sequence['on']['table']) || empty($sequence['on']['field']))
  626. ) {
  627. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  628. 'sequence "'.$sequence_name.'" on a table was not properly defined');
  629. }
  630. return MDB2_OK;
  631. }
  632. // }}}
  633. // {{{ validateDatabase()
  634. /**
  635. * Checks whether a parsed database is valid. Modify its structure and
  636. * data when necessary.
  637. *
  638. * @param array &$database multi dimensional array that contains the
  639. * structure and optional data of the database.
  640. *
  641. * @return bool|error object
  642. *
  643. * @access public
  644. */
  645. function validateDatabase(&$database)
  646. {
  647. if (!is_array($database)) {
  648. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  649. 'something wrong went with database definition');
  650. }
  651. /**
  652. * Valid name ?
  653. */
  654. $result = $this->validateIdentifier($database['name'], 'database');
  655. if (PEAR::isError($result)) {
  656. return $result;
  657. }
  658. /* Create */
  659. if (isset($database['create'])
  660. && !$this->isBoolean($database['create'])
  661. ) {
  662. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  663. 'field "create" has to be a boolean value');
  664. }
  665. /* Overwrite */
  666. if (isset($database['overwrite'])
  667. && $database['overwrite'] !== ''
  668. && !$this->isBoolean($database['overwrite'])
  669. ) {
  670. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  671. 'field "overwrite" has to be a boolean value');
  672. }
  673. /*
  674. * This have to be done here otherwise we can't guarantee that all
  675. * tables were already defined in the moment we are parsing constraints
  676. */
  677. if (isset($database['tables'])) {
  678. foreach ($database['tables'] as $table_name => $table) {
  679. if (!empty($table['constraints'])) {
  680. foreach ($table['constraints'] as $constraint_name => $constraint) {
  681. $referenced_table_name = $constraint['references']['table'];
  682. if (!isset($database['tables'][$referenced_table_name])) {
  683. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  684. 'referenced table "'.$referenced_table_name.'" of foreign key "'.$constraint_name.'" of table "'.$table_name.'" does not exist');
  685. }
  686. if (empty($constraint['references']['fields'])) {
  687. $referenced_table = $database['tables'][$referenced_table_name];
  688. $primary = false;
  689. if (!empty($referenced_table['indexes'])) {
  690. foreach ($referenced_table['indexes'] as $index_name => $index) {
  691. if (array_key_exists('primary', $index)
  692. && $index['primary']
  693. ) {
  694. $primary = array();
  695. foreach ($index['fields'] as $field_name => $field) {
  696. $primary[$field_name] = '';
  697. }
  698. break;
  699. }
  700. }
  701. }
  702. if (!$primary) {
  703. foreach ($referenced_table['fields'] as $field_name => $field) {
  704. if (array_key_exists('autoincrement', $field)
  705. && $field['autoincrement']
  706. ) {
  707. $primary = array( $field_name => '' );
  708. break;
  709. }
  710. }
  711. }
  712. if (!$primary) {
  713. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  714. 'referenced table "'.$referenced_table_name.'" has no primary key and no referenced field was specified for foreign key "'.$constraint_name.'" of table "'.$table_name.'"');
  715. }
  716. $constraint['references']['fields'] = $primary;
  717. }
  718. /* the same number of referencing and referenced fields ? */
  719. if (count($constraint['fields']) != count($constraint['references']['fields'])) {
  720. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  721. 'The number of fields in the referenced key must match those of the foreign key "'.$constraint_name.'"');
  722. }
  723. $database['tables'][$table_name]['constraints'][$constraint_name]['references']['fields'] = $constraint['references']['fields'];
  724. }
  725. }
  726. }
  727. }
  728. /*
  729. * This have to be done here otherwise we can't guarantee that all
  730. * tables were already defined in the moment we are parsing sequences
  731. */
  732. if (isset($database['sequences'])) {
  733. foreach ($database['sequences'] as $seq_name => $seq) {
  734. if (!empty($seq['on'])
  735. && empty($database['tables'][$seq['on']['table']]['fields'][$seq['on']['field']])
  736. ) {
  737. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  738. 'sequence "'.$seq_name.'" was assigned on unexisting field/table');
  739. }
  740. }
  741. }
  742. return MDB2_OK;
  743. }
  744. // }}}
  745. // {{{ validateDataField()
  746. /* Data Manipulation */
  747. /**
  748. * Checks whether a parsed DML-field is valid. Modify its structure when
  749. * necessary. This is called when validating INSERT and
  750. * UPDATE.
  751. *
  752. * @param array $table_fields multi dimensional array that contains the
  753. * definition for current table's fields.
  754. * @param array $instruction_fields multi dimensional array that contains the
  755. * parsed fields of the current DML instruction.
  756. * @param string &$field array that contains the parsed instruction field
  757. *
  758. * @return bool|error object
  759. *
  760. * @access public
  761. */
  762. function validateDataField($table_fields, $instruction_fields, &$field)
  763. {
  764. /**
  765. * Valid name ?
  766. */
  767. $result = $this->validateIdentifier($field['name'], 'field');
  768. if (PEAR::isError($result)) {
  769. return $result;
  770. }
  771. if (is_array($instruction_fields) && isset($instruction_fields[$field['name']])) {
  772. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  773. 'field "'.$field['name'].'" already initialized');
  774. }
  775. if (is_array($table_fields) && !isset($table_fields[$field['name']])) {
  776. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  777. '"'.$field['name'].'" is not defined');
  778. }
  779. if (!isset($field['group']['type'])) {
  780. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  781. '"'.$field['name'].'" has no initial value');
  782. }
  783. if (isset($field['group']['data'])
  784. && $field['group']['type'] == 'value'
  785. && $field['group']['data'] !== ''
  786. && PEAR::isError($result = $this->validateDataFieldValue($table_fields[$field['name']], $field['group']['data'], $field['name']))
  787. ) {
  788. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  789. 'value of "'.$field['name'].'" is incorrect: '.$result->getUserinfo());
  790. }
  791. return MDB2_OK;
  792. }
  793. // }}}
  794. // {{{ validateDataFieldValue()
  795. /**
  796. * Checks whether a given value is compatible with a table field. This is
  797. * done when parsing a field for a INSERT or UPDATE instruction.
  798. *
  799. * @param array $field_def multi dimensional array that contains the
  800. * definition for current table's fields.
  801. * @param string &$field_value value to fill the parsed field
  802. * @param string $field_name name of the parsed field
  803. *
  804. * @return bool|error object
  805. *
  806. * @access public
  807. * @see MDB2_Schema_Validate::validateInsertField()
  808. */
  809. function validateDataFieldValue($field_def, &$field_value, $field_name)
  810. {
  811. switch ($field_def['type']) {
  812. case 'text':
  813. case 'clob':
  814. if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) {
  815. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  816. '"'.$field_value.'" is larger than "'.$field_def['length'].'"');
  817. }
  818. break;
  819. case 'blob':
  820. $field_value = pack('H*', $field_value);
  821. if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) {
  822. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  823. '"'.$field_value.'" is larger than "'.$field_def['type'].'"');
  824. }
  825. break;
  826. case 'integer':
  827. if ($field_value != ((int)$field_value)) {
  828. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  829. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  830. }
  831. //$field_value = (int)$field_value;
  832. if (!empty($field_def['unsigned']) && $field_def['unsigned'] && $field_value < 0) {
  833. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  834. '"'.$field_value.'" signed instead of unsigned');
  835. }
  836. break;
  837. case 'boolean':
  838. if (!$this->isBoolean($field_value)) {
  839. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  840. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  841. }
  842. break;
  843. case 'date':
  844. if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})/', $field_value)
  845. && $field_value !== 'CURRENT_DATE'
  846. ) {
  847. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  848. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  849. }
  850. break;
  851. case 'timestamp':
  852. 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)
  853. && strcasecmp($field_value, 'now()') != 0
  854. && $field_value !== 'CURRENT_TIMESTAMP'
  855. ) {
  856. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  857. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  858. }
  859. break;
  860. case 'time':
  861. if (!preg_match("/([0-9]{2}):([0-9]{2}):([0-9]{2})/", $field_value)
  862. && $field_value !== 'CURRENT_TIME'
  863. ) {
  864. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  865. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  866. }
  867. break;
  868. case 'float':
  869. case 'double':
  870. if ($field_value != (double)$field_value) {
  871. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  872. '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
  873. }
  874. //$field_value = (double)$field_value;
  875. break;
  876. }
  877. return MDB2_OK;
  878. }
  879. // }}}
  880. // {{{ validateIdentifier()
  881. /**
  882. * Checks whether a given identifier is valid for current driver.
  883. *
  884. * @param string $id identifier to check
  885. * @param string $type whether identifier represents a table name, index, etc.
  886. *
  887. * @return bool|error object
  888. *
  889. * @access public
  890. */
  891. function validateIdentifier($id, $type)
  892. {
  893. $max_length = $this->max_identifiers_length;
  894. $cur_length = strlen($id);
  895. /**
  896. * Have we got a name?
  897. */
  898. if (!$id) {
  899. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  900. "a $type has to have a name");
  901. }
  902. /**
  903. * Supported length ?
  904. */
  905. if ($max_length !== null
  906. && $cur_length > $max_length
  907. ) {
  908. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  909. "$type name '$id' is too long for current driver");
  910. } elseif ($cur_length > 30) {
  911. // FIXME: find a way to issue a warning in MDB2_Schema object
  912. /* $this->warnings[] = "$type name '$id' might not be
  913. portable to other drivers"; */
  914. }
  915. /**
  916. * Reserved ?
  917. */
  918. if (is_array($this->fail_on_invalid_names)) {
  919. $name = strtoupper($id);
  920. foreach ($this->fail_on_invalid_names as $rdbms) {
  921. if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
  922. return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
  923. "$type name '$id' is a reserved word in: $rdbms");
  924. }
  925. }
  926. }
  927. return MDB2_OK;
  928. }
  929. // }}}
  930. }