PageRenderTime 62ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/system/libraries/pdosqlite3.lib.php

https://gitlab.com/paulmfoster/grotworx
PHP | 415 lines | 346 code | 13 blank | 56 comment | 15 complexity | 91e9cb548dcf10d1c3a4008a1ee5d9c5 MD5 | raw file
  1. <?php
  2. /**
  3. * pdosqlite3 class
  4. *
  5. * This class applies the PDO class to the SQLite3 database
  6. * type. PDO has a design weakness under normal use, where
  7. * its internal design uses a PDO object and a PDOStatement
  8. * object. If you used this class as is, your database
  9. * operations would be needlessly complex because of this.
  10. * So this class acts as a cover over the PDO classes, to make
  11. * the programmer's life easier.
  12. *
  13. */
  14. class pdosqlite3
  15. {
  16. var $handle, $dd = array();
  17. function __construct($dsn)
  18. {
  19. $parms = explode(':', $dsn);
  20. if (count($parms) === 1) {
  21. die('Configuration error: No database file specified.');
  22. }
  23. $filename = $parms[1];
  24. $this->db_status = file_exists($filename) ? TRUE : FALSE;
  25. try {
  26. $this->handle = new PDO($dsn);
  27. }
  28. catch (PDOException $e) {
  29. die($e->getMessage());
  30. // $this->db_status = FALSE;
  31. }
  32. }
  33. /**
  34. * Report status of database.
  35. *
  36. * Returns FALSE if database is missing or there was some problem in
  37. * opening it.
  38. *
  39. * @return bool
  40. */
  41. function status()
  42. {
  43. return $this->db_status;
  44. }
  45. function fatal($function, $sql)
  46. {
  47. error_log("DATE: " . date('c') . "\n", 3, 'error.log');
  48. error_log("ERROR: Failure in $function:\n", 3, 'error.log');
  49. error_log("SQL: $sql\n", 3, 'error.log');
  50. $pdo_message = var_export($this->handle->errorInfo()[2], TRUE);
  51. error_log("PDO error: $pdo_message\n", 3, 'error.log');
  52. $msg = "\nTRACE:\n--------------------------\n";
  53. $backtrace = debug_backtrace();
  54. foreach ($backtrace as $key => $trace) {
  55. if ($key != 0) {
  56. $msg .= "#$key ";
  57. if (array_key_exists('file', $trace)) {
  58. $msg .= $trace['file'];
  59. }
  60. if (array_key_exists('line', $trace)) {
  61. $msg .= ":$trace[line]";
  62. }
  63. if (array_key_exists('class', $trace)) {
  64. $msg .= ":$trace[class]";
  65. }
  66. if (array_key_exists('type', $trace)) {
  67. $msg .= " $trace[type] " ; // ->, ::, or nothing
  68. }
  69. if (array_key_exists('function', $trace)) {
  70. $msg .= " $trace[function](";
  71. if (array_key_exists('args', $trace)) {
  72. $first = true;
  73. foreach ($trace['args'] as $arg) {
  74. if (!$first) {
  75. $msg .= ', ';
  76. }
  77. if (is_object($arg))
  78. $msg .= get_class($arg) . ' OBJECT ';
  79. else {
  80. if (is_string($arg)) {
  81. $msg .= $arg;
  82. }
  83. else {
  84. $msg .= 'TYPE: ' . gettype($arg);
  85. }
  86. }
  87. if ($first)
  88. $first = false;
  89. }
  90. }
  91. $msg .= ')';
  92. }
  93. $msg .= "\n";
  94. }
  95. }
  96. $msg .= "\n";
  97. error_log($msg, 3, 'error.log');
  98. error_log("-\n", 3, 'error.log');
  99. die('FATAL database error, aborting. See error log.');
  100. }
  101. /**
  102. * Gather a data dictionary from the SQLite3 database.
  103. *
  104. * Because SQLite does not implement INFORMATION_SCHEMA, we have to
  105. * find some other way to obtain metadata about tables and columns.
  106. * This code is based on a StackOverflow answer.
  107. *
  108. * NOTE: I learned the hard way-- the pragma_table_info() and
  109. * similar functions were added in SQLite version 3.16.0. If PHP's
  110. * version of SQLite is less than this, the query will fail with
  111. * inscrutable errors. Run phpinfo() to determine your version of
  112. * PHP and the SQLite3 module.
  113. *
  114. * result table looks like this:
  115. *
  116. * table_name|column_name|column_type|is_nullable|column_default|pkey
  117. *
  118. * This routine is only called by self::prepare().
  119. *
  120. * @return array Array of column descriptions
  121. *
  122. */
  123. function datadict()
  124. {
  125. $sql = "SELECT m.name AS table_name, p.name AS column_name, p.type AS column_type, not `notnull` AS is_nullable, p.dflt_value AS column_default, p.pk AS pkey FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS p WHERE m.type = 'table' ORDER BY m.name, p.cid";
  126. $this->dd = $this->query($sql)->fetch_all();
  127. }
  128. /**
  129. * Prettifies string values for use in queries.
  130. *
  131. * Adds single quotes before and after string values,
  132. * turns internal single quotes into double single quotes
  133. *
  134. * NOTE: This is a static function
  135. *
  136. * @param string $value The value to be quoted
  137. *
  138. * @return string The quoted value
  139. *
  140. */
  141. static public function quote($value)
  142. {
  143. $quoted = str_replace("'", "''", $value);
  144. return "'" . $quoted . "'";
  145. }
  146. /**
  147. * Discard fields in POST array not germane to a given table.
  148. *
  149. * Currently, this function discards all fields not part of the
  150. * table in question, and leaves only the fields which are actually
  151. * part of the table.
  152. *
  153. * Typically, for functions like insert(), the function is fed the
  154. * whole $_POST array. This function strips items from the array
  155. * which might be buttons or somesuch, and leaves only appropriate
  156. * fields.
  157. *
  158. * @param string $table which table in the $dd?
  159. * @param array $rec Associative array ['field_name' => field_value, * ...]
  160. *
  161. * @return array An array with appropriately "repaired" values
  162. *
  163. */
  164. function prepare($table, $rec)
  165. {
  166. if (empty($this->dd)) {
  167. $this->datadict();
  168. }
  169. $prepped = array();
  170. foreach ($this->dd as $column) {
  171. if ($column['table_name'] != $table) {
  172. continue;
  173. }
  174. $column_name = $column['column_name'];
  175. if (isset($rec[$column_name])) {
  176. $prepped[$column_name] = $rec[$column_name];
  177. }
  178. }
  179. return $prepped;
  180. }
  181. function begin_transaction()
  182. {
  183. $this->handle->beginTransaction();
  184. }
  185. function begin()
  186. {
  187. $this->handle->beginTransaction();
  188. }
  189. /**
  190. * Execute any sql statement
  191. *
  192. * NOTE: You may chain other routines from here.
  193. *
  194. * @param string $sql The SQL statement
  195. *
  196. * @return reference this object
  197. */
  198. function query($sql)
  199. {
  200. $this->result = $this->handle->query($sql);
  201. // this only happens on things like a query to a non-existent
  202. // table, or a query of a non-existent field
  203. if ($this->result === FALSE) {
  204. $this->fatal('PDO::query()', $sql);
  205. }
  206. return $this;
  207. }
  208. /**
  209. * Fetch a single record.
  210. *
  211. * @return array A single row of query results or FALSE if no rows
  212. *
  213. */
  214. function fetch()
  215. {
  216. // PDOStatement::fetch() returns FALSE on no results
  217. return $this->result->fetch(PDO::FETCH_ASSOC);
  218. }
  219. /**
  220. * Fetches an indexed/associative array of all results from a query
  221. *
  222. * @return array Indexed/associative array of results
  223. *
  224. * @return array Indexed array of records returned from prior query
  225. *
  226. */
  227. function fetch_all()
  228. {
  229. // PDOStatement::fetchAll returns an empty array on no results
  230. $recs = $this->result->fetchAll(PDO::FETCH_ASSOC);
  231. // change return to FALSE for consistency with fetch()
  232. return empty($recs) ? FALSE : $recs;
  233. }
  234. /**
  235. * Fetches the index number for the last record stored
  236. *
  237. * NOTE: This function does not deal with concurrency issues
  238. *
  239. * @param string $table Name of table to query
  240. *
  241. * @return integer Last ID
  242. *
  243. */
  244. function lastid($table)
  245. {
  246. $sql = "SELECT seq FROM sqlite_sequence WHERE name = '$table'";
  247. $seq_rec = $this->query($sql)->fetch();
  248. return ($seq_rec['seq']);
  249. }
  250. /**
  251. * Insert a record into a table.
  252. *
  253. * Special case of query(), where the input is, instead of a SQL
  254. * statement, an associative array of fieldnames and values.
  255. *
  256. * For string and some other data types, values must be quoted, or
  257. * SQLite3 will generate an exception.
  258. *
  259. * @param string $table The table to be inserted into
  260. * @param array $records The indexed/associated table of
  261. * fieldnames/values.
  262. *
  263. */
  264. function insert($table, $record)
  265. {
  266. if (empty($table)) {
  267. $this->fatal('Call to database::insert() with no table name', '--');
  268. }
  269. if (empty($record)) {
  270. $this->fatal('Call to database::insert() with no field data', '--');
  271. }
  272. $fields = array_keys($record);
  273. $fieldnames = implode(', ', $fields);
  274. $pms = array();
  275. foreach($record as $field => $value) {
  276. $pms[] = '?';
  277. }
  278. $placemarkers = implode(', ', $pms);
  279. $values = array_values($record);
  280. $sql = "INSERT INTO $table ($fieldnames) VALUES ($placemarkers)";
  281. $this->result = $this->handle->prepare($sql);
  282. if ($this->result === FALSE) {
  283. $this->fatal('PDO::prepare()', $sql);
  284. }
  285. $this->result->execute($values);
  286. }
  287. /**
  288. * Update a record in a table.
  289. *
  290. * Implements the UPDATE statement.
  291. *
  292. * NOTE: If a field should be quoted, ensure you do it beforehand.
  293. *
  294. * @param string $table Table name
  295. * @param array Associative array of fields and values
  296. * @param string Where clause
  297. */
  298. function update($table, $record, $where_clause)
  299. {
  300. if (empty($table))
  301. $this->fatal('Call to database::update() with no table name', '--');
  302. if (empty($record))
  303. $this->fatal('Call to database::update() with no field data', '--');
  304. if (empty($where_clause))
  305. $this->fatal('Call to database::update() with no where clause', '--');
  306. $fields = array_keys($record);
  307. $values = array_values($record);
  308. $max_terms = count($fields);
  309. for ($i = 0; $i < $max_terms; $i++) {
  310. $str = $fields[$i] . ' = ?';
  311. $terms[] = $str;
  312. }
  313. $fields_clause = implode(', ', $terms);
  314. $sql = "UPDATE $table SET $fields_clause WHERE $where_clause";
  315. $this->result = $this->handle->prepare($sql);
  316. if ($this->result === FALSE) {
  317. $this->fatal('PDO::prepare()', $sql);
  318. }
  319. $this->result->execute($values);
  320. }
  321. /**
  322. * Delete a record from a table.
  323. *
  324. * Simplified implementation of SQL DELETE command
  325. *
  326. * @param string $table The table
  327. * @param string $where_clause The WHERE clause
  328. */
  329. function delete($table, $where_clause = NULL)
  330. {
  331. if (empty($table)) {
  332. $this->dbh->fatal('Call to database::delete() with no table name', '--');
  333. }
  334. if (!is_null($where_clause)) {
  335. $sql = "DELETE FROM $table WHERE $where_clause";
  336. }
  337. else {
  338. $sql = "DELETE FROM $table";
  339. }
  340. $this->query($sql);
  341. }
  342. function commit()
  343. {
  344. $this->handle->commit();
  345. }
  346. function end()
  347. {
  348. $this->handle->commit();
  349. }
  350. function rollback()
  351. {
  352. $this->handle->rollBack();
  353. }
  354. function version()
  355. {
  356. return 2.5;
  357. }
  358. }