Rev 245 | Rev 247 | 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 { |
150 | daniel-mar | 23 | protected $connected = false; |
237 | daniel-mar | 24 | protected $html = null; |
150 | daniel-mar | 25 | |
236 | daniel-mar | 26 | public abstract static function name(): string; // this is the name that is set to the configuration value OIDPLUS_DATABASE_PLUGIN to identify the database plugin |
239 | daniel-mar | 27 | public abstract function query(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult; |
236 | daniel-mar | 28 | public abstract function insert_id(): int; |
29 | public abstract function error(): string; |
||
30 | public abstract function transaction_begin(): void; |
||
31 | public abstract function transaction_commit(): void; |
||
32 | public abstract function transaction_rollback(): void; |
||
245 | daniel-mar | 33 | protected abstract function doConnect(): void; |
34 | protected abstract function doDisconnect(): void; |
||
35 | |||
36 | public final function connect(): void { |
||
37 | if ($this->connected) return; |
||
38 | $this->beforeConnect(); |
||
39 | $this->doConnect(); |
||
40 | $this->connected = true; |
||
41 | register_shutdown_function(array($this, 'disconnect')); |
||
42 | $this->afterConnect(); |
||
43 | } |
||
44 | |||
45 | public final function disconnect(): void { |
||
46 | if (!$this->connected) return; |
||
47 | $this->beforeDisconnect(); |
||
48 | $this->doDisconnect(); |
||
49 | $this->connected = false; |
||
50 | $this->afterDisconnect(); |
||
51 | } |
||
150 | daniel-mar | 52 | |
239 | daniel-mar | 53 | public function natOrder($fieldname, $order='asc'): string { |
54 | |||
55 | $order = strtolower($order); |
||
56 | if (($order != 'asc') && ($order != 'desc')) { |
||
57 | throw new Exception("Invalid order '$order' (needs to be 'asc' or 'desc')"); |
||
58 | } |
||
59 | |||
244 | daniel-mar | 60 | $maxdepth = 100; // adjust this if you have performance issues |
61 | $max_arc_len = 50; |
||
239 | daniel-mar | 62 | |
150 | daniel-mar | 63 | $out = array(); |
239 | daniel-mar | 64 | |
65 | if ($this->slang() == 'pgsql') { |
||
245 | daniel-mar | 66 | if ($max_arc_len > 131072) $max_arc_len = 131072; // Limit of the "numeric()" type |
239 | daniel-mar | 67 | |
68 | // 1. Sort by namespace (oid, guid, ...) |
||
69 | $out[] = "SPLIT_PART($fieldname, ':', 1) $order"; |
||
70 | |||
71 | // 2. Only if namespace is 'oid:': Sort OID as integer array |
||
72 | $out[] = "STRING_TO_ARRAY(SPLIT_PART($fieldname, 'oid:', 2), '.')::numeric($max_arc_len)[] $order"; |
||
73 | |||
74 | // 3. Otherwise order by ID |
||
75 | $out[] = "$fieldname $order"; |
||
76 | |||
77 | } else if ($this->slang() == 'mysql') { |
||
245 | daniel-mar | 78 | if ($max_arc_len > 65) $max_arc_len = 65; // Limit of "decimal()" type |
239 | daniel-mar | 79 | |
80 | // 1. sort by namespace (oid, guid, ...) |
||
81 | $out[] = "SUBSTRING_INDEX($fieldname,':',1) $order"; |
||
82 | |||
83 | // 2. sort by first arc (0,1,2) |
||
84 | $out[] = "SUBSTRING(SUBSTRING_INDEX($fieldname,'.',1), LENGTH(SUBSTRING_INDEX($fieldname,':',1))+2, $max_arc_len) $order"; |
||
85 | |||
86 | for ($i=2; $i<=$maxdepth; $i++) { |
||
87 | // 3. Sort by the rest arcs one by one, not that MySQL can only handle decimal(65), not decimal($max_arc_len) |
||
245 | daniel-mar | 88 | $out[] = "cast(SUBSTRING(SUBSTRING_INDEX($fieldname,'.',$i), LENGTH(SUBSTRING_INDEX($fieldname,'.',".($i-1)."))+2, $max_arc_len) as decimal($max_arc_len)) $order"; |
239 | daniel-mar | 89 | } |
150 | daniel-mar | 90 | |
239 | daniel-mar | 91 | // 4. as last resort, sort by the identifier itself, e.g. if the casts above did fail (happens if it is not an OID) |
92 | $out[] = "$fieldname $order"; |
||
93 | |||
242 | daniel-mar | 94 | } else if ($this->slang() == 'mssql') { |
95 | |||
96 | // 1. sort by namespace (oid, guid, ...) |
||
244 | daniel-mar | 97 | $out[] = "SUBSTRING($fieldname,1,CHARINDEX(':',$fieldname)-1) $order"; |
242 | daniel-mar | 98 | |
99 | for ($i=1; $i<=$maxdepth; $i++) { |
||
100 | // 2. Sort by the rest arcs one by one, not that MySQL can only handle decimal(65), not decimal($max_arc_len) |
||
244 | daniel-mar | 101 | $out[] = "dbo.getOidArc($fieldname, $max_arc_len, $i) $order"; |
242 | daniel-mar | 102 | } |
103 | |||
104 | // 3. as last resort, sort by the identifier itself, e.g. if the function getOidArc always return 0 (happens if it is not an OID) |
||
105 | $out[] = "$fieldname $order"; |
||
239 | daniel-mar | 106 | } else { |
107 | |||
108 | // For (yet) unsupported DBMS, we do not offer natural sort |
||
109 | $out[] = "$fieldname $order"; |
||
110 | |||
150 | daniel-mar | 111 | } |
239 | daniel-mar | 112 | |
113 | return implode(', ', $out); |
||
150 | daniel-mar | 114 | } |
115 | |||
245 | daniel-mar | 116 | protected function beforeDisconnect(): void {} |
117 | |||
118 | protected function afterDisconnect(): void {} |
||
119 | |||
120 | protected function beforeConnect(): void {} |
||
121 | |||
237 | daniel-mar | 122 | protected function afterConnect(): void { |
245 | daniel-mar | 123 | // Check if the config table exists. This is important because the database version is stored in it |
124 | $this->initRequireTables(array('config')); |
||
150 | daniel-mar | 125 | |
245 | daniel-mar | 126 | // Do the database tables need an update? |
127 | // It is important that we do it immediately after connecting, |
||
128 | // because the database structure might change and therefore various things might fail. |
||
150 | daniel-mar | 129 | // Note: The config setting "database_version" is inserted in setup/sql/...sql, not in the OIDplus core init |
130 | |||
223 | daniel-mar | 131 | $res = $this->query("SELECT value FROM ".OIDPLUS_TABLENAME_PREFIX."config WHERE name = 'database_version'"); |
245 | daniel-mar | 132 | $row = $res->fetch_array(); |
239 | daniel-mar | 133 | if ($row == null) { |
240 | daniel-mar | 134 | throw new OIDplusConfigInitializationException('Cannot determine database version (the entry "database_version" inside the table "'.OIDPLUS_TABLENAME_PREFIX.'config" is probably missing)'); |
245 | daniel-mar | 135 | } |
150 | daniel-mar | 136 | $version = $row['value']; |
245 | daniel-mar | 137 | if (!is_numeric($version) || ($version < 200) || ($version > 999)) { |
138 | throw new OIDplusConfigInitializationException('Entry "database_version" inside the table "'.OIDPLUS_TABLENAME_PREFIX.'config" seems to be wrong (expect number between 200 and 999)'); |
||
139 | } |
||
140 | while (file_exists($file = __DIR__."/../db_updates/update$version.inc.php")) { |
||
141 | $prev_version = $version; |
||
142 | include $file; // run update-script |
||
143 | if ($version != $prev_version+1) { |
||
144 | // This should usually not happen, since the update-file should increase the version |
||
145 | // or throw an Exception by itself |
||
146 | throw new Exception("Database update $prev_version -> ".($prev_version+1)." failed (script reports new version to be $version)"); |
||
147 | } |
||
150 | daniel-mar | 148 | } |
239 | daniel-mar | 149 | |
245 | daniel-mar | 150 | // Now that our database is up-to-date, we check if database tables are existing |
151 | // without config table, because it was checked above |
||
152 | $this->initRequireTables(array('objects', 'asn1id', 'iri', 'ra'/*, 'config'*/)); |
||
153 | } |
||
154 | |||
155 | private function initRequireTables($tableNames) { |
||
156 | $msgs = array(); |
||
157 | foreach ($tableNames as $tableName) { |
||
158 | if (!$this->tableExists(OIDPLUS_TABLENAME_PREFIX.$tableName)) { |
||
159 | $msgs[] = 'Table '.OIDPLUS_TABLENAME_PREFIX.$tableName.' is missing!'; |
||
239 | daniel-mar | 160 | } |
161 | } |
||
245 | daniel-mar | 162 | if (count($msgs) > 0) { |
163 | throw new OIDplusConfigInitializationException(implode("\n\n",$msgs)); |
||
242 | daniel-mar | 164 | } |
150 | daniel-mar | 165 | } |
245 | daniel-mar | 166 | |
167 | public function tableExists($tableName) { |
||
168 | try { |
||
169 | $this->query("select 0 from ".$tableName." where 1=0"); |
||
170 | return true; |
||
171 | } catch (Exception $e) { |
||
172 | return false; |
||
173 | } |
||
237 | daniel-mar | 174 | } |
175 | |||
236 | daniel-mar | 176 | public function isConnected(): bool { |
150 | daniel-mar | 177 | return $this->connected; |
178 | } |
||
237 | daniel-mar | 179 | |
180 | public function init($html = true): void { |
||
181 | $this->html = $html; |
||
182 | } |
||
239 | daniel-mar | 183 | |
184 | public function slang(): string { |
||
185 | // The constant OIDPLUS_DBMS_SLANG is used as cache. |
||
186 | // You can also put it in your config.inc.php if you want to enforce a slang to be used |
||
187 | |||
188 | if (defined('OIDPLUS_DBMS_SLANG')) { |
||
189 | return OIDPLUS_DBMS_SLANG; |
||
190 | } else { |
||
191 | try { |
||
192 | // MySQL, MariaDB and PostgreSQL |
||
243 | daniel-mar | 193 | $vers = $this->query("select version() as dbms_version")->fetch_object()->dbms_version; |
239 | daniel-mar | 194 | $vers = strtolower($vers); |
195 | } catch (Exception $e) { |
||
196 | try { |
||
197 | // Microsoft SQL Server |
||
243 | daniel-mar | 198 | $vers = $this->query("select @@version as dbms_version")->fetch_object()->dbms_version; |
239 | daniel-mar | 199 | $vers = strtolower($vers); |
200 | } catch (Exception $e) { |
||
201 | throw new Exception("Cannot determine the slang of your DBMS (function 'version()' could not be called). Your DBMS is probably not supported."); |
||
202 | } |
||
203 | } |
||
204 | |||
205 | $slang = null; |
||
206 | if (strpos($vers, 'postgresql') !== false) $slang = 'pgsql'; |
||
207 | if (strpos($vers, 'mysql') !== false) $slang = 'mysql'; |
||
208 | if (strpos($vers, 'mariadb') !== false) $slang = 'mysql'; |
||
209 | if (strpos($vers, 'microsoft sql server') !== false) $slang = 'mssql'; |
||
210 | if (!is_null($slang)) { |
||
211 | define('OIDPLUS_DBMS_SLANG', $slang); |
||
212 | return $slang; |
||
213 | } else { |
||
214 | throw new Exception("Cannot determine the slang of your DBMS (we don't know what to do with the DBMS '$vers'). Your DBMS is probably not supported."); |
||
215 | } |
||
216 | } |
||
217 | } |
||
2 | daniel-mar | 218 | } |
219 |