PageRenderTime 68ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/ezdbschema/classes/ezdbschemainterface.php

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