Subversion Repositories oidplus

Rev

Rev 274 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
150 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
 
20
if (!defined('IN_OIDPLUS')) die();
21
 
246 daniel-mar 22
class OIDplusDatabasePluginODBC extends OIDplusDatabasePlugin {
257 daniel-mar 23
        private $conn;
261 daniel-mar 24
        private $last_error = null; // do the same like MySQL+PDO, just to be equal in the behavior
150 daniel-mar 25
 
236 daniel-mar 26
        public static function getPluginInformation(): array {
222 daniel-mar 27
                $out = array();
28
                $out['name'] = 'ODBC';
29
                $out['author'] = 'ViaThinkSoft';
30
                $out['version'] = null;
31
                $out['descriptionHTML'] = null;
32
                return $out;
33
        }
34
 
275 daniel-mar 35
        public static function id(): string {
150 daniel-mar 36
                return "ODBC";
37
        }
38
 
261 daniel-mar 39
        public function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
40
                $this->last_error = null;
150 daniel-mar 41
                if (is_null($prepared_args)) {
257 daniel-mar 42
                        $res = @odbc_exec($this->conn, $sql);
236 daniel-mar 43
 
44
                        if ($res === false) {
261 daniel-mar 45
                                $this->last_error = odbc_errormsg($this->conn);
236 daniel-mar 46
                                throw new OIDplusSQLException($sql, $this->error());
47
                        } else {
48
                                return new OIDplusQueryResultODBC($res);
49
                        }
150 daniel-mar 50
                } else {
51
                        // TEST: Emulate the prepared statement
236 daniel-mar 52
                        /*
150 daniel-mar 53
                        foreach ($prepared_args as $arg) {
54
                                $needle = '?';
55
                                $replace = "'$arg'"; // TODO: types
56
                                $pos = strpos($sql, $needle);
57
                                if ($pos !== false) {
58
                                        $sql = substr_replace($sql, $replace, $pos, strlen($needle));
59
                                }
60
                        }
257 daniel-mar 61
                        return OIDplusQueryResultODBC(@odbc_exec($this->conn, $sql));
236 daniel-mar 62
                        */
150 daniel-mar 63
                        if (!is_array($prepared_args)) {
250 daniel-mar 64
                                throw new OIDplusException("'prepared_args' must be either NULL or an ARRAY.");
150 daniel-mar 65
                        }
260 daniel-mar 66
 
239 daniel-mar 67
                        foreach ($prepared_args as &$value) {
68
                                // ODBC/SQLServer has problems converting "true" to the data type "bit"
69
                                // Error "Invalid character value for cast specification"
70
                                if (is_bool($value)) $value = $value ? '1' : '0';
150 daniel-mar 71
                        }
260 daniel-mar 72
 
257 daniel-mar 73
                        $ps = @odbc_prepare($this->conn, $sql);
239 daniel-mar 74
                        if (!$ps) {
266 daniel-mar 75
                                $this->last_error = odbc_errormsg($this->conn);
76
                                throw new OIDplusSQLException($sql, 'Cannot prepare statement: '.$this->error());
239 daniel-mar 77
                        }
78
 
150 daniel-mar 79
                        if (!@odbc_execute($ps, $prepared_args)) {
261 daniel-mar 80
                                $this->last_error = odbc_errormsg($this->conn);
236 daniel-mar 81
                                throw new OIDplusSQLException($sql, $this->error());
150 daniel-mar 82
                        }
236 daniel-mar 83
                        return new OIDplusQueryResultODBC($ps);
150 daniel-mar 84
                }
85
        }
236 daniel-mar 86
 
87
        public function error(): string {
261 daniel-mar 88
                $err = $this->last_error;
89
                if ($err == null) $err = '';
90
                return $err;
150 daniel-mar 91
        }
236 daniel-mar 92
 
269 daniel-mar 93
        protected function doConnect()/*: void*/ {
260 daniel-mar 94
                if (!function_exists('odbc_connect')) throw new OIDplusConfigInitializationException('PHP extension "ODBC" not installed');
95
 
150 daniel-mar 96
                // Try connecting to the database
261 daniel-mar 97
                $dsn      = OIDplus::baseConfig()->getValue('ODBC_DSN',      'DRIVER={SQL Server};SERVER=localhost;DATABASE=oidplus;CHARSET=UTF8');
98
                $username = OIDplus::baseConfig()->getValue('ODBC_USERNAME', '');
99
                $password = OIDplus::baseConfig()->getValue('ODBC_PASSWORD', '');
100
                $this->conn = @odbc_connect($dsn, $username, $password);
150 daniel-mar 101
 
257 daniel-mar 102
                if (!$this->conn) {
245 daniel-mar 103
                        $message = odbc_errormsg();
104
                        throw new OIDplusConfigInitializationException('Connection to the database failed! '.$message);
150 daniel-mar 105
                }
106
 
264 daniel-mar 107
                $this->last_error = null;
108
 
236 daniel-mar 109
                try {
110
                        $this->query("SET NAMES 'utf8'"); // Does most likely NOT work with ODBC. Try adding ";CHARSET=UTF8" (or similar) to the DSN
111
                } catch (Exception $e) {
112
                }
150 daniel-mar 113
        }
