PageRenderTime 37ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/libs/dibi/drivers/sqlite3.php

https://github.com/vohnicky/treeview
PHP | 531 lines | 254 code | 112 blank | 165 comment | 28 complexity | eae4bd85627fc4d3fbb67e1cdfadb19f MD5 | raw file
  1. <?php
  2. /**
  3. * dibi - tiny'n'smart database abstraction layer
  4. * ----------------------------------------------
  5. *
  6. * @copyright Copyright (c) 2005, 2010 David Grudl
  7. * @license http://dibiphp.com/license dibi license
  8. * @link http://dibiphp.com
  9. * @package dibi
  10. */
  11. /**
  12. * The dibi driver for SQLite3 database.
  13. *
  14. * Connection options:
  15. * - 'database' (or 'file') - the filename of the SQLite3 database
  16. * - 'lazy' - if TRUE, connection will be established only when required
  17. * - 'formatDate' - how to format date in SQL (@see date)
  18. * - 'formatDateTime' - how to format datetime in SQL (@see date)
  19. * - 'dbcharset' - database character encoding (will be converted to 'charset')
  20. * - 'charset' - character encoding to set (default is UTF-8)
  21. * - 'resource' - connection resource (optional)
  22. *
  23. * @copyright Copyright (c) 2005, 2010 David Grudl
  24. * @package dibi
  25. */
  26. class DibiSqlite3Driver extends DibiObject implements IDibiDriver
  27. {
  28. /** @var SQLite3 Connection resource */
  29. private $connection;
  30. /** @var SQLite3Result Resultset resource */
  31. private $resultSet;
  32. /** @var string Date and datetime format */
  33. private $fmtDate, $fmtDateTime;
  34. /** @var string character encoding */
  35. private $dbcharset, $charset;
  36. /**
  37. * @throws DibiException
  38. */
  39. public function __construct()
  40. {
  41. if (!extension_loaded('sqlite3')) {
  42. throw new DibiDriverException("PHP extension 'sqlite3' is not loaded.");
  43. }
  44. }
  45. /**
  46. * Connects to a database.
  47. * @return void
  48. * @throws DibiException
  49. */
  50. public function connect(array &$config)
  51. {
  52. DibiConnection::alias($config, 'database', 'file');
  53. $this->fmtDate = isset($config['formatDate']) ? $config['formatDate'] : 'U';
  54. $this->fmtDateTime = isset($config['formatDateTime']) ? $config['formatDateTime'] : 'U';
  55. if (isset($config['resource']) && $config['resource'] instanceof SQLite3) {
  56. $this->connection = $config['resource'];
  57. } else try {
  58. $this->connection = new SQLite3($config['database']);
  59. } catch (Exception $e) {
  60. throw new DibiDriverException($e->getMessage(), $e->getCode());
  61. }
  62. $this->dbcharset = empty($config['dbcharset']) ? 'UTF-8' : $config['dbcharset'];
  63. $this->charset = empty($config['charset']) ? 'UTF-8' : $config['charset'];
  64. if (strcasecmp($this->dbcharset, $this->charset) === 0) {
  65. $this->dbcharset = $this->charset = NULL;
  66. }
  67. // enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
  68. $version = SQLite3::version();
  69. if ($version['versionNumber'] >= '3006019') {
  70. $this->query("PRAGMA foreign_keys = ON");
  71. }
  72. }
  73. /**
  74. * Disconnects from a database.
  75. * @return void
  76. */
  77. public function disconnect()
  78. {
  79. $this->connection->close();
  80. }
  81. /**
  82. * Executes the SQL query.
  83. * @param string SQL statement.
  84. * @return IDibiDriver|NULL
  85. * @throws DibiDriverException
  86. */
  87. public function query($sql)
  88. {
  89. if ($this->dbcharset !== NULL) {
  90. $sql = iconv($this->charset, $this->dbcharset . '//IGNORE', $sql);
  91. }
  92. $this->resultSet = @$this->connection->query($sql); // intentionally @
  93. if ($this->connection->lastErrorCode()) {
  94. throw new DibiDriverException($this->connection->lastErrorMsg(), $this->connection->lastErrorCode(), $sql);
  95. }
  96. return $this->resultSet instanceof SQLite3Result ? clone $this : NULL;
  97. }
  98. /**
  99. * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
  100. * @return int|FALSE number of rows or FALSE on error
  101. */
  102. public function getAffectedRows()
  103. {
  104. return $this->connection->changes();
  105. }
  106. /**
  107. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
  108. * @return int|FALSE int on success or FALSE on failure
  109. */
  110. public function getInsertId($sequence)
  111. {
  112. return $this->connection->lastInsertRowID();
  113. }
  114. /**
  115. * Begins a transaction (if supported).
  116. * @param string optional savepoint name
  117. * @return void
  118. * @throws DibiDriverException
  119. */
  120. public function begin($savepoint = NULL)
  121. {
  122. $this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
  123. }
  124. /**
  125. * Commits statements in a transaction.
  126. * @param string optional savepoint name
  127. * @return void
  128. * @throws DibiDriverException
  129. */
  130. public function commit($savepoint = NULL)
  131. {
  132. $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
  133. }
  134. /**
  135. * Rollback changes in a transaction.
  136. * @param string optional savepoint name
  137. * @return void
  138. * @throws DibiDriverException
  139. */
  140. public function rollback($savepoint = NULL)
  141. {
  142. $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
  143. }
  144. /**
  145. * Returns the connection resource.
  146. * @return mixed
  147. */
  148. public function getResource()
  149. {
  150. return $this->connection;
  151. }
  152. /********************* SQL ****************d*g**/
  153. /**
  154. * Encodes data for use in a SQL statement.
  155. * @param mixed value
  156. * @param string type (dibi::TEXT, dibi::BOOL, ...)
  157. * @return string encoded value
  158. * @throws InvalidArgumentException
  159. */
  160. public function escape($value, $type)
  161. {
  162. switch ($type) {
  163. case dibi::TEXT:
  164. return "'" . $this->connection->escapeString($value) . "'";
  165. case dibi::BINARY:
  166. return "X'" . bin2hex((string) $value) . "'";
  167. case dibi::IDENTIFIER:
  168. return '[' . str_replace('.', '].[', strtr($value, '[]', ' ')) . ']';
  169. case dibi::BOOL:
  170. return $value ? 1 : 0;
  171. case dibi::DATE:
  172. return $value instanceof DateTime ? $value->format($this->fmtDate) : date($this->fmtDate, $value);
  173. case dibi::DATETIME:
  174. return $value instanceof DateTime ? $value->format($this->fmtDateTime) : date($this->fmtDateTime, $value);
  175. default:
  176. throw new InvalidArgumentException('Unsupported type.');
  177. }
  178. }
  179. /**
  180. * Decodes data from result set.
  181. * @param string value
  182. * @param string type (dibi::BINARY)
  183. * @return string decoded value
  184. * @throws InvalidArgumentException
  185. */
  186. public function unescape($value, $type)
  187. {
  188. if ($type === dibi::BINARY) {
  189. return $value;
  190. }
  191. throw new InvalidArgumentException('Unsupported type.');
  192. }
  193. /**
  194. * Injects LIMIT/OFFSET to the SQL query.
  195. * @param string &$sql The SQL query that will be modified.
  196. * @param int $limit
  197. * @param int $offset
  198. * @return void
  199. */
  200. public function applyLimit(&$sql, $limit, $offset)
  201. {
  202. if ($limit < 0 && $offset < 1) return;
  203. $sql .= ' LIMIT ' . $limit . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
  204. }
  205. /********************* result set ****************d*g**/
  206. /**
  207. * Returns the number of rows in a result set.
  208. * @return int
  209. * @throws NotSupportedException
  210. */
  211. public function getRowCount()
  212. {
  213. throw new NotSupportedException('Row count is not available for unbuffered queries.');
  214. }
  215. /**
  216. * Fetches the row at current position and moves the internal cursor to the next position.
  217. * @param bool TRUE for associative array, FALSE for numeric
  218. * @return array array on success, nonarray if no next record
  219. * @internal
  220. */
  221. public function fetch($assoc)
  222. {
  223. $row = $this->resultSet->fetchArray($assoc ? SQLITE3_ASSOC : SQLITE3_NUM);
  224. $charset = $this->charset === NULL ? NULL : $this->charset . '//TRANSLIT';
  225. if ($row && ($assoc || $charset)) {
  226. $tmp = array();
  227. foreach ($row as $k => $v) {
  228. if ($charset !== NULL && is_string($v)) {
  229. $v = iconv($this->dbcharset, $charset, $v);
  230. }
  231. $tmp[str_replace(array('[', ']'), '', $k)] = $v;
  232. }
  233. return $tmp;
  234. }
  235. return $row;
  236. }
  237. /**
  238. * Moves cursor position without fetching row.
  239. * @param int the 0-based cursor pos to seek to
  240. * @return boolean TRUE on success, FALSE if unable to seek to specified record
  241. * @throws NotSupportedException
  242. */
  243. public function seek($row)
  244. {
  245. throw new NotSupportedException('Cannot seek an unbuffered result set.');
  246. }
  247. /**
  248. * Frees the resources allocated for this result set.
  249. * @return void
  250. */
  251. public function free()
  252. {
  253. $this->resultSet = NULL;
  254. }
  255. /**
  256. * Returns metadata for all columns in a result set.
  257. * @return array
  258. */
  259. public function getColumnsMeta()
  260. {
  261. $count = $this->resultSet->numColumns();
  262. $res = array();
  263. static $types = array(SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null');
  264. for ($i = 0; $i < $count; $i++) {
  265. $res[] = array(
  266. 'name' => $this->resultSet->columnName($i),
  267. 'table' => NULL,
  268. 'fullname' => $this->resultSet->columnName($i),
  269. 'nativetype' => $types[$this->resultSet->columnType($i)],
  270. );
  271. }
  272. return $res;
  273. }
  274. /**
  275. * Returns the result set resource.
  276. * @return mixed
  277. */
  278. public function getResultResource()
  279. {
  280. return $this->resultSet;
  281. }
  282. /********************* reflection ****************d*g**/
  283. /**
  284. * Returns list of tables.
  285. * @return array
  286. */
  287. public function getTables()
  288. {
  289. $this->query("
  290. SELECT name, type = 'view' as view FROM sqlite_master WHERE type IN ('table', 'view')
  291. UNION ALL
  292. SELECT name, type = 'view' as view FROM sqlite_temp_master WHERE type IN ('table', 'view')
  293. ORDER BY name
  294. ");
  295. $res = array();
  296. while ($row = $this->fetch(TRUE)) {
  297. $res[] = $row;
  298. }
  299. $this->free();
  300. return $res;
  301. }
  302. /**
  303. * Returns metadata for all columns in a table.
  304. * @param string
  305. * @return array
  306. */
  307. public function getColumns($table)
  308. {
  309. $this->query("
  310. SELECT sql FROM sqlite_master WHERE type = 'table' AND name = '$table'
  311. UNION ALL
  312. SELECT sql FROM sqlite_temp_master WHERE type = 'table' AND name = '$table'"
  313. );
  314. $meta = $this->fetch(TRUE);
  315. $this->free();
  316. $this->query("PRAGMA table_info([$table])");
  317. $res = array();
  318. while ($row = $this->fetch(TRUE)) {
  319. $column = $row['name'];
  320. $pattern = "/(\"$column\"|\[$column\]|$column)\s+[^,]+\s+PRIMARY\s+KEY\s+AUTOINCREMENT/Ui";
  321. $type = explode('(', $row['type']);
  322. $res[] = array(
  323. 'name' => $column,
  324. 'table' => $table,
  325. 'fullname' => "$table.$column",
  326. 'nativetype' => strtoupper($type[0]),
  327. 'size' => isset($type[1]) ? (int) $type[1] : NULL,
  328. 'nullable' => $row['notnull'] == '0',
  329. 'default' => $row['dflt_value'],
  330. 'autoincrement' => (bool) preg_match($pattern, $meta['sql']),
  331. 'vendor' => $row,
  332. );
  333. }
  334. $this->free();
  335. return $res;
  336. }
  337. /**
  338. * Returns metadata for all indexes in a table.
  339. * @param string
  340. * @return array
  341. */
  342. public function getIndexes($table)
  343. {
  344. $this->query("PRAGMA index_list([$table])");
  345. $res = array();
  346. while ($row = $this->fetch(TRUE)) {
  347. $res[$row['name']]['name'] = $row['name'];
  348. $res[$row['name']]['unique'] = (bool) $row['unique'];
  349. }
  350. $this->free();
  351. foreach ($res as $index => $values) {
  352. $this->query("PRAGMA index_info([$index])");
  353. while ($row = $this->fetch(TRUE)) {
  354. $res[$index]['columns'][$row['seqno']] = $row['name'];
  355. }
  356. }
  357. $this->free();
  358. $columns = $this->getColumns($table);
  359. foreach ($res as $index => $values) {
  360. $column = $res[$index]['columns'][0];
  361. $primary = FALSE;
  362. foreach ($columns as $info) {
  363. if ($column == $info['name']) {
  364. $primary = $info['vendor']['pk'];
  365. break;
  366. }
  367. }
  368. $res[$index]['primary'] = (bool) $primary;
  369. }
  370. return array_values($res);
  371. }
  372. /**
  373. * Returns metadata for all foreign keys in a table.
  374. * @param string
  375. * @return array
  376. */
  377. public function getForeignKeys($table)
  378. {
  379. $this->query("PRAGMA foreign_key_list([$table])");
  380. $res = array();
  381. while ($row = $this->fetch(TRUE)) {
  382. $res[$row['id']]['name'] = $row['id']; // foreign key name
  383. $res[$row['id']]['local'][$row['seq']] = $row['from']; // local columns
  384. $res[$row['id']]['table'] = $row['table']; // referenced table
  385. $res[$row['id']]['foreign'][$row['seq']] = $row['to']; // referenced columns
  386. $res[$row['id']]['onDelete'] = $row['on_delete'];
  387. $res[$row['id']]['onUpdate'] = $row['on_update'];
  388. if ($res[$row['id']]['foreign'][0] == NULL) {
  389. $res[$row['id']]['foreign'] = NULL;
  390. }
  391. }
  392. $this->free();
  393. return array_values($res);
  394. }
  395. /********************* user defined functions ****************d*g**/
  396. /**
  397. * Registers an user defined function for use in SQL statements.
  398. * @param string function name
  399. * @param mixed callback
  400. * @param int num of arguments
  401. * @return void
  402. */
  403. public function registerFunction($name, $callback, $numArgs = -1)
  404. {
  405. $this->connection->createFunction($name, $callback, $numArgs);
  406. }
  407. /**
  408. * Registers an aggregating user defined function for use in SQL statements.
  409. * @param string function name
  410. * @param mixed callback called for each row of the result set
  411. * @param mixed callback called to aggregate the "stepped" data from each row
  412. * @param int num of arguments
  413. * @return void
  414. */
  415. public function registerAggregateFunction($name, $rowCallback, $agrCallback, $numArgs = -1)
  416. {
  417. $this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
  418. }
  419. }