Subversion Repositories oidplus

Rev

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