Subversion Repositories oidplus

Rev

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

Rev Author Line No. Line
635 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
5
 * Copyright 2019 - 2021 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('INSIDE_OIDPLUS')) die();
21
 
22
class OIDplusDatabaseConnectionODBC extends OIDplusDatabaseConnection {
23
        private $conn;
24
        private $last_error = null; // do the same like MySQL+PDO, just to be equal in the behavior
25
        private $transactions_supported = false;
26
 
27
        protected function forcePrepareEmulation() {
28
                $mode = OIDplus::baseConfig()->getValue('PREPARED_STATEMENTS_EMULATION', 'auto');
29
                if ($mode === 'on') return true;
30
                if ($mode === 'off') return false;
31
 
32
                static $res = null;
33
                if (is_null($res)) {
34
                        $sql = 'select name from ###config where name = ?';
35
                        $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql);
36
                        $res = @odbc_prepare($this->conn, $sql) === false;
37
                }
38
 
39
                return $res;
40
        }
41
 
42
        public function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
43
                $this->last_error = null;
44
                if (is_null($prepared_args)) {
45
                        $res = @odbc_exec($this->conn, $sql);
46
 
47
                        if ($res === false) {
48
                                $this->last_error = odbc_errormsg($this->conn);
49
                                throw new OIDplusSQLException($sql, $this->error());
50
                        } else {
51
                                return new OIDplusQueryResultODBC($res);
52
                        }
53
                } else {
54
                        if (!is_array($prepared_args)) {
55
                                throw new OIDplusException(_L('"prepared_args" must be either NULL or an ARRAY.'));
56
                        }
57
 
58
                        if ($this->forcePrepareEmulation()) {
59
                                // For some drivers (e.g. Microsoft Access), we need to do this kind of emulation, because odbc_prepare() does not work
60
                                $sql = str_replace('?', chr(1), $sql);
61
                                foreach ($prepared_args as $arg) {
62
                                        $needle = chr(1);
63
                                        if (is_bool($arg)) {
64
                                                if ($this->slangDetectionDone) {
65
                                                        $replace = $this->getSlang()->getSQLBool($arg);
66
                                                } else {
67
                                                        $replace = $arg ? '1' : '0';
68
                                                }
69
                                        } else {
70
                                                if ($this->slangDetectionDone) {
71
                                                        $replace = "'".$this->getSlang()->escapeString($arg)."'"; // TODO: types
72
                                                } else {
73
                                                        $replace = "'".str_replace("'", "''", $arg)."'"; // TODO: types
74
                                                }
75
                                        }
76
                                        $pos = strpos($sql, $needle);
77
                                        if ($pos !== false) {
78
                                                $sql = substr_replace($sql, $replace, $pos, strlen($needle));
79
                                        }
80
                                }
81
                                $sql = str_replace(chr(1), '?', $sql);
82
                                $ps = @odbc_exec($this->conn, $sql);
83
                                if (!$ps) {
84
                                        $this->last_error = odbc_errormsg($this->conn);
85
                                        throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error());
86
                                }
87
                                return new OIDplusQueryResultODBC($ps);
88
                        } else {
89
                                foreach ($prepared_args as &$value) {
90
                                        // ODBC/SQLServer has problems converting "true" to the data type "bit"
91
                                        // Error "Invalid character value for cast specification"
92
                                        if (is_bool($value)) {
93
                                                if ($this->slangDetectionDone) {
94
                                                        $value = $this->getSlang()->getSQLBool($value);
95
                                                } else {
96
                                                        $value = $value ? '1' : '0';
97
                                                }
98
                                        }
99
                                }
100
 
101
                                $ps = @odbc_prepare($this->conn, $sql);
102
                                if (!$ps) {
103
                                        $this->last_error = odbc_errormsg($this->conn);
104
                                        throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error());
105
                                }
106
 
