Subversion Repositories oidplus

Rev

Rev 501 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 501 Rev 502
Line 18... Line 18...
18
 */
18
 */
19
 
19
 
20
class OIDplusDatabaseConnectionPDO extends OIDplusDatabaseConnection {
20
class OIDplusDatabaseConnectionPDO extends OIDplusDatabaseConnection {
21
        private $conn = null;
21
        private $conn = null;
22
        private $last_error = null; // we need that because PDO divides prepared statement errors and normal query errors, but we have only one "error()" method
22
        private $last_error = null; // we need that because PDO divides prepared statement errors and normal query errors, but we have only one "error()" method
-
 
23
        private $transactions_supported = false;
23
 
24
 
24
        public static function getPlugin(): OIDplusDatabasePlugin {
25
        public static function getPlugin(): OIDplusDatabasePlugin {
25
                return new OIDplusDatabasePluginPDO();
26
                return new OIDplusDatabasePluginPDO();
26
        }
27
        }
27
 
28
 
Line 35... Line 36...
35
                                throw new OIDplusSQLException($sql, $this->error());
36
                                throw new OIDplusSQLException($sql, $this->error());
36
                        } else {
37
                        } else {
37
                                return new OIDplusQueryResultPDO($res);
38
                                return new OIDplusQueryResultPDO($res);
38
                        }
39
                        }
39
                } else {
40
                } else {
40
                        // TEST: Emulate the prepared statement
41
                        if (!is_array($prepared_args)) {
-
 
42
                                throw new OIDplusException(_L('"prepared_args" must be either NULL or an ARRAY.'));
-
 
43
                        }
-
 
44
 
41
                        /*
45
                        /*
-
 
46
                        // Not required because PDO can emulate prepared statements by itself
-
 
47
                        if ($this->forcePrepareEmulation()) {
-
 
48
                                $sql = str_replace('?', chr(1), $sql);
42
                        foreach ($prepared_args as $arg) {
49
                                foreach ($prepared_args as $arg) {
43
                                $needle = '?';
50
                                        $needle = chr(1);
-
 
51
                                        if (is_bool($arg)) {
-
 
52
                                                if ($this->slangDetectionDone) {
-
 
53
                                                        $replace = $this->getSlang()->getSQLBool($arg);
-
 
54
                                                } else {
-
 
55
                                                        $replace = $arg ? '1' : '0';
-
 
56
                                                }
-
 
57
                                        } else {
44
                                $replace = "'$arg'"; // TODO: types
58
                                                $replace = "'$arg'"; // TODO: types
-
 
59
                                        }
45
                                $pos = strpos($sql, $needle);
60
                                        $pos = strpos($sql, $needle);
46
                                if ($pos !== false) {
61
                                        if ($pos !== false) {
47
                                        $sql = substr_replace($sql, $replace, $pos, strlen($needle));
62
                                                $sql = substr_replace($sql, $replace, $pos, strlen($needle));
48
                                }
63
                                        }
49
                        }
64
                                }
-
 
65
                                $sql = str_replace(chr(1), '?', $sql);
50
                        return new OIDplusQueryResultPDO($this->conn->query($sql));
66
                                $ps = $this->conn->query($sql);
51
                        */
67
                                if (!$ps) {
52
 
-
 
53
                        if (!is_array($prepared_args)) {
68
                                        $this->last_error = $this->error();
54
                                throw new OIDplusException(_L('"prepared_args" must be either NULL or an ARRAY.'));
69
                                        throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error());
55
                        }
70
                                }
-
 
71
                                return new OIDplusQueryResultPDO($ps);
-
 
72
                        }
-
 
73
                        */
56
 
74
 
57
                        foreach ($prepared_args as &$value) {
75
                        foreach ($prepared_args as &$value) {
58
                                // We need to manually convert booleans into strings, because there is a
76
                                // We need to manually convert booleans into strings, because there is a
59
                                // 14 year old bug that hasn't been adressed by the PDO developers:
77
                                // 14 year old bug that hasn't been adressed by the PDO developers:
60
                                // https://bugs.php.net/bug.php?id=57157
78
                                // https://bugs.php.net/bug.php?id=57157
-
 
79
                                if (is_bool($value)) {
-
 
80
                                        if ($this->slangDetectionDone) {
-
 
81
                                                $value = $this->getSlang()->getSQLBool($value);
-
 
82
                                        } else {
-
 
83
                                                // This works for everything except Microsoft Access (which needs -1 and 0)
61
                                // Note: We are using '1' and '0' instead of 'true' and 'false' because MySQL converts boolean to tinyint(1)
84
                                                // Note: We are using '1' and '0' instead of 'true' and 'false' because MySQL converts boolean to tinyint(1)
62
                                if (is_bool($value)) $value = $value ? '1' : '0';
85
                                                $value = $value ? '1' : '0';
-
 
86
                                        }
-
 
87
                                }
63
                        }
88
                        }
64
 
89
 
65
                        $ps = $this->conn->prepare($sql);
90
                        $ps = $this->conn->prepare($sql);
