xpdomanager.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <?php
  2. /*
  3. * Copyright 2010-2015 by MODX, LLC.
  4. *
  5. * This file is part of xPDO.
  6. *
  7. * xPDO is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  18. * Suite 330, Boston, MA 02111-1307 USA
  19. */
  20. /**
  21. * The SQLite implementation of the xPDOManager class.
  22. *
  23. * @package xpdo
  24. * @subpackage om.sqlite
  25. */
  26. /**
  27. * Include the parent {@link xPDOManager} class.
  28. */
  29. require_once (dirname(__DIR__) . '/xpdomanager.class.php');
  30. /**
  31. * Provides SQLite data source management for an xPDO instance.
  32. *
  33. * These are utility functions that only need to be loaded under special
  34. * circumstances, such as creating tables, adding indexes, altering table
  35. * structures, etc. xPDOManager class implementations are specific to a
  36. * database driver and this instance is implemented for SQLite.
  37. *
  38. * @package xpdo
  39. * @subpackage om.sqlite
  40. */
  41. class xPDOManager_sqlite extends xPDOManager {
  42. public function createSourceContainer($dsnArray = null, $username= null, $password= null, $containerOptions= array ()) {
  43. $created = false;
  44. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  45. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, 'SQLite does not support source container creation');
  46. if ($dsnArray === null) $dsnArray = xPDO::parseDSN($this->xpdo->getOption('dsn'));
  47. if (is_array($dsnArray)) {
  48. try {
  49. $dbfile = $dsnArray['dbname'];
  50. $created = !file_exists($dbfile);
  51. } catch (Exception $e) {
  52. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error creating source container: " . $e->getMessage());
  53. }
  54. }
  55. }
  56. return $created;
  57. }
  58. public function removeSourceContainer($dsnArray = null, $username= null, $password= null) {
  59. $removed= false;
  60. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  61. if ($dsnArray === null) $dsnArray = xPDO::parseDSN($this->xpdo->getOption('dsn'));
  62. if (is_array($dsnArray)) {
  63. try {
  64. $dbfile = $dsnArray['dbname'];
  65. if (file_exists($dbfile)) {
  66. $removed = unlink($dbfile);
  67. if (!$removed) {
  68. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not remove source container");
  69. }
  70. } else {
  71. $removed= true;
  72. }
  73. } catch (Exception $e) {
  74. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not remove source container: " . $e->getMessage());
  75. }
  76. }
  77. }
  78. return $removed;
  79. }
  80. public function removeObjectContainer($className) {
  81. $removed= false;
  82. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  83. $instance= $this->xpdo->newObject($className);
  84. if ($instance) {
  85. $sql= 'DROP TABLE ' . $this->xpdo->getTableName($className);
  86. $removed= $this->xpdo->exec($sql);
  87. if ($removed === false && $this->xpdo->errorCode() !== '' && $this->xpdo->errorCode() !== PDO::ERR_NONE) {
  88. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not drop table ' . $className . "\nSQL: {$sql}\nERROR: " . print_r($this->xpdo->pdo->errorInfo(), true));
  89. } else {
  90. $removed= true;
  91. $this->xpdo->log(xPDO::LOG_LEVEL_INFO, 'Dropped table' . $className . "\nSQL: {$sql}\n");
  92. }
  93. }
  94. }
  95. return $removed;
  96. }
  97. public function createObjectContainer($className) {
  98. $created= false;
  99. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  100. $instance= $this->xpdo->newObject($className);
  101. if ($instance) {
  102. $tableName= $this->xpdo->getTableName($className);
  103. $existsStmt = $this->xpdo->query("SELECT COUNT(*) FROM {$tableName}");
  104. if ($existsStmt && $existsStmt->fetchAll()) {
  105. return true;
  106. }
  107. $tableMeta= $this->xpdo->getTableMeta($className);
  108. $sql= 'CREATE TABLE ' . $tableName . ' (';
  109. $fieldMeta = $this->xpdo->getFieldMeta($className, true);
  110. $nativeGen = false;
  111. $columns = array();
  112. foreach ($fieldMeta as $key => $meta) {
  113. $columns[] = $this->getColumnDef($className, $key, $meta);
  114. if (array_key_exists('generated', $meta) && $meta['generated'] == 'native') $nativeGen = true;
  115. }
  116. $sql .= implode(', ', $columns);
  117. $indexes = $this->xpdo->getIndexMeta($className);
  118. $createIndices = array();
  119. $tableConstraints = array();
  120. if (!empty ($indexes)) {
  121. foreach ($indexes as $indexkey => $indexdef) {
  122. $indexkey = $this->xpdo->literal($instance->_table) . '_' . $indexkey;
  123. $indexType = ($indexdef['primary'] ? 'PRIMARY KEY' : ($indexdef['unique'] ? 'UNIQUE' : 'INDEX'));
  124. $index = $indexdef['columns'];
  125. if (is_array($index)) {
  126. $indexset= array ();
  127. foreach ($index as $indexmember => $indexmemberdetails) {
  128. $indexMemberDetails = $this->xpdo->escape($indexmember);
  129. $indexset[]= $indexMemberDetails;
  130. }
  131. $indexset= implode(',', $indexset);
  132. if (!empty($indexset)) {
  133. switch ($indexType) {
  134. case 'UNIQUE':
  135. $createIndices[$indexkey] = "CREATE UNIQUE INDEX {$this->xpdo->escape($indexkey)} ON {$tableName} ({$indexset})";
  136. break;
  137. case 'INDEX':
  138. $createIndices[$indexkey] = "CREATE INDEX {$this->xpdo->escape($indexkey)} ON {$tableName} ({$indexset})";
  139. break;
  140. case 'PRIMARY KEY':
  141. if ($nativeGen) break;
  142. $tableConstraints[]= "{$indexType} ({$indexset})";
  143. break;
  144. default:
  145. $tableConstraints[]= "CONSTRAINT {$this->xpdo->escape($indexkey)} {$indexType} ({$indexset})";
  146. break;
  147. }
  148. }
  149. }
  150. }
  151. }
  152. if (!empty($tableConstraints)) {
  153. $sql .= ', ' . implode(', ', $tableConstraints);
  154. }
  155. $sql .= ")";
  156. $created= $this->xpdo->exec($sql);
  157. if ($created === false && $this->xpdo->errorCode() !== '' && $this->xpdo->errorCode() !== PDO::ERR_NONE) {
  158. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not create table ' . $tableName . "\nSQL: {$sql}\nERROR: " . print_r($this->xpdo->errorInfo(), true));
  159. } else {
  160. $created= true;
  161. $this->xpdo->log(xPDO::LOG_LEVEL_INFO, 'Created table ' . $tableName . "\nSQL: {$sql}\n");
  162. }
  163. if ($created === true && !empty($createIndices)) {
  164. foreach ($createIndices as $createIndexKey => $createIndex) {
  165. $indexCreated = $this->xpdo->exec($createIndex);
  166. if ($indexCreated === false && $this->xpdo->errorCode() !== '' && $this->xpdo->errorCode() !== PDO::ERR_NONE) {
  167. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not create index {$createIndexKey}: {$createIndex} " . print_r($this->xpdo->errorInfo(), true));
  168. } else {
  169. $this->xpdo->log(xPDO::LOG_LEVEL_INFO, "Created index {$createIndexKey} on {$tableName}: {$createIndex}");
  170. }
  171. }
  172. }
  173. }
  174. }
  175. return $created;
  176. }
  177. public function alterObjectContainer($className, array $options = array()) {
  178. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  179. // TODO: Implement alterObjectContainer() method.
  180. }
  181. }
  182. public function addConstraint($class, $name, array $options = array()) {
  183. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  184. // TODO: Implement addConstraint() method.
  185. }
  186. }
  187. public function addField($class, $name, array $options = array()) {
  188. $result = false;
  189. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  190. $className = $this->xpdo->loadClass($class);
  191. if ($className) {
  192. $meta = $this->xpdo->getFieldMeta($className, true);
  193. if (is_array($meta) && array_key_exists($name, $meta)) {
  194. $colDef = $this->getColumnDef($className, $name, $meta[$name]);
  195. if (!empty($colDef)) {
  196. $sql = "ALTER TABLE {$this->xpdo->getTableName($className)} ADD COLUMN {$colDef}";
  197. if ($this->xpdo->exec($sql) !== false) {
  198. $result = true;
  199. } else {
  200. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error adding field {$class}->{$name}: " . print_r($this->xpdo->errorInfo(), true), '', __METHOD__, __FILE__, __LINE__);
  201. }
  202. } else {
  203. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error adding field {$class}->{$name}: Could not get column definition");
  204. }
  205. } else {
  206. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error adding field {$class}->{$name}: No metadata defined");
  207. }
  208. }
  209. }
  210. return $result;
  211. }
  212. public function addIndex($class, $name, array $options = array()) {
  213. $result = false;
  214. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  215. $className = $this->xpdo->loadClass($class);
  216. if ($className) {
  217. $meta = $this->xpdo->getIndexMeta($className);
  218. if (is_array($meta) && array_key_exists($name, $meta)) {
  219. $indexType = ($meta[$name]['unique'] ? 'UNIQUE INDEX' : 'INDEX');
  220. $idxDef = $this->getIndexDef($className, $name, $meta[$name]);
  221. if (!empty($idxDef)) {
  222. $sql = "CREATE {$indexType} ON {$this->xpdo->getTableName($className)} ({$idxDef})";
  223. if ($this->xpdo->exec($sql) !== false) {
  224. $result = true;
  225. } else {
  226. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error adding index {$name} to {$class}: " . print_r($this->xpdo->errorInfo(), true), '', __METHOD__, __FILE__, __LINE__);
  227. }
  228. } else {
  229. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error adding index {$name} to {$class}: Could not get index definition");
  230. }
  231. } else {
  232. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error adding index {$name} to {$class}: No metadata defined");
  233. }
  234. }
  235. }
  236. return $result;
  237. }
  238. public function alterField($class, $name, array $options = array()) {
  239. $result = false;
  240. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  241. // TODO: Implement alterField() method somehow, no support in sqlite for altering existing columns
  242. }
  243. return $result;
  244. }
  245. public function removeConstraint($class, $name, array $options = array()) {
  246. $result = false;
  247. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  248. // TODO: Implement removeConstraint() method.
  249. }
  250. return $result;
  251. }
  252. public function removeField($class, $name, array $options = array()) {
  253. $result = false;
  254. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  255. // TODO: Implement removeField() method somehow, no support in sqlite for dropping existing columns
  256. }
  257. return $result;
  258. }
  259. public function removeIndex($class, $name, array $options = array()) {
  260. $result = false;
  261. if ($this->xpdo->getConnection(array(xPDO::OPT_CONN_MUTABLE => true))) {
  262. $className = $this->xpdo->loadClass($class);
  263. if ($className) {
  264. $sql = "DROP INDEX {$this->xpdo->escape($name)}";
  265. if ($this->xpdo->exec($sql) !== false) {
  266. $result = true;
  267. } else {
  268. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error removing index {$name} from {$class}: " . print_r($this->xpdo->errorInfo(), true), '', __METHOD__, __FILE__, __LINE__);
  269. }
  270. }
  271. }
  272. return $result;
  273. }
  274. protected function getColumnDef($class, $name, $meta, array $options = array()) {
  275. $pk = $this->xpdo->getPK($class);
  276. $pktype = $this->xpdo->getPKType($class);
  277. $dbtype= strtoupper($meta['dbtype']);
  278. $precision= isset ($meta['precision']) && !preg_match('/ENUM/i', $dbtype) ? '(' . $meta['precision'] . ')' : '';
  279. if (preg_match('/ENUM/i', $dbtype)) {
  280. $dbtype= 'CHAR';
  281. }
  282. $notNull= !isset ($meta['null'])
  283. ? false
  284. : ($meta['null'] === 'false' || empty($meta['null']));
  285. $null= $notNull ? ' NOT NULL' : ' NULL';
  286. $extra = '';
  287. if (isset ($meta['index']) && $meta['index'] == 'pk' && !is_array($pk) && $pktype == 'integer' && isset ($meta['generated']) && $meta['generated'] == 'native') {
  288. $extra= ' PRIMARY KEY AUTOINCREMENT';
  289. $options['nativeGen'] = true;
  290. $null= '';
  291. }
  292. if (empty ($extra) && isset ($meta['extra'])) {
  293. $extra= ' ' . $meta['extra'];
  294. }
  295. $default= '';
  296. if (array_key_exists('default', $meta)) {
  297. $defaultVal= $meta['default'];
  298. if ($defaultVal === null || strtoupper($defaultVal) === 'NULL' || in_array($this->xpdo->driver->getPhpType($dbtype), array('integer', 'float')) || (in_array($meta['phptype'], array('datetime', 'date', 'timestamp', 'time')) && in_array($defaultVal, array_merge($this->xpdo->driver->_currentTimestamps, $this->xpdo->driver->_currentDates, $this->xpdo->driver->_currentTimes)))) {
  299. $default= ' DEFAULT ' . $defaultVal;
  300. } else {
  301. $default= ' DEFAULT \'' . $defaultVal . '\'';
  302. }
  303. }
  304. $attributes= (isset ($meta['attributes'])) ? ' ' . $meta['attributes'] : '';
  305. if (strpos(strtolower($attributes), 'unsigned') !== false) {
  306. $result = $this->xpdo->escape($name) . ' ' . $dbtype . $precision . $attributes . $null . $default . $extra;
  307. } else {
  308. $result = $this->xpdo->escape($name) . ' ' . $dbtype . $precision . $null . $default . $attributes . $extra;
  309. }
  310. return $result;
  311. }
  312. protected function getIndexDef($class, $name, $meta, array $options = array()) {
  313. $result = '';
  314. $index = isset($meta['columns']) ? $meta['columns'] : null;
  315. if (is_array($index)) {
  316. $indexset= array ();
  317. foreach ($index as $indexmember => $indexmemberdetails) {
  318. $indexMemberDetails = $this->xpdo->escape($indexmember);
  319. $indexset[]= $indexMemberDetails;
  320. }
  321. $result= implode(',', $indexset);
  322. }
  323. return $result;
  324. }
  325. }