260 daniel-mar 114
 
269 daniel-mar 115
        protected function doDisconnect()/*: void*/ {
264 daniel-mar 116
                if (!is_null($this->conn)) {
117
                        @odbc_close($this->conn);
118
                        $this->conn = null;
119
                }
245 daniel-mar 120
        }
150 daniel-mar 121
 
122
        private $intransaction = false;
123
 
269 daniel-mar 124
        public function transaction_begin()/*: void*/ {
250 daniel-mar 125
                if ($this->intransaction) throw new OIDplusException("Nested transactions are not supported by this database plugin.");
264 daniel-mar 126
                odbc_autocommit($this->conn, false); // begin transaction
150 daniel-mar 127
                $this->intransaction = true;
128
        }
129
 
269 daniel-mar 130
        public function transaction_commit()/*: void*/ {
257 daniel-mar 131
                odbc_commit($this->conn);
264 daniel-mar 132
                odbc_autocommit($this->conn, true);
150 daniel-mar 133
                $this->intransaction = false;
134
        }
135
 
269 daniel-mar 136
        public function transaction_rollback()/*: void*/ {
257 daniel-mar 137
                odbc_rollback($this->conn);
264 daniel-mar 138
                odbc_autocommit($this->conn, true);
150 daniel-mar 139
                $this->intransaction = false;
140
        }
141
}
236 daniel-mar 142
 
143
class OIDplusQueryResultODBC extends OIDplusQueryResult {
144
        protected $no_resultset;
145
        protected $res;
146
 
147
        public function __construct($res) {
239 daniel-mar 148
                $this->no_resultset = is_bool($res);
260 daniel-mar 149
 
239 daniel-mar 150
                if (!$this->no_resultset) {
151
                        $this->res = $res;
152
                }
236 daniel-mar 153
        }
260 daniel-mar 154
 
239 daniel-mar 155
        public function __destruct() {
156
                // odbc_close_cursor($this->res);
157
        }
236 daniel-mar 158
 
159
        public function containsResultSet(): bool {
160
                return !$this->no_resultset;
161
        }
162
 
163
        public function num_rows(): int {
250 daniel-mar 164
                if ($this->no_resultset) throw new OIDplusException("The query has returned no result set (i.e. it was not a SELECT query)");
236 daniel-mar 165
                return odbc_num_rows($this->res);
166
        }
167
 
168
        public function fetch_array()/*: ?array*/ {
250 daniel-mar 169
                if ($this->no_resultset) throw new OIDplusException("The query has returned no result set (i.e. it was not a SELECT query)");
236 daniel-mar 170
                $ret = odbc_fetch_array($this->res);
171
                if ($ret === false) $ret = null;
239 daniel-mar 172
                if (!is_null($ret)) {
173
                        // ODBC gives bit(1) as binary, MySQL as integer and PDO as string.
174
                        // We'll do it like MySQL does, even if ODBC is actually more correct.
175
                        foreach ($ret as &$value) {
176
                                if ($value === chr(0)) $value = 0;
177
                                if ($value === chr(1)) $value = 1;
178
                        }
179
                }
236 daniel-mar 180
                return $ret;
181
        }
182
 
183
        public function fetch_object()/*: ?object*/ {
250 daniel-mar 184
                if ($this->no_resultset) throw new OIDplusException("The query has returned no result set (i.e. it was not a SELECT query)");
236 daniel-mar 185
                $ret = odbc_fetch_object($this->res);
186
                if ($ret === false) $ret = null;
239 daniel-mar 187
                if (!is_null($ret)) {
188
                        // ODBC gives bit(1) as binary, MySQL as integer and PDO as string.
189
                        // We'll do it like MySQL does, even if ODBC is actually more correct.
190
                        foreach ($ret as &$value) {
191
                                if ($value === chr(0)) $value = 0;
192
                                if ($value === chr(1)) $value = 1;
193
                        }
194
                }
236 daniel-mar 195
                return $ret;
196
        }
197
}