PageRenderTime 65ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/ezdbschema/classes/ezdbschemainterface.php

https://github.com/aurelienRT1/ezpublish
PHP | 1394 lines | 969 code | 121 blank | 304 comment | 154 complexity | 0a47a4cc245619e62b2b516991abf152 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0
  1. <?php
  2. //
  3. // Created on: <21-Apr-2004 11:04:30 kk>
  4. //
  5. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  6. // SOFTWARE NAME: eZ Publish
  7. // SOFTWARE RELEASE: 4.1.x
  8. // COPYRIGHT NOTICE: Copyright (C) 1999-2010 eZ Systems AS
  9. // SOFTWARE LICENSE: GNU General Public License v2.0
  10. // NOTICE: >
  11. // This program is free software; you can redistribute it and/or
  12. // modify it under the terms of version 2.0 of the GNU General
  13. // Public License as published by the Free Software Foundation.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of version 2.0 of the GNU General
  21. // Public License along with this program; if not, write to the Free
  22. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  23. // MA 02110-1301, USA.
  24. //
  25. //
  26. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  27. //
  28. /*! \file
  29. Database schema abstraction layer.
  30. */
  31. /*! \defgroup eZDbSchema Database schema abstraction layer */
  32. /*!
  33. \class eZDBSchemaInterface ezdbschemainterface.php
  34. \ingroup eZDbSchema
  35. \brief This class provide interface for DB schema library
  36. Schema structure an array with Table structures, each key is the name of the
  37. table.
  38. Table structure:
  39. - name - Name of table
  40. - fields - Array of Field structures, each key is the field name
  41. - indexes - Array of Index structures, each key is the index name
  42. - removed - Contains whether the table has been removed or not (Optional)
  43. - comments - An array with comment strings (Optional)
  44. The \c removed entry will only be used when some comments have been added
  45. to the table. That way the comments can be added to the DROP TABLE statements.
  46. Field structure:
  47. - length - A number which defines the length/size of the type or \c false
  48. - type - String containing the identifier of the Type, see Types below.
  49. - not_null - Is 1 if the type cannot be null, if 0 or not defined it can be null
  50. - default - The default value, the value depends on the type, \c false means no default value.
  51. Index structure:
  52. - type - What kind of index, see Index Types.
  53. - fields - Array of field names the index is made on, or of sub-arrays where the field name is in the 'name' field
  54. Index Types:
  55. - primary - A primary key, there can only be one primary key. This key will be named PRIMARY.
  56. - non-unique - A standard index
  57. - unique - A unique index
  58. Field Types:
  59. - int - Integer, uses \c length to define number of digits.
  60. - float - Float, uses \c length to define number of digits.
  61. - auto_increment - Integer that auto increments (uses sequence+trigger).
  62. - varchar - String with variable length, uses \c length for max length.
  63. - char - String with fixed length, uses \c length.
  64. - longtext - String with 2^32 number of possible characters
  65. - mediumtext - String with 2^24 number of possible characters
  66. - shorttext - String with 2^16 number of possible characters
  67. When stored as a PHP array the schema structure will be placed in a variable
  68. called $schema. The data structure will be placed in $data.
  69. */
  70. class eZDBSchemaInterface
  71. {
  72. /*!
  73. Constructor
  74. \sa eZDB
  75. */
  76. function eZDBSchemaInterface( $params )
  77. {
  78. $this->DBInstance = $params['instance'];
  79. $this->Schema = false;
  80. $this->Data = false;
  81. if ( isset( $params['schema'] ) )
  82. $this->Schema = $params['schema'];
  83. if ( isset( $params['data'] ) )
  84. $this->Data = $params['data'];
  85. }
  86. /*!
  87. \pure
  88. Gets SQL db schema definition by analyzing the current DB instance and return
  89. it in array format.
  90. NB: once the schema is generated, it might be cached.
  91. \param $params supported options are 'meta_data' and 'format'
  92. \return DB schema array
  93. */
  94. function schema( $params = array() )
  95. {
  96. }
  97. /*!
  98. Fetches the data for all tables and returns an array containing the data.
  99. NB: once the data is generated, it might be cached.
  100. \param $schema A schema definition array which defines tables to fetch from.
  101. If \c false it will call schema() to fetch it.
  102. \param $tableNameList An array with tables to include, will further narrow
  103. tables in \a $schema. Use \c false to fetch all tables.
  104. \note You shouldn't need to reimplement this method since the default
  105. code will do simple SELECT queries
  106. \sa fetchTableData()
  107. */
  108. function data( $schema = false, $tableNameList = false, $params = array() )
  109. {
  110. $params = array_merge( array( 'meta_data' => false,
  111. 'format' => 'generic' ),
  112. $params );
  113. if ( $this->Data === false )
  114. {
  115. if ( $schema === false )
  116. $schema = $this->schema( $params );
  117. // We need to transform schema to local format for data to be fetched correctly.
  118. if ( $schema['_info']['format'] == 'generic' )
  119. $this->transformSchema( $schema, true );
  120. $data = array();
  121. foreach ( $schema as $tableName => $tableInfo )
  122. {
  123. // Skip the information array, this is not a table
  124. if ( $tableName == '_info' )
  125. continue;
  126. if ( is_array( $tableNameList ) and
  127. !in_array( $tableName, $tableNameList ) )
  128. continue;
  129. $tableEntry = $this->fetchTableData( $tableInfo );
  130. if ( count( $tableEntry['rows'] ) > 0 )
  131. $data[$tableName] = $tableEntry;
  132. }
  133. $this->transformData( $data, $params['format'] == 'local' );
  134. ksort( $data );
  135. $this->Data = $data;
  136. }
  137. else
  138. {
  139. $this->transformData( $this->Data, $params['format'] == 'local' );
  140. $data = $this->Data;
  141. }
  142. return $data;
  143. }
  144. /*!
  145. Validates the current schema and returns \c true if it is correct or
  146. \c false if something must be fixed.
  147. \note This should not be reimplemented by normal handlers, only schema
  148. checkers.
  149. */
  150. function validate()
  151. {
  152. return false;
  153. }
  154. /*!
  155. \protected
  156. Fetches all rows for table defined in \a $tableInfo and returns this structure:
  157. - fields - Array with fields that were fetched from table, the order of the fields
  158. are the same as the order of the data
  159. - rows - Array with all rows, each row is an indexed array with the data.
  160. \param $tableInfo Table structure from schema definition.
  161. \param $offset Which offset to start from or \c false to start at top
  162. \param $limit How many rows to fetch or \c false for no limit.
  163. \note You shouldn't need to reimplement this method since the default
  164. code will do simple SELECT queries
  165. \sa data()
  166. */
  167. function fetchTableData( $tableInfo, $offset = false, $limit = false )
  168. {
  169. if ( count( $tableInfo['fields'] ) == 0 )
  170. return false;
  171. $tableName = $tableInfo['name'];
  172. $fieldText = '';
  173. $i = 0;
  174. $fields = array();
  175. foreach ( $tableInfo['fields'] as $fieldName => $field )
  176. {
  177. if ( $i > 0 )
  178. $fieldText .= ', ';
  179. $fieldText .= $fieldName;
  180. $fields[] = $fieldName;
  181. ++$i;
  182. }
  183. $rows = $this->DBInstance->arrayQuery( "SELECT $fieldText FROM $tableName" );
  184. $resultArray = array();
  185. foreach ( $rows as $row )
  186. {
  187. $rowData = array();
  188. foreach ( $tableInfo['fields'] as $fieldName => $field )
  189. {
  190. if ( $field['type'] == 'char' )
  191. {
  192. $rowData[$fieldName] = str_pad( $row[$fieldName], $field['length'], ' ' );
  193. }
  194. else
  195. {
  196. $rowData[$fieldName] = $row[$fieldName];
  197. }
  198. }
  199. $resultArray[] = array_values( $rowData );
  200. }
  201. return array( 'fields' => $fields,
  202. 'rows' => $resultArray );
  203. }
  204. /*!
  205. \pure
  206. Write upgrade sql to file
  207. \param differences array
  208. \param filename
  209. */
  210. function writeUpgradeFile( $differences, $filename, $params = array() )
  211. {
  212. $params = array_merge( array( 'schema' => true,
  213. 'data' => false,
  214. 'allow_multi_insert' => false,
  215. 'diff_friendly' => false ),
  216. $params );
  217. $fp = @fopen( $filename, 'w' );
  218. if ( $fp )
  219. {
  220. fputs( $fp, $this->generateUpgradeFile( $differences, $params ) );
  221. fclose( $fp );
  222. return true;
  223. }
  224. else
  225. {
  226. return false;
  227. }
  228. }
  229. /*!
  230. Write SQL schema definition to file.
  231. The generated schema is always in 'local' format, as 'generic' SQL does not exist.
  232. \param filename
  233. */
  234. function writeSQLSchemaFile( $filename, $params = array() )
  235. {
  236. $params = array_merge( array( 'schema' => true,
  237. 'data' => false,
  238. 'allow_multi_insert' => false,
  239. 'diff_friendly' => false ),
  240. $params );
  241. $includeSchema = $params['schema'];
  242. $includeData = $params['data'];
  243. $fp = @fopen( $filename, 'w' );
  244. if ( $fp )
  245. {
  246. $schema = $this->schema( $params );
  247. $this->transformSchema( $schema, true );
  248. if ( $includeSchema )
  249. {
  250. fputs( $fp, $this->generateSchemaFile( $schema, $params ) );
  251. }
  252. if ( $includeData )
  253. {
  254. $data = $this->data( $schema );
  255. $this->transformData( $data, true );
  256. fputs( $fp, $this->generateDataFile( $schema, $data, $params ) );
  257. }
  258. fclose( $fp );
  259. return true;
  260. }
  261. else
  262. {
  263. return false;
  264. }
  265. }
  266. /*!
  267. Write PHP schema definition to file using PHP serialized format.
  268. \param filename
  269. */
  270. function writeSerializedSchemaFile( $filename, $params = array() )
  271. {
  272. $params = array_merge( array( 'schema' => true,
  273. 'data' => false ),
  274. $params );
  275. $includeSchema = $params['schema'];
  276. $includeData = $params['data'];
  277. $fp = @fopen( $filename, 'w' );
  278. if ( $fp )
  279. {
  280. $schema = $this->schema( $params );
  281. if ( $includeSchema and $includeData )
  282. {
  283. fputs( $fp, serialize( array( 'schema' => $schema,
  284. 'data' => $this->data( $schema ) ) ) );
  285. }
  286. else if ( $includeSchema )
  287. {
  288. fputs( $fp, serialize( $schema ) );
  289. }
  290. else if ( $includeData )
  291. {
  292. fputs( $fp, serialize( $this->data( $schema ) ) );
  293. }
  294. fclose( $fp );
  295. return true;
  296. }
  297. else
  298. {
  299. return false;
  300. }
  301. }
  302. /*!
  303. Write PHP schema definition to file using PHP array structures.
  304. \param filename
  305. */
  306. function writeArraySchemaFile( $filename, $params = array() )
  307. {
  308. $params = array_merge( array( 'schema' => true,
  309. 'data' => false ),
  310. $params );
  311. $includeSchema = $params['schema'];
  312. $includeData = $params['data'];
  313. $fp = @fopen( $filename, 'w' );
  314. if ( $fp )
  315. {
  316. $schema = $this->schema( $params );
  317. fputs( $fp, '<?' . 'php' . "\n" );
  318. if ( $includeSchema )
  319. {
  320. fputs( $fp, "// This array contains the database schema\n" );
  321. if ( isset( $schema['_info'] ) )
  322. {
  323. $info = $schema['_info'];
  324. unset( $schema['_info'] );
  325. $schema['_info'] = $info;
  326. }
  327. fputs( $fp, '$schema = ' . var_export( $schema, true ) . ";\n" );
  328. }
  329. if ( $includeData )
  330. {
  331. $data = $this->data( $schema );
  332. fputs( $fp, "// This array contains the database data\n" );
  333. if ( isset( $data['_info'] ) )
  334. {
  335. $info = $data['_info'];
  336. unset( $data['_info'] );
  337. $data['_info'] = $info;
  338. }
  339. fputs( $fp, '$data = ' . var_export( $data, true ) . ";\n" );
  340. }
  341. fputs( $fp, "\n" . '?>' );
  342. fclose( $fp );
  343. return true;
  344. }
  345. else
  346. {
  347. return false;
  348. }
  349. }
  350. /*!
  351. Insert PHP schema to the current database instance by running one SQL at a time.
  352. \param $params Optional parameter which controls what to insert:
  353. - schema - Whether to insert the schema or not, default is \c true.
  354. - data - Whether to insert the data or not, default is \c false
  355. \return \c false if the schema could not be inserted, \c true if successful
  356. */
  357. function insertSchema( $params = array() )
  358. {
  359. $params = array_merge( array( 'schema' => true,
  360. 'data' => false ),
  361. $params );
  362. if ( !is_object( $this->DBInstance ) )
  363. {
  364. eZDebug::writeError( "No database instance is available, cannot insert", 'eZDBSchemaInterface::insertSchema' );
  365. return false;
  366. }
  367. $oldOutputSQL = $this->DBInstance->OutputSQL;
  368. $this->DBInstance->OutputSQL = false;
  369. $includeSchema = $params['schema'];
  370. $includeData = $params['data'];
  371. $params['format'] = 'local';
  372. $schema = $this->schema( $params );
  373. if ( $includeSchema )
  374. {
  375. foreach ( $schema as $tableName => $table )
  376. {
  377. // Skip the information array, this is not a table
  378. if ( $tableName == '_info' )
  379. continue;
  380. $sqlList = $this->generateTableSQLList( $tableName, $table, $params, false );
  381. foreach ( $sqlList as $sql )
  382. {
  383. if ( !$this->DBInstance->query( $sql ) )
  384. {
  385. eZDebug::writeError( "Failed inserting the SQL:\n$sql" );
  386. return false;
  387. }
  388. }
  389. }
  390. }
  391. if ( $includeData )
  392. {
  393. $data = $this->data( $schema, false, array( 'format' => 'local' ) );
  394. $this->DBInstance->begin();
  395. foreach ( $schema as $tableName => $table )
  396. {
  397. // Skip the information array, this is not a table
  398. if ( $tableName == '_info' )
  399. continue;
  400. if ( !isset( $data[$tableName] ) )
  401. {
  402. continue;
  403. }
  404. $sqlList = $this->generateTableInsertSQLList( $tableName, $table, $data[$tableName], $params, false );
  405. foreach ( $sqlList as $sql )
  406. {
  407. if ( !$this->DBInstance->query( $sql ) )
  408. {
  409. eZDebug::writeError( "Failed inserting the SQL:\n$sql" );
  410. $this->DBInstance->rollback();
  411. return false;
  412. }
  413. }
  414. }
  415. $this->DBInstance->commit();
  416. // Update sequences for databases that require this
  417. if ( method_exists( $this->DBInstance, 'correctSequenceValues' ) )
  418. {
  419. $status = $this->DBInstance->correctSequenceValues();
  420. if ( !$status )
  421. return false;
  422. }
  423. }
  424. $this->DBInstance->OutputSQL = $oldOutputSQL;
  425. return true;
  426. }
  427. /*!
  428. \private
  429. \param schema database schema
  430. \return schema for file output
  431. */
  432. function generateDataFile( $schema, $data, $params )
  433. {
  434. $params = array_merge( array( 'allow_multi_insert' => false,
  435. 'diff_friendly' => false ),
  436. $params );
  437. $sql = '';
  438. $i = 0;
  439. foreach ( $schema as $tableName => $tableDef )
  440. {
  441. // Skip the info structure, this is not a table
  442. if ( $tableName == '_info' )
  443. continue;
  444. if ( !isset( $data[$tableName] ) )
  445. continue;
  446. if ( $i > 0 )
  447. $sql .= "\n\n";
  448. $dataEntries = $data[$tableName];
  449. $sql .= $this->generateTableInsert( $tableName, $tableDef, $dataEntries, $params );
  450. ++$i;
  451. }
  452. return $sql;
  453. }
  454. /*!
  455. \private
  456. \param schema database schema
  457. \return schema for file output
  458. */
  459. function generateSchemaFile( $schema, $params = array() )
  460. {
  461. $sql = '';
  462. $i = 0;
  463. foreach ( $schema as $table => $tableDef )
  464. {
  465. // Skip the info structure, this is not a table
  466. if ( $table == '_info' )
  467. continue;
  468. if ( $i > 0 )
  469. $sql .= "\n\n";
  470. $sql .= $this->generateTableSchema( $table, $tableDef, $params );
  471. ++$i;
  472. }
  473. return $sql;
  474. }
  475. /*!
  476. * \private
  477. */
  478. function generateUpgradeFile( $differences, $params = array() )
  479. {
  480. $params = array_merge( array( 'schema' => true,
  481. 'data' => false,
  482. 'allow_multi_insert' => false,
  483. 'diff_friendly' => false ),
  484. $params );
  485. $sql = '';
  486. /* Loop over all 'table_changes' */
  487. if ( isset( $differences['table_changes'] ) )
  488. {
  489. foreach ( $differences['table_changes'] as $table => $table_diff )
  490. {
  491. if ( isset ( $table_diff['added_fields'] ) )
  492. {
  493. foreach ( $table_diff['added_fields'] as $field_name => $added_field )
  494. {
  495. $this->appendSQLComments( $added_field, $sql );
  496. $sql .= $this->generateAddFieldSql( $table, $field_name, $added_field, $params );
  497. }
  498. }
  499. if ( isset ( $table_diff['changed_fields'] ) )
  500. {
  501. foreach ( $table_diff['changed_fields'] as $field_name => $changed_field )
  502. {
  503. $changed_field_def = $changed_field['field-def'];
  504. $diffPrams = array_merge( $params, array( 'different-options' => $changed_field['different-options'] ) );
  505. $this->appendSQLComments( $changed_field_def, $sql );
  506. $sql .= $this->generateAlterFieldSql( $table, $field_name, $changed_field_def, $diffPrams );
  507. unset( $diffPrams );
  508. }
  509. }
  510. if ( isset ( $table_diff['removed_fields'] ) )
  511. {
  512. foreach ( $table_diff['removed_fields'] as $field_name => $removed_field)
  513. {
  514. $this->appendSQLComments( $removed_field, $sql );
  515. $sql .= $this->generateDropFieldSql( $table, $field_name, $params );
  516. }
  517. }
  518. if ( isset ( $table_diff['removed_indexes'] ) )
  519. {
  520. foreach ( $table_diff['removed_indexes'] as $index_name => $removed_index)
  521. {
  522. $this->appendSQLComments( $removed_index, $sql );
  523. $sql .= $this->generateDropIndexSql( $table, $index_name, $removed_index, $params );
  524. }
  525. }
  526. if ( isset ( $table_diff['added_indexes'] ) )
  527. {
  528. foreach ( $table_diff['added_indexes'] as $index_name => $added_index)
  529. {
  530. $this->appendSQLComments( $added_index, $sql );
  531. $sql .= $this->generateAddIndexSql( $table, $index_name, $added_index, $params );
  532. }
  533. }
  534. if ( isset ( $table_diff['changed_indexes'] ) )
  535. {
  536. foreach ( $table_diff['changed_indexes'] as $index_name => $changed_index )
  537. {
  538. //eZDebug::writeDebug( $changed_index, "changed index $index_name" );
  539. $this->appendSQLComments( $changed_index, $sql );
  540. $sql .= $this->generateDropIndexSql( $table, $index_name, $changed_index, $params );
  541. $sql .= $this->generateAddIndexSql( $table, $index_name, $changed_index, $params );
  542. //eZDebug::writeDebug( 'qqq' );
  543. }
  544. }
  545. }
  546. }
  547. if ( isset( $differences['new_tables'] ) )
  548. {
  549. foreach ( $differences['new_tables'] as $table => $table_def )
  550. {
  551. $this->appendSQLComments( $table_def, $sql );
  552. $sql .= $this->generateTableSchema( $table, $table_def, $params );
  553. }
  554. }
  555. if ( isset( $differences['removed_tables'] ) )
  556. {
  557. foreach ( $differences['removed_tables'] as $table => $table_def )
  558. {
  559. $this->appendSQLComments( $table_def, $sql );
  560. $sql .= $this->generateDropTable( $table, $params );
  561. }
  562. }
  563. return $sql;
  564. }
  565. /*!
  566. \pure
  567. \protected
  568. Generates the necessary SQLs to create the table and returns them all in an array.
  569. \param $tableName The table name
  570. \param $table The table structure, see class definition for more details
  571. \param $params An associative array with optional parameters which controls the output of SQLs
  572. \param $separateTypes If \c true then the returned array must be an associative array
  573. containing the SQL arrays split into multiple groups.
  574. The groups are:
  575. - sequences - List of sequences
  576. - tables - List of tables
  577. - trigger - List of triggers
  578. - indexes - List of indexes
  579. - constraints - List of constraints/primary keys
  580. - other - Other SQLs that doesn't fit into the above
  581. .
  582. Each group can be omitted and will be run in order.
  583. \note Each SQL in the array will be without a semi-colon
  584. \sa generateTableSchema()
  585. */
  586. function generateTableSQLList( $tableName, $table, $params, $separateTypes )
  587. {
  588. return false;
  589. }
  590. /*!
  591. \pure
  592. \protected
  593. Generates the necessary SQLs to create the table and returns them all in a string.
  594. \param $tableName The table name
  595. \param $table The table structure, see class definition for more details
  596. \note The SQLs will be ended with a semi-colon.
  597. \sa generateTableSQLList()
  598. */
  599. function generateTableSchema( $tableName, $table, $params )
  600. {
  601. return false;
  602. }
  603. /*!
  604. \protected
  605. \note Calls generateTableInsertSQLList and joins the SQLs to a string
  606. */
  607. function generateTableInsert( $tableName, $tableDef, $dataEntries, $params )
  608. {
  609. return join( "\n", $this->generateTableInsertSQLList( $tableName, $tableDef, $dataEntries, $params, true ) );
  610. }
  611. /*!
  612. \protected
  613. */
  614. function generateTableInsertSQLList( $tableName, $tableDef, $dataEntries, $params, $withClosure = true )
  615. {
  616. $diffFriendly = isset( $params['diff_friendly'] ) ? $params['diff_friendly'] : false;
  617. $multiInsert = ( isset( $params['allow_multi_insert'] ) and $params['allow_multi_insert'] ) ? $this->isMultiInsertSupported() : false;
  618. // Make sure we don't generate SQL when there are no rows
  619. if ( count( $dataEntries['rows'] ) == 0 )
  620. return '';
  621. $sqlList = array();
  622. $sql = '';
  623. $defText = '';
  624. $entryIndex = 0;
  625. foreach ( $dataEntries['fields'] as $fieldName )
  626. {
  627. if ( !isset( $tableDef['fields'][$fieldName] ) )
  628. continue;
  629. if ( $entryIndex == 0 )
  630. {
  631. if ( $diffFriendly )
  632. {
  633. $defText .= " ";
  634. }
  635. }
  636. else
  637. {
  638. if ( $diffFriendly )
  639. {
  640. $defText .= ",\n ";
  641. }
  642. else
  643. {
  644. $defText .= ", ";
  645. }
  646. }
  647. $defText .= $fieldName;
  648. ++$entryIndex;
  649. }
  650. if ( $multiInsert )
  651. {
  652. if ( $diffFriendly )
  653. {
  654. $sql .= "INSERT INTO $tableName (\n $defText\n)\nVALUES\n";
  655. }
  656. else
  657. {
  658. $sql .= "INSERT INTO $tableName ($defText) VALUES ";
  659. }
  660. }
  661. $insertIndex = 0;
  662. foreach ( $dataEntries['rows'] as $row )
  663. {
  664. if ( $multiInsert and $insertIndex > 0 )
  665. {
  666. if ( $diffFriendly )
  667. $sql .= "\n,\n";
  668. else
  669. $sql .= ", ";
  670. }
  671. $dataText = '';
  672. $entryIndex = 0;
  673. foreach ( $dataEntries['fields'] as $fieldName )
  674. {
  675. if ( !isset( $tableDef['fields'][$fieldName] ) )
  676. continue;
  677. if ( $entryIndex == 0 )
  678. {
  679. if ( $diffFriendly )
  680. {
  681. $dataText .= " ";
  682. }
  683. }
  684. else
  685. {
  686. if ( $diffFriendly )
  687. {
  688. $dataText .= ",\n ";
  689. }
  690. else
  691. {
  692. $dataText .= ",";
  693. }
  694. }
  695. $dataText .= $this->generateDataValueTextSQL( $tableDef['fields'][$fieldName], $row[$entryIndex] );
  696. ++$entryIndex;
  697. }
  698. if ( $multiInsert )
  699. {
  700. if ( $diffFriendly )
  701. {
  702. $sql .= "(\n $dataText\n)";
  703. }
  704. else
  705. {
  706. $sql .= "($dataText)";
  707. }
  708. ++$insertIndex;
  709. }
  710. else
  711. {
  712. if ( $diffFriendly )
  713. {
  714. $sqlList[] = "INSERT INTO $tableName (\n$defText\n) VALUES (\n$dataText\n)" . ( $withClosure ? ";" : "" );
  715. }
  716. else
  717. {
  718. $sqlList[] = "INSERT INTO $tableName ($defText) VALUES ($dataText)" . ( $withClosure ? ";" : "" );
  719. }
  720. }
  721. }
  722. if ( $multiInsert )
  723. {
  724. if ( $withClosure )
  725. $sql .= "\n;";
  726. $sqlList[] = $sql;
  727. }
  728. return $sqlList;
  729. }
  730. /*!
  731. \protected
  732. */
  733. function generateDataValueTextSQL( $fieldDef, $value )
  734. {
  735. if ( $fieldDef['type'] == 'auto_increment' or
  736. $fieldDef['type'] == 'int' or
  737. $fieldDef['type'] == 'float' )
  738. {
  739. if ( $value === null or
  740. $value === false )
  741. return "NULL";
  742. $value = (int)$value;
  743. $value = (string)$value;
  744. return $value;
  745. }
  746. else if ( is_string( $value ) )
  747. {
  748. return "'" . $this->escapeSQLString( $value ) . "'";
  749. }
  750. else
  751. {
  752. if ( $value === null or
  753. $value === false )
  754. return "NULL";
  755. return (string)$value;
  756. }
  757. }
  758. /*!
  759. \pure
  760. This escapes the string according to the current database type and returns it.
  761. \note The default just returns the value so it must be reimplemented.
  762. */
  763. function escapeSQLString( $value )
  764. {
  765. return $value;
  766. }
  767. /*!
  768. \pure
  769. \protected
  770. */
  771. function generateAlterFieldSql( $table_name, $field_name, $def, $params )
  772. {
  773. }
  774. /*!
  775. \pure
  776. \protected
  777. */
  778. function generateAddFieldSql( $table_name, $field_name, $def, $params )
  779. {
  780. }
  781. /*!
  782. \private
  783. */
  784. function generateDropFieldSql( $table_name, $field_name, $params )
  785. {
  786. $sql = "ALTER TABLE $table_name DROP COLUMN $field_name";
  787. return $sql . ";\n";
  788. }
  789. /*!
  790. Appends any comments found in \a $def to SQL text \a $sql as SQL comments.
  791. \return \c true if any comments were added.
  792. */
  793. function appendSQLComments( $def, &$sql )
  794. {
  795. if ( isset( $def['comments'] ) )
  796. {
  797. if ( count( $def['comments'] ) > 0 )
  798. $sql .= "\n";
  799. foreach ( $def['comments'] as $comment )
  800. {
  801. $commentLines = explode( "\n", $comment );
  802. foreach ( $commentLines as $commentLine )
  803. {
  804. $sql .= '-- ' . $commentLine . "\n";
  805. }
  806. }
  807. return true;
  808. }
  809. return false;
  810. }
  811. /*!
  812. \protected
  813. \return \c true if the schema system supports multi inserts.
  814. The default is to return \c false.
  815. */
  816. function isMultiInsertSupported()
  817. {
  818. return false;
  819. }
  820. /*!
  821. \pure
  822. \return Identifier for schema type as string.
  823. Examples: 'mysql', 'postgresql', 'oracle'
  824. \sa schemaName()
  825. */
  826. function schemaType()
  827. {
  828. }
  829. /*!
  830. \pure
  831. \return Displayable name for schema type as string.
  832. Examples: 'MySQL', 'PostgreSQL', 'Oracle'
  833. \sa schemaType()
  834. */
  835. function schemaName()
  836. {
  837. }
  838. /*!
  839. \private
  840. \static
  841. \return array of transformation rules on success, false otherwise
  842. */
  843. function loadSchemaTransformationRules( $schemaType )
  844. {
  845. $ini = eZINI::instance( 'dbschema.ini' );
  846. if ( !$ini )
  847. {
  848. eZDebug::writeError( "Error loading $schemaType schema transformation rules" );
  849. return false;
  850. }
  851. $transformationRules = array();
  852. if ( $ini->hasVariable( $schemaType, 'ColumnNameTranslation' ) )
  853. $transformationRules['column-name'] = $ini->variable( $schemaType, 'ColumnNameTranslation' );
  854. if ( $ini->hasVariable( $schemaType, 'ColumnTypeTranslation' ) )
  855. {
  856. $transformationRules['column-type'] = $ini->variable( $schemaType, 'ColumnTypeTranslation' );
  857. // substitute values like "type1;type2" with an appropriate arrays
  858. if ( is_array( $transformationRules['column-type'] ) )
  859. {
  860. foreach ( $transformationRules['column-type'] as $key => $val )
  861. {
  862. $types = explode( ';', $val );
  863. $transformationRules['column-type'][$key] = $types;
  864. }
  865. }
  866. }
  867. $indexTranslations = array();
  868. if ( $ini->hasVariable( $schemaType, 'IndexTranslation' ) )
  869. {
  870. $translations = $ini->variable( $schemaType, 'IndexTranslation' );
  871. foreach ( $translations as $combinedName => $translation )
  872. {
  873. list( $tableName, $indexName ) = explode( '.', $combinedName );
  874. $indexTranslations[$tableName][$indexName] = array();
  875. $fields = explode( ';', $translation );
  876. foreach ( $fields as $field )
  877. {
  878. $entries = explode( '.', $field );
  879. $fieldName = $entries[0];
  880. $fieldData = array();
  881. for ( $i = 1; $i < count( $entries ); ++$i )
  882. {
  883. list( $metaName, $metaValue ) = explode( '/', $entries[$i], 2 );
  884. if ( is_numeric( $metaValue ) )
  885. $metaValue = (int)$metaValue;
  886. $fieldData[$metaName] = $metaValue;
  887. }
  888. $indexTranslations[$tableName][$indexName][$fieldName] = $fieldData;
  889. }
  890. }
  891. }
  892. $transformationRules['index-field'] = $indexTranslations;
  893. $tableTranslations = array();
  894. if ( $ini->hasVariable( $schemaType, 'TableOptionTranslation' ) )
  895. {
  896. $translations = $ini->variable( $schemaType, 'TableOptionTranslation' );
  897. foreach ( $translations as $tableName => $optionTexts )
  898. {
  899. $tableTranslations[$tableName] = array();
  900. $options = explode( ';', $optionTexts );
  901. $optionData = array();
  902. foreach ( $options as $option )
  903. {
  904. list( $metaName, $metaValue ) = explode( '/', $option, 2 );
  905. if ( is_numeric( $metaValue ) )
  906. $metaValue = (int)$metaValue;
  907. $optionData[$metaName] = $metaValue;
  908. $tableTranslations[$tableName] = $optionData;
  909. }
  910. }
  911. }
  912. $transformationRules['table-option'] = $tableTranslations;
  913. if ( $ini->hasVariable( $schemaType, 'IndexNameTranslation' ) )
  914. {
  915. $tmpIdxNameTranslations = $ini->variable( $schemaType, 'IndexNameTranslation' );
  916. if ( is_array( $tmpIdxNameTranslations ) )
  917. {
  918. foreach ( $tmpIdxNameTranslations as $key => $val )
  919. {
  920. list( $tableName, $genericIdxName ) = explode( '.', $key );
  921. $localIdxName = $val;
  922. if ( !$tableName || !$genericIdxName || !$localIdxName )
  923. {
  924. eZDebug::writeWarning( "Malformed index name translation rule: $key => $val" );
  925. continue;
  926. }
  927. $transformationRules['index-name'][] = array( $tableName, $genericIdxName, $localIdxName );
  928. }
  929. }
  930. unset( $tmpIdxNameTranslations );
  931. }
  932. if ( $ini->hasVariable( $schemaType, 'ColumnOptionTranslations' ) )
  933. {
  934. $transformationRules['column-option'] = array();
  935. foreach( $ini->variable( $schemaType, 'ColumnOptionTranslations' ) as $key => $val )
  936. {
  937. list( $tableName, $colName ) = explode( '.', $key );
  938. $colOptOverride = $val;
  939. if ( !$tableName || !$colName || !$colOptOverride )
  940. {
  941. eZDebug::writeWarning( "Malformed column option translation rule: $key => $val" );
  942. continue;
  943. }
  944. $transformationRules['column-option'][] = array( $tableName, $colName, $colOptOverride );
  945. }
  946. }
  947. // prevent PHP warnings when cycling through the rules
  948. foreach ( array( 'column-name', 'column-type', 'column-option', 'index-name' ) as $rulesType )
  949. {
  950. if( !isset( $transformationRules[$rulesType] ) )
  951. $transformationRules[$rulesType] = array();
  952. }
  953. return $transformationRules;
  954. }
  955. /*!
  956. \protected
  957. \return true on success, false otherwise
  958. Transforms database schema to the given direction, applying the transformation rules.
  959. */
  960. function transformSchema( &$schema, /* bool */ $toLocal )
  961. {
  962. // Check if it is already in correct format
  963. if ( isset( $schema['_info']['format'] ) )
  964. {
  965. if ( $schema['_info']['format'] == ( $toLocal ? 'local' : 'generic' ) )
  966. return true;
  967. }
  968. // Set the new format it will get
  969. $schema['_info']['format'] = $toLocal ? 'local' : 'generic';
  970. // load the schema transformation rules
  971. $schemaType = $this->schemaType();
  972. $schemaTransformationRules = eZDBSchemaInterface::loadSchemaTransformationRules( $schemaType );
  973. if ( $schemaTransformationRules === false )
  974. return false;
  975. // transform column names
  976. foreach ( $schemaTransformationRules['column-name'] as $key => $val )
  977. {
  978. list( $tableName, $genericColName ) = explode( '.', $key );
  979. $localColName = $val;
  980. if ( $toLocal )
  981. {
  982. $searchColName = $genericColName;
  983. $replacementColName = $localColName;
  984. }
  985. else
  986. {
  987. $searchColName = $localColName;
  988. $replacementColName = $genericColName;
  989. }
  990. if ( !isset( $schema[$tableName] ) )
  991. continue;
  992. // transform column names in tables
  993. if ( isset( $schema[$tableName]['fields'][$searchColName] ) )
  994. {
  995. $schema[$tableName]['fields'][$replacementColName] = $schema[$tableName]['fields'][$searchColName];
  996. unset( $schema[$tableName]['fields'][$searchColName] );
  997. ksort( $schema[$tableName]['fields'] );
  998. eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
  999. "transformed table column name $tableName.$searchColName to $replacementColName" );
  1000. }
  1001. // transform column names in indexes
  1002. foreach ( $schema[$tableName]['indexes'] as $indexName => $indexSchema )
  1003. {
  1004. if ( ( $key = array_search( $searchColName, $indexSchema['fields'] ) ) !== false )
  1005. {
  1006. $schema[$tableName]['indexes'][$indexName]['fields'][$key] = $replacementColName;
  1007. eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
  1008. "transformed index columnn name $indexName.$searchColName to $replacementColName" );
  1009. }
  1010. }
  1011. }
  1012. // tranform column types
  1013. foreach ( $schemaTransformationRules['column-type'] as $key => $val )
  1014. {
  1015. list( $tableName, $colName ) = explode( '.', $key );
  1016. list( $genericType, $localType ) = $val;
  1017. if ( !isset( $schema[$tableName] ) )
  1018. continue;
  1019. preg_match( '/(\w+)\((\d+)\)/', $localType, $matches );
  1020. $localLength = ( count($matches) == 3 ) ? $matches[2] : null;
  1021. if ( count($matches) == 3 )
  1022. $localType = $matches[1];
  1023. preg_match( '/(\w+)\((\d+)\)/', $genericType, $matches );
  1024. $genericLength = ( count($matches) == 3 ) ? $matches[2] : null;
  1025. if ( count($matches) == 3 )
  1026. $genericType = $matches[1];
  1027. if ( !isset( $schema[$tableName]['fields'][$colName] ) )
  1028. continue;
  1029. $fieldSchema = $schema[$tableName]['fields'][$colName];
  1030. if ( $toLocal )
  1031. {
  1032. $searchType = $genericType;
  1033. $searchLength = $genericLength;
  1034. $replacementType = $localType;
  1035. $replacementLength = $localLength;
  1036. }
  1037. else // to generic
  1038. {
  1039. $searchType = $localType;
  1040. $searchLength = $localLength;
  1041. $replacementType = $genericType;
  1042. $replacementLength = $genericLength;
  1043. }
  1044. $fieldSchema['type'] = $replacementType;
  1045. if ( $replacementLength !== null )
  1046. {
  1047. $fieldSchema['length'] = $replacementLength;
  1048. }
  1049. else
  1050. {
  1051. unset( $fieldSchema['length'] );
  1052. }
  1053. eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
  1054. "transformed table column type $schemaType:$tableName.$colName from $searchType to $replacementType" );
  1055. $schema[$tableName]['fields'][$colName] = $fieldSchema;
  1056. }
  1057. // Find indexes that needs to be fixed
  1058. foreach ( $schemaTransformationRules['index-field'] as $tableName => $indexes )
  1059. {
  1060. foreach ( $indexes as $indexName => $fields )
  1061. {
  1062. if ( !isset( $schema[$tableName]['indexes'][$indexName]['fields'] ) )
  1063. continue;
  1064. $newFields = array();
  1065. foreach ( $schema[$tableName]['indexes'][$indexName]['fields'] as $indexField )
  1066. {
  1067. if ( !is_array( $indexField ) )
  1068. {
  1069. $indexField = array( 'name' => $indexField );
  1070. }
  1071. $fieldName = $indexField['name'];
  1072. if ( isset( $fields[$fieldName] ) )
  1073. {
  1074. if ( $toLocal )
  1075. {
  1076. $indexField = array_merge( $indexField,
  1077. $fields[$fieldName] );
  1078. }
  1079. else
  1080. {
  1081. foreach ( $fields[$fieldName] as $removeName => $removeValue )
  1082. {
  1083. unset( $indexField[$removeName] );
  1084. }
  1085. }
  1086. }
  1087. // Check if we have any entries other than 'name', if not we skip the array definition
  1088. if ( count( array_diff( array_keys( $indexField ), array( 'name' ) ) ) == 0 )
  1089. {
  1090. $indexField = $indexField['name'];
  1091. }
  1092. $newFields[] = $indexField;
  1093. }
  1094. $schema[$tableName]['indexes'][$indexName]['fields'] = $newFields;
  1095. }
  1096. }
  1097. // Find tables that needs to fix their options
  1098. foreach ( $schemaTransformationRules['table-option'] as $tableName => $options )
  1099. {
  1100. if ( !isset( $schema[$tableName] ) )
  1101. continue;
  1102. if ( !isset( $schema[$tableName]['options'] ) )
  1103. {
  1104. if ( $toLocal )
  1105. $schema[$tableName]['options'] = $options;
  1106. }
  1107. else
  1108. {
  1109. if ( $toLocal )
  1110. {
  1111. $schema[$tableName]['options'] = array_merge( $schema[$tableName]['options'], $options );
  1112. }
  1113. else
  1114. {
  1115. foreach ( $options as $optionName => $optionValue )
  1116. {
  1117. unset( $schema[$tableName]['options'][$optionName] );
  1118. }
  1119. }
  1120. }
  1121. }
  1122. // Transform index names
  1123. foreach ( $schemaTransformationRules['index-name'] as $idxTransRule )
  1124. {
  1125. list( $tableName, $genericIdxName, $localIdxName ) = $idxTransRule;
  1126. if ( $toLocal )
  1127. {
  1128. $searchIdxName = $genericIdxName;
  1129. $replacementIdxName = $localIdxName;
  1130. }
  1131. else
  1132. {
  1133. $searchIdxName = $localIdxName;
  1134. $replacementIdxName = $genericIdxName;
  1135. }
  1136. if ( !isset( $schema[$tableName] ) )
  1137. continue;
  1138. if ( isset( $schema[$tableName]['indexes'][$searchIdxName] ) )
  1139. {
  1140. eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
  1141. "replaced $tableName.$searchIdxName => $replacementIdxName" );
  1142. $schema[$tableName]['indexes'][$replacementIdxName] = $schema[$tableName]['indexes'][$searchIdxName];
  1143. unset( $schema[$tableName]['indexes'][$searchIdxName] );
  1144. ksort( $schema[$tableName]['indexes'] );
  1145. }
  1146. }
  1147. // Transform table column options
  1148. foreach ( $schemaTransformationRules['column-option'] as $colOptTransRule )
  1149. {
  1150. list( $tableName, $colName, $colOptOverride ) = $colOptTransRule;
  1151. if ( !isset( $schema[$tableName] ) || !isset( $schema[$tableName]['fields'][$colName] ) )
  1152. continue;
  1153. $fieldSchema = $schema[$tableName]['fields'][$colName];
  1154. switch ( $colOptOverride )
  1155. {
  1156. case 'null':
  1157. {
  1158. if ( $toLocal )
  1159. {
  1160. // remove "NOT NULL" requirement
  1161. unset( $fieldSchema['not_null'] );
  1162. eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
  1163. "transformed table column option: $schemaType:$tableName.$colName set to NULL" );
  1164. }
  1165. else
  1166. {
  1167. // add "NOT NULL" requirement
  1168. $fieldSchema['not_null'] = '1';
  1169. eZDebugSetting::writeDebug( 'lib-dbschema-transformation', '',
  1170. "transformed table column option: $schemaType:$tableName.$colName set to NOT NULL" );
  1171. }
  1172. // FIXME: ugly hack preserving keys order in the field schema array, just to be diff-friendly
  1173. {
  1174. $tmp = $fieldSchema['default'];
  1175. unset( $fieldSchema['default'] );
  1176. $fieldSchema['default'] = $tmp;
  1177. }
  1178. } break;
  1179. default:
  1180. {
  1181. eZDebug::writeWarning( "Column option override '$colOptOverride' is not supported" );
  1182. } break;
  1183. }
  1184. $schema[$tableName]['fields'][$colName] = $fieldSchema;
  1185. }
  1186. return true;
  1187. }
  1188. /*!
  1189. \protected
  1190. \return true on success, false otherwise
  1191. Transforms database data to the given direction, applying the transformation rules.
  1192. */
  1193. function transformData( &$data, /* bool */ $toLocal )
  1194. {
  1195. // Check if it is already in correct format
  1196. if ( isset( $data['_info']['format'] ) )
  1197. {
  1198. if ( $data['_info']['format'] == ( $toLocal ? 'local' : 'generic' ) )
  1199. return true;
  1200. }
  1201. // Set the new format it will get
  1202. $data['_info']['format'] = $toLocal ? 'local' : 'generic';
  1203. // load the schema transformation rules
  1204. $schemaType = $this->schemaType();
  1205. $schemaTransformationRules = eZDBSchemaInterface::loadSchemaTransformationRules( $schemaType );
  1206. if ( $schemaTransformationRules === false )
  1207. return false;
  1208. // transform column names
  1209. foreach ( $schemaTransformationRules['column-name'] as $key => $val )
  1210. {
  1211. list( $tableName, $genericColName ) = explode( '.', $key );
  1212. $localColName = $val;
  1213. if ( $toLocal )
  1214. {
  1215. $searchColName = $genericColName;
  1216. $replacementColName = $localColName;
  1217. }
  1218. else
  1219. {
  1220. $searchColName = $localColName;
  1221. $replacementColName = $genericColName;
  1222. }
  1223. if ( !isset( $data[$tableName] ) )
  1224. continue;
  1225. // transform column names in tables
  1226. $fieldsData = $data[$tableName]['fields'];
  1227. foreach ( $fieldsData as $key => $fieldName )
  1228. {
  1229. if ( $searchColName == $fieldName )
  1230. {
  1231. $data[$tableName]['fields'][$key] = $replacementColName;
  1232. eZDebugSetting::writeDebug( 'lib-dbschema-data-transformation', '',
  1233. "transformed table column name $tableName.$searchColName to $replacementColName" );
  1234. }
  1235. }
  1236. }
  1237. return true;
  1238. }
  1239. /// eZDB instance
  1240. public $DBInstance;
  1241. public $Schema;
  1242. public $Data;
  1243. }
  1244. ?>