Subversion Repositories oidplus

Rev

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