107
                                if (!@odbc_execute($ps, $prepared_args)) {
108
                                        $this->last_error = odbc_errormsg($this->conn);
109
                                        throw new OIDplusSQLException($sql, $this->error());
110
                                }
111
                                return new OIDplusQueryResultODBC($ps);
112
                        }
113
                }
114
        }
115
 
116
        public function error(): string {
117
                $err = $this->last_error;
118
                if ($err == null) $err = '';
119
                $err = utf8_encode($err); // because ODBC might output weird stuff ...
120
                return $err;
121
        }
122
 
123
        protected function doConnect()/*: void*/ {
124
                if (!function_exists('odbc_connect')) throw new OIDplusConfigInitializationException(_L('PHP extension "%1" not installed','ODBC'));
125
 
126
                // Try connecting to the database
127
                $dsn      = OIDplus::baseConfig()->getValue('ODBC_DSN',      'DRIVER={SQL Server};SERVER=localhost;DATABASE=oidplus;CHARSET=UTF8');
128
                $username = OIDplus::baseConfig()->getValue('ODBC_USERNAME', '');
129
                $password = OIDplus::baseConfig()->getValue('ODBC_PASSWORD', '');
130
                $this->conn = @odbc_connect($dsn, $username, $password);
131
 
132
                if (!$this->conn) {
133
                        $message = odbc_errormsg();
134
                        throw new OIDplusConfigInitializationException(_L('Connection to the database failed!').' '.$message);
135
                }
136
 
137
                $this->last_error = null;
138
 
139
                try {
140
                        @odbc_exec($this->conn, "SET NAMES 'utf8'"); // Does most likely NOT work with ODBC. Try adding ";CHARSET=UTF8" (or similar) to the DSN
141
                } catch (Exception $e) {
142
                }
143
 
144
                // We check if the DBMS supports autocommit.
145
                // Attention: Check it after you have sent a query already, because Microsoft Access doesn't seem to allow
146
                // changing auto commit once a query was executed ("Attribute cannot be set now SQLState: S1011")
147
                // Note: For some weird reason we *DO* need to redirect the output to "$dummy", otherwise it won't work!
148
                $sql = "select name from ###config where 1=0";
149
                $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql);
150
                $dummy = @odbc_exec($this->conn, $sql);
151
                $this->transactions_supported = @odbc_autocommit($this->conn, false);
152
                @odbc_autocommit($this->conn, true);
153
        }
154
 
155
        protected function doDisconnect()/*: void*/ {
156
                if (!is_null($this->conn)) {
157
                        @odbc_close($this->conn);
158
                        $this->conn = null;
159
                }
160
        }
161
 
162
        private $intransaction = false;
163
 
164
        public function transaction_supported(): bool {
165
                return $this->transactions_supported;
166
        }
167
 
168
        public function transaction_level(): int {
169
                if (!$this->transaction_supported()) {
170
                        // TODO?
171
                        return 0;
172
                }
173
                return $this->intransaction ? 1 : 0;
174
        }
175
 
176
        public function transaction_begin()/*: void*/ {
177
                if (!$this->transaction_supported()) {
178
                        // TODO?
179
                        return;
180
                }
181
                if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
182
                odbc_autocommit($this->conn, false); // begin transaction
183
                $this->intransaction = true;
184
        }
185
 
186
        public function transaction_commit()/*: void*/ {
187
                if (!$this->transaction_supported()) {
188
                        // TODO?
189
                        return;
190
                }
191
                odbc_commit($this->conn);
192
                odbc_autocommit($this->conn, true);
193
                $this->intransaction = false;
194
        }
195
 
196
        public function transaction_rollback()/*: void*/ {
197
                if (!$this->transaction_supported()) {
198
                        // TODO?
199
                        return;
200
                }
201
                odbc_rollback($this->conn);
202
                odbc_autocommit($this->conn, true);
203
                $this->intransaction = false;
204
        }
205
}