66
                        if (!$ps) {
91
                        if (!$ps) {
67
                                $this->last_error = $ps->errorInfo()[2];
92
                                $this->last_error = $this->conn->errorInfo()[2];
68
                                throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error());
93
                                throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error());
69
                        }
94
                        }
70
 
95
 
71
                        if (!$ps->execute($prepared_args)) {
96
                        if (!$ps->execute($prepared_args)) {
72
                                $this->last_error = $ps->errorInfo()[2];
97
                                $this->last_error = $ps->errorInfo()[2];
Line 75... Line 100...
75
                        return new OIDplusQueryResultPDO($ps);
100
                        return new OIDplusQueryResultPDO($ps);
76
                }
101
                }
77
        }
102
        }
78
 
103
 
79
        public function insert_id(): int {
104
        public function insert_id(): int {
-
 
105
                try {
80
                return $this->conn->lastInsertId();
106
                        $out = @($this->conn->lastInsertId());
-
 
107
                        if ($out === false) return parent::insert_id(); // fallback method that uses the SQL slang
-
 
108
                        return $out;
-
 
109
                } catch (Exception $e) {
-
 
110
                        return parent::insert_id(); // fallback method that uses the SQL slang
-
 
111
                }
81
        }
112
        }
82
 
113
 
83
        public function error(): string {
114
        public function error(): string {
84
                $err = $this->last_error;
115
                $err = $this->last_error;
85
                if ($err == null) $err = '';
116
                if ($err == null) $err = '';
Line 106... Line 137...
106
                        throw new OIDplusConfigInitializationException(_L('Connection to the database failed!').' '.$message);
137
                        throw new OIDplusConfigInitializationException(_L('Connection to the database failed!').' '.$message);
107
                }
138
                }
108
 
139
 
109
                $this->last_error = null;
140
                $this->last_error = null;
110
 
141
 
-
 
142
                try {
111
                $this->query("SET NAMES 'utf8'");
143
                        @$this->conn->exec("SET NAMES 'utf8'");
-
 
144
                } catch (Exception $e) {
-
 
145
                }
-
 
146
 
-
 
147
                // We check if the DBMS supports autocommit.
-
 
148
                // Attention: Check it after you have sent a query already, because Microsoft Access doesn't seem to allow
-
 
149
                // changing auto commit once a query was executed ("Attribute cannot be set now SQLState: S1011")
-
 
150
                // Note: For some weird reason we *DO* need to redirect the output to "$dummy", otherwise it won't work!
-
 
151
                $dummy = $this->conn->query("select name from config where 1=0");
-
 
152
                try {
-
 
153
                        $this->conn->beginTransaction();
-
 
154
                        $this->conn->rollBack();
-
 
155
                        $this->transactions_supported = true;
-
 
156
                } catch (Exception $e) {
-
 
157
                        $this->transactions_supported = false;
-
 
158
                }
112
        }
159
        }
113
 
160
 
114
        protected function doDisconnect()/*: void*/ {
161
        protected function doDisconnect()/*: void*/ {
115
                $this->conn = null; // the connection will be closed by removing the reference
162
                $this->conn = null; // the connection will be closed by removing the reference
116
        }
163
        }
117
 
164
 
118
        private $intransaction = false;
165
        private $intransaction = false;
119
 
166
 
120
        public function transaction_supported(): bool {
167
        public function transaction_supported(): bool {
121
                return true;
168
                return $this->transactions_supported;
122
        }
169
        }
123
 
170
 
124
        public function transaction_level(): int {
171
        public function transaction_level(): int {
-
 
172
                if (!$this->transaction_supported()) {
-
 
173
                        // TODO?
-
 
174
                        return 0;
-
 
175
                }
125
                return $this->intransaction ? 1 : 0;
176
                return $this->intransaction ? 1 : 0;
126
        }
177
        }
127
 
178
 
128
        public function transaction_begin()/*: void*/ {
179
        public function transaction_begin()/*: void*/ {
-
 
180
                if (!$this->transaction_supported()) {
-
 
181
                        // TODO?
-
 
182
                        return;
-
 
183
                }
129
                if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
184
                if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
130
                $this->conn->beginTransaction();
185
                $this->conn->beginTransaction();
131
                $this->intransaction = true;
186
                $this->intransaction = true;
132
        }
187
        }
133
 
188
 
134
        public function transaction_commit()/*: void*/ {
189
        public function transaction_commit()/*: void*/ {
-
 
190
                if (!$this->transaction_supported()) {
-
 
191
                        // TODO?
-
 
192
                        return;
-
 
193
                }
135
                $this->conn->commit();
194
                $this->conn->commit();
136
                $this->intransaction = false;
195
                $this->intransaction = false;
137
        }
196
        }
138
 
197
 
139
        public function transaction_rollback()/*: void*/ {
198
        public function transaction_rollback()/*: void*/ {
-
 
199
                if (!$this->transaction_supported()) {
-
 
200
                        // TODO?
-
 
201
                        return;
-
 
202
                }
140
                $this->conn->rollBack();
203
                $this->conn->rollBack();
141
                $this->intransaction = false;
204
                $this->intransaction = false;
142
        }
205
        }
143
}
206
}