Rev 274 | Rev 277 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2 | daniel-mar | 1 | <?php |
2 | |||
3 | /* |
||
4 | * OIDplus 2.0 |
||
5 | * Copyright 2019 Daniel Marschall, ViaThinkSoft |
||
6 | * |
||
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
8 | * you may not use this file except in compliance with the License. |
||
9 | * You may obtain a copy of the License at |
||
10 | * |
||
11 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
12 | * |
||
13 | * Unless required by applicable law or agreed to in writing, software |
||
14 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
16 | * See the License for the specific language governing permissions and |
||
17 | * limitations under the License. |
||
18 | */ |
||
19 | |||
112 | daniel-mar | 20 | if (!defined('IN_OIDPLUS')) die(); |
21 | |||
246 | daniel-mar | 22 | abstract class OIDplusDatabasePlugin extends OIDplusPlugin { |
274 | daniel-mar | 23 | protected /*bool*/ $connected = false; |
24 | protected /*?bool*/ $html = null; |
||
25 | protected /*?string*/ $last_query = null; |
||
26 | protected /*?OIDplusSqlSlangPlugin*/ $slang = null; |
||
150 | daniel-mar | 27 | |
275 | daniel-mar | 28 | public abstract static function id(): string; // this is the name that is set to the configuration value OIDplus::baseConfig()->getValue('DATABASE_PLUGIN') to identify the database plugin |
261 | daniel-mar | 29 | protected abstract function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult; |
236 | daniel-mar | 30 | public abstract function error(): string; |
269 | daniel-mar | 31 | public abstract function transaction_begin()/*: void*/; |
32 | public abstract function transaction_commit()/*: void*/; |
||
33 | public abstract function transaction_rollback()/*: void*/; |
||
34 | protected abstract function doConnect()/*: void*/; |
||
35 | protected abstract function doDisconnect()/*: void*/; |
||
261 | daniel-mar | 36 | |
274 | daniel-mar | 37 | public function insert_id(): int { |
38 | // This is the "fallback" variant. If your database provider (e.g. PDO) supports |
||
39 | // a function to detect the last inserted id, please override this |
||
40 | // function in order to use that specialized function (since it is usually |
||
41 | // more reliable). |
||
42 | return $this->getSlang()->insert_id(); |
||
43 | } |
||
44 | |||
261 | daniel-mar | 45 | public final function query(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult { |
46 | |||
47 | $query_logfile = OIDplus::baseConfig()->getValue('QUERY_LOGFILE', ''); |
||
48 | if (!empty($query_logfile)) { |
||
49 | $ts = explode(" ",microtime()); |
||
50 | $ts = date("Y-m-d H:i:s",$ts[1]).substr((string)$ts[0],1,4); |
||
51 | static $log_session_id = ""; |
||
52 | if (empty($log_session_id)) { |
||
53 | $log_session_id = rand(10000,99999); |
||
54 | } |
||
55 | $file = isset($_SERVER['REQUEST_URI']) ? ' | '.$_SERVER['REQUEST_URI'] : ''; |
||
56 | file_put_contents($query_logfile, "$ts <$log_session_id$file> $sql\n", FILE_APPEND); |
||
57 | } |
||
58 | |||
59 | $this->last_query = $sql; |
||
60 | $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql); |
||
61 | return $this->doQuery($sql, $prepared_args); |
||
62 | } |
||
63 | |||
269 | daniel-mar | 64 | public final function connect()/*: void*/ { |
245 | daniel-mar | 65 | if ($this->connected) return; |
66 | $this->beforeConnect(); |
||
67 | $this->doConnect(); |
||
68 | $this->connected = true; |
||
69 | register_shutdown_function(array($this, 'disconnect')); |
||
70 | $this->afterConnect(); |
||
71 | } |
||
261 | daniel-mar | 72 | |
269 | daniel-mar | 73 | public final function disconnect()/*: void*/ { |
245 | daniel-mar | 74 | if (!$this->connected) return; |
75 | $this->beforeDisconnect(); |
||
76 | $this->doDisconnect(); |
||
77 | $this->connected = false; |
||
78 | $this->afterDisconnect(); |
||
79 | } |
||
150 | daniel-mar | 80 | |
239 | daniel-mar | 81 | public function natOrder($fieldname, $order='asc'): string { |
274 | daniel-mar | 82 | if (!is_null($this->slang)) { |
83 | return $this->slang->natOrder($fieldname, $order); |
||
84 | } else { |
||
85 | $order = strtolower($order); |
||
86 | if (($order != 'asc') && ($order != 'desc')) { |
||
87 | throw new OIDplusException("Invalid order '$order' (needs to be 'asc' or 'desc')"); |
||
239 | daniel-mar | 88 | } |
150 | daniel-mar | 89 | |
239 | daniel-mar | 90 | // For (yet) unsupported DBMS, we do not offer natural sort |
274 | daniel-mar | 91 | return "$fieldname $order"; |
150 | daniel-mar | 92 | } |
93 | } |
||
94 | |||
269 | daniel-mar | 95 | protected function beforeDisconnect()/*: void*/ {} |
245 | daniel-mar | 96 | |
269 | daniel-mar | 97 | protected function afterDisconnect()/*: void*/ {} |
245 | daniel-mar | 98 | |
269 | daniel-mar | 99 | protected function beforeConnect()/*: void*/ {} |
245 | daniel-mar | 100 | |
269 | daniel-mar | 101 | protected function afterConnect()/*: void*/ { |
245 | daniel-mar | 102 | // Check if the config table exists. This is important because the database version is stored in it |
103 | $this->initRequireTables(array('config')); |
||
150 | daniel-mar | 104 | |
245 | daniel-mar | 105 | // Do the database tables need an update? |
106 | // It is important that we do it immediately after connecting, |
||
107 | // because the database structure might change and therefore various things might fail. |
||
150 | daniel-mar | 108 | // Note: The config setting "database_version" is inserted in setup/sql/...sql, not in the OIDplus core init |
109 | |||
261 | daniel-mar | 110 | $res = $this->query("SELECT value FROM ###config WHERE name = 'database_version'"); |
111 | $row = $res->fetch_array(); |
||
239 | daniel-mar | 112 | if ($row == null) { |
261 | daniel-mar | 113 | throw new OIDplusConfigInitializationException('Cannot determine database version (the entry "database_version" inside the table "###config" is probably missing)'); |
114 | } |
||
150 | daniel-mar | 115 | $version = $row['value']; |
245 | daniel-mar | 116 | if (!is_numeric($version) || ($version < 200) || ($version > 999)) { |
261 | daniel-mar | 117 | throw new OIDplusConfigInitializationException('Entry "database_version" inside the table "###config" seems to be wrong (expect number between 200 and 999)'); |
118 | } |
||
245 | daniel-mar | 119 | while (file_exists($file = __DIR__."/../db_updates/update$version.inc.php")) { |
120 | $prev_version = $version; |
||
121 | include $file; // run update-script |
||
122 | if ($version != $prev_version+1) { |
||
123 | // This should usually not happen, since the update-file should increase the version |
||
124 | // or throw an Exception by itself |
||
250 | daniel-mar | 125 | throw new OIDplusException("Database update $prev_version -> ".($prev_version+1)." failed (script reports new version to be $version)"); |
245 | daniel-mar | 126 | } |
150 | daniel-mar | 127 | } |
239 | daniel-mar | 128 | |
245 | daniel-mar | 129 | // Now that our database is up-to-date, we check if database tables are existing |
130 | // without config table, because it was checked above |
||
131 | $this->initRequireTables(array('objects', 'asn1id', 'iri', 'ra'/*, 'config'*/)); |
||
132 | } |
||
261 | daniel-mar | 133 | |
274 | daniel-mar | 134 | protected static function getHardcodedSlangById($id): /*?*/OIDplusSqlSlangPlugin { |
135 | foreach (OIDplus::getSqlSlangPlugins() as $plugin) { |
||
136 | if ($plugin::id() == $id) { |
||
137 | return $plugin; |
||
138 | } |
||
139 | } |
||
140 | return null; |
||
141 | } |
||
142 | |||
143 | private function initRequireTables($tableNames)/*: void*/ { |
||
245 | daniel-mar | 144 | $msgs = array(); |
145 | foreach ($tableNames as $tableName) { |
||
261 | daniel-mar | 146 | $prefix = OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''); |
147 | if (!$this->tableExists($prefix.$tableName)) { |
||
148 | $msgs[] = 'Table '.$prefix.$tableName.' is missing!'; |
||
239 | daniel-mar | 149 | } |
150 | } |
||
245 | daniel-mar | 151 | if (count($msgs) > 0) { |
152 | throw new OIDplusConfigInitializationException(implode("\n\n",$msgs)); |
||
242 | daniel-mar | 153 | } |
150 | daniel-mar | 154 | } |
261 | daniel-mar | 155 | |
274 | daniel-mar | 156 | public function tableExists($tableName): bool { |
245 | daniel-mar | 157 | try { |
158 | $this->query("select 0 from ".$tableName." where 1=0"); |
||
159 | return true; |
||
160 | } catch (Exception $e) { |
||
161 | return false; |
||
162 | } |
||
237 | daniel-mar | 163 | } |
164 | |||
236 | daniel-mar | 165 | public function isConnected(): bool { |
150 | daniel-mar | 166 | return $this->connected; |
167 | } |
||
237 | daniel-mar | 168 | |
269 | daniel-mar | 169 | public function init($html = true)/*: void*/ { |
237 | daniel-mar | 170 | $this->html = $html; |
171 | } |
||
239 | daniel-mar | 172 | |
264 | daniel-mar | 173 | public function sqlDate(): string { |
274 | daniel-mar | 174 | if (!is_null($this->slang)) { |
175 | return $this->slang->sqlDate(); |
||
176 | } else { |
||
177 | return "'" . datetime('Y-m-d H:i:s') . "'"; |
||
264 | daniel-mar | 178 | } |
179 | } |
||
180 | |||
274 | daniel-mar | 181 | public final function getSlang(bool $mustExist=true): /*?*/OIDplusSqlSlangPlugin { |
182 | if (is_null($this->slang)) { |
||
183 | if (OIDplus::baseConfig()->exists('FORCE_DBMS_SLANG')) { |
||
184 | $name = OIDplus::baseConfig()->getValue('FORCE_DBMS_SLANG', ''); |
||
185 | $this->slang = self::getHardcodedSlangById($name); |
||
186 | if ($mustExist && is_null($this->slang)) { |
||
187 | throw new OIDplusConfigInitializationException("Enforced SQL slang (via setting FORCE_DBMS_SLANG) '$name' does not exist."); |
||
188 | } |
||
189 | } else { |
||
190 | foreach (OIDplus::getSqlSlangPlugins() as $plugin) { |
||
191 | if ($plugin->detect()) { |
||
192 | $this->slang = $plugin; |
||
193 | break; |
||
264 | daniel-mar | 194 | } |
239 | daniel-mar | 195 | } |
274 | daniel-mar | 196 | if ($mustExist && is_null($this->slang)) { |
197 | throw new OIDplusException("Cannot determine the SQL slang of your DBMS. Your DBMS is probably not supported."); |
||
198 | } |
||
239 | daniel-mar | 199 | } |
274 | daniel-mar | 200 | } |
239 | daniel-mar | 201 | |
274 | daniel-mar | 202 | return $this->slang; |
239 | daniel-mar | 203 | } |
2 | daniel-mar | 204 | } |
205 |