KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > quercus > lib > db > PDO


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.quercus.lib.db;
31
32 import com.caucho.quercus.UnimplementedException;
33 import com.caucho.quercus.annotation.Optional;
34 import com.caucho.quercus.annotation.ReturnNullAsFalse;
35 import com.caucho.quercus.env.ArrayValue;
36 import com.caucho.quercus.env.BooleanValue;
37 import com.caucho.quercus.env.Env;
38 import com.caucho.quercus.env.LongValue;
39 import com.caucho.quercus.env.StringValue;
40 import com.caucho.quercus.env.Value;
41 import com.caucho.util.L10N;
42
43 import javax.naming.Context JavaDoc;
44 import javax.naming.InitialContext JavaDoc;
45 import javax.naming.NamingException JavaDoc;
46 import javax.sql.DataSource JavaDoc;
47 import java.sql.Connection JavaDoc;
48 import java.sql.ResultSet JavaDoc;
49 import java.sql.SQLException JavaDoc;
50 import java.sql.Statement JavaDoc;
51 import java.util.HashMap JavaDoc;
52 import java.util.logging.Level JavaDoc;
53 import java.util.logging.Logger JavaDoc;
54
55 /**
56  * PDO object oriented API facade.
57  */

58 public class PDO implements java.io.Closeable JavaDoc {
59   private static final Logger JavaDoc log = Logger.getLogger(PDO.class.getName());
60   private static final L10N L = new L10N(PDO.class);
61
62   public static final int ATTR_AUTOCOMMIT = 0;
63   public static final int ATTR_PREFETCH = 1;
64   public static final int ATTR_TIMEOUT = 2;
65   public static final int ATTR_ERRMODE = 3;
66   public static final int ATTR_SERVER_VERSION = 4;
67   public static final int ATTR_CLIENT_VERSION = 5;
68   public static final int ATTR_SERVER_INFO = 6;
69   public static final int ATTR_CONNECTION_STATUS = 7;
70   public static final int ATTR_CASE = 8;
71   public static final int ATTR_CURSOR_NAME = 9;
72   public static final int ATTR_CURSOR = 10;
73   public static final int ATTR_ORACLE_NULLS = 11;
74   public static final int ATTR_PERSISTENT = 12;
75   public static final int ATTR_STATEMENT_CLASS = 13;
76   public static final int ATTR_FETCH_TABLE_NAMES = 14;
77   public static final int ATTR_FETCH_CATALOG_NAMES = 15;
78   public static final int ATTR_DRIVER_NAME = 16;
79   public static final int ATTR_STRINGIFY_FETCHES = 17;
80   public static final int ATTR_MAX_COLUMN_LEN = 18;
81
82   public static final int CASE_NATURAL = 0;
83   public static final int CASE_UPPER = 1;
84   public static final int CASE_LOWER = 2;
85
86   public static final int CURSOR_FWDONLY = 0;
87   public static final int CURSOR_SCROLL = 1;
88
89   public static final String JavaDoc ERR_NONE = "00000";
90
91   public static final int ERRMODE_SILENT = 0;
92   public static final int ERRMODE_WARNING = 1;
93   public static final int ERRMODE_EXCEPTION = 2;
94
95   public static final int FETCH_LAZY = 1;
96   public static final int FETCH_ASSOC = 2;
97   public static final int FETCH_NUM = 3;
98   public static final int FETCH_BOTH = 4;
99   public static final int FETCH_OBJ = 5;
100   public static final int FETCH_BOUND = 6;
101   public static final int FETCH_COLUMN = 7;
102   public static final int FETCH_CLASS = 8;
103   public static final int FETCH_INTO = 9;
104   public static final int FETCH_FUNC = 10;
105   public static final int FETCH_NAMED = 11;
106
107   public static final int FETCH_GROUP = 0x00010000;
108   public static final int FETCH_UNIQUE = 0x00030000;
109   public static final int FETCH_CLASSTYPE = 0x00040000;
110   public static final int FETCH_SERIALIZE = 0x00080000;
111
112   public static final int FETCH_ORI_NEXT = 0;
113   public static final int FETCH_ORI_PRIOR = 1;
114   public static final int FETCH_ORI_FIRST = 2;
115   public static final int FETCH_ORI_LAST = 3;
116   public static final int FETCH_ORI_ABS = 4;
117   public static final int FETCH_ORI_REL = 5;
118
119   public static final int NULL_NATURAL = 0;
120   public static final int NULL_EMPTY_STRING = 1;
121   public static final int NULL_TO_STRING = 2;
122
123   public static final int PARAM_NULL = 0;
124   public static final int PARAM_INT = 1;
125   public static final int PARAM_STR = 2;
126   public static final int PARAM_LOB = 3;
127   public static final int PARAM_STMT = 4;
128   public static final int PARAM_BOOL = 5;
129
130   public static final int PARAM_INPUT_OUTPUT = 0x80000000;
131
132   private final Env _env;
133   private final String JavaDoc _dsn;
134   private String JavaDoc _user;
135   private String JavaDoc _password;
136
137   private final PDOError _error;
138
139   private Connection JavaDoc _conn;
140
141   private Statement JavaDoc _lastStatement;
142   private PDOStatement _lastPDOStatement;
143   private String JavaDoc _lastInsertId;
144
145   private boolean _inTransaction;
146
147   public PDO(Env env,
148              String JavaDoc dsn,
149              @Optional String JavaDoc user,
150              @Optional String JavaDoc password,
151              @Optional ArrayValue options)
152   {
153     _env = env;
154     _dsn = dsn;
155     _user = user;
156     _password = password;
157     _error = new PDOError(_env);
158
159     // XXX: following would be better as annotation on destroy() method
160
_env.addClose(this);
161
162     try {
163       DataSource JavaDoc ds = getDataSource(env, dsn);
164
165       if (ds == null) {
166         env.warning(L.l("'{0}' is an unknown PDO data source.", dsn));
167       }
168       else
169         _conn = ds.getConnection(_user, _password);
170     } catch (RuntimeException JavaDoc e) {
171       throw e;
172     } catch (Exception JavaDoc e) {
173       env.warning("A link to the server could not be established.");
174       _error.error(e);
175     }
176   }
177
178   /**
179    * Starts a transaction.
180    */

181   public boolean beginTransaction()
182   {
183     if (_conn == null)
184       return false;
185
186     if (_inTransaction)
187       return false;
188
189     _inTransaction = true;
190
191     try {
192       _conn.setAutoCommit(false);
193       return true;
194     }
195     catch (SQLException JavaDoc e) {
196       _error.error(e);
197       return false;
198     }
199   }
200
201   private void closeStatements()
202   {
203     Statement JavaDoc lastStatement = _lastStatement;
204
205     _lastInsertId = null;
206     _lastStatement = null;
207     _lastPDOStatement = null;
208
209     try {
210       if (lastStatement != null)
211         lastStatement.close();
212     }
213     catch (Throwable JavaDoc t) {
214       log.log(Level.WARNING, t.toString(), t);
215     }
216
217   }
218
219   /**
220    * Commits a transaction.
221    */

222   public boolean commit()
223   {
224     if (_conn == null)
225       return false;
226
227     if (! _inTransaction)
228       return false;
229
230     _inTransaction = false;
231
232     boolean result = false;
233     try {
234       _conn.commit();
235       _conn.setAutoCommit(true);
236
237       return true;
238     }
239     catch (SQLException JavaDoc e) {
240       _error.error(e);
241     }
242
243     return result;
244   }
245
246   public void close()
247   {
248     Connection JavaDoc conn = _conn;
249
250     _conn = null;
251
252     closeStatements();
253
254     if (conn != null) {
255       try {
256         conn.close();
257       }
258       catch (SQLException JavaDoc e) {
259         log.log(Level.WARNING, e.toString(), e);
260       }
261     }
262   }
263
264   public String JavaDoc errorCode()
265   {
266     return _error.errorCode();
267   }
268
269   public ArrayValue errorInfo()
270   {
271     return _error.errorInfo();
272   }
273
274   /**
275    * Executes a statement, returning the number of rows.
276    */

277   public int exec(String JavaDoc query)
278     throws SQLException JavaDoc
279   {
280     if (_conn == null)
281       return -1;
282
283     closeStatements();
284
285     Statement JavaDoc stmt = null;
286
287     int rowCount;
288
289     try {
290       stmt = _conn.createStatement();
291       stmt.setEscapeProcessing(false);
292
293       if (stmt.execute(query)) {
294         ResultSet JavaDoc resultSet = null;
295
296         try {
297           resultSet = stmt.getResultSet();
298
299           resultSet.last();
300
301           rowCount = resultSet.getRow();
302
303           _lastStatement = stmt;
304
305           stmt = null;
306         }
307         finally {
308           try {
309             if (resultSet != null)
310               resultSet.close();
311           }
312           catch (SQLException JavaDoc e) {
313             log.log(Level.FINER, e.toString(), e);
314           }
315         }
316       }
317       else {
318         rowCount = stmt.getUpdateCount();
319
320         _lastStatement = stmt;
321
322         stmt = null;
323       }
324     } catch (SQLException JavaDoc e) {
325       _error.error(e);
326
327       return -1;
328     } finally {
329       try {
330         if (stmt != null)
331           stmt.close();
332       } catch (SQLException JavaDoc e) {
333         log.log(Level.FINER, e.toString(), e);
334       }
335     }
336
337     return rowCount;
338   }
339
340   public Value getAttribute(int attribute)
341   {
342     switch (attribute) {
343       case ATTR_AUTOCOMMIT:
344         return BooleanValue.create(getAutocommit());
345       case ATTR_CASE:
346         return LongValue.create(getCase());
347       case ATTR_CLIENT_VERSION:
348         throw new UnimplementedException();
349       case ATTR_CONNECTION_STATUS:
350         throw new UnimplementedException();
351       case ATTR_DRIVER_NAME:
352         throw new UnimplementedException();
353       case ATTR_ERRMODE:
354         return LongValue.create(_error.getErrmode());
355       case ATTR_ORACLE_NULLS:
356         return LongValue.create(getOracleNulls());
357       case ATTR_PERSISTENT:
358         return BooleanValue.create(getPersistent());
359       case ATTR_PREFETCH:
360         return LongValue.create(getPrefetch());
361       case ATTR_SERVER_INFO:
362         return StringValue.create(getServerInfo());
363       case ATTR_SERVER_VERSION:
364         return StringValue.create(getServerVersion());
365       case ATTR_TIMEOUT:
366         return LongValue.create(getTimeout());
367
368       default:
369         _error.unsupportedAttribute(attribute);
370         // XXX: check what php does
371
return BooleanValue.FALSE;
372
373     }
374   }
375
376   /**
377    * Returns the auto commit value for the connection.
378    */

379   private boolean getAutocommit()
380   {
381     if (_conn == null)
382       return true;
383
384     try {
385       return _conn.getAutoCommit();
386     }
387     catch (SQLException JavaDoc e) {
388       _error.error(e);
389       return true;
390     }
391   }
392
393   public ArrayValue getAvailableDrivers()
394   {
395     throw new UnimplementedException();
396   }
397
398   public int getCase()
399   {
400     throw new UnimplementedException();
401   }
402
403   public int getOracleNulls()
404   {
405     throw new UnimplementedException();
406   }
407
408   private boolean getPersistent()
409   {
410     return true;
411   }
412
413   private int getPrefetch()
414   {
415     throw new UnimplementedException();
416   }
417
418   private String JavaDoc getServerInfo()
419   {
420     throw new UnimplementedException();
421   }
422
423   // XXX: might be int return
424
private String JavaDoc getServerVersion()
425   {
426     throw new UnimplementedException();
427   }
428
429   private int getTimeout()
430   {
431     throw new UnimplementedException();
432   }
433
434   public String JavaDoc lastInsertId(@Optional String JavaDoc name)
435   {
436     if (!(name == null || name.length() == 0))
437       throw new UnimplementedException("lastInsertId with name");
438
439     if (_lastInsertId != null)
440       return _lastInsertId;
441
442     String JavaDoc lastInsertId = null;
443
444     if (_lastPDOStatement != null)
445       lastInsertId = _lastPDOStatement.lastInsertId(name);
446     else if (_lastStatement != null) {
447       ResultSet JavaDoc resultSet = null;
448
449       try {
450         resultSet = _lastStatement.getGeneratedKeys();
451
452         if (resultSet.next())
453           lastInsertId = resultSet.getString(1);
454       }
455       catch (SQLException JavaDoc ex) {
456         _error.error(ex);
457       }
458       finally {
459         try {
460           if (resultSet != null)
461             resultSet.close();
462         }
463         catch (SQLException JavaDoc ex) {
464           log.log(Level.WARNING, ex.toString(), ex);
465         }
466       }
467     }
468
469     _lastInsertId = lastInsertId == null ? "0" : lastInsertId;
470
471     return _lastInsertId;
472   }
473
474   /**
475    * Prepares a statement for execution.
476    */

477   @ReturnNullAsFalse
478   public PDOStatement prepare(String JavaDoc statement,
479                               @Optional ArrayValue driverOptions)
480   {
481     if (_conn == null)
482       return null;
483
484     try {
485       closeStatements();
486
487       PDOStatement pdoStatement = new PDOStatement(_env, _conn, statement, true, driverOptions);
488       _lastPDOStatement = pdoStatement;
489
490       return pdoStatement;
491     } catch (SQLException JavaDoc e) {
492       _error.error(e);
493
494       return null;
495     }
496   }
497
498   /**
499    * Queries the database
500    */

501   public Value query(String JavaDoc query)
502   {
503     if (_conn == null)
504       return BooleanValue.FALSE;
505
506     try {
507       closeStatements();
508
509       PDOStatement pdoStatement = new PDOStatement(_env, _conn, query, false, null);
510       _lastPDOStatement = pdoStatement;
511       return _env.wrapJava(pdoStatement);
512     } catch (SQLException JavaDoc e) {
513       _error.error(e);
514
515       return BooleanValue.FALSE;
516     }
517   }
518
519   /**
520    * Quotes the string
521    */

522   public String JavaDoc quote(String JavaDoc query, @Optional int parameterType)
523   {
524     return "'" + real_escape_string(query) + "'";
525   }
526
527   /**
528    * Escapes the string.
529    */

530   public String JavaDoc real_escape_string(String JavaDoc str)
531   {
532     StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
533
534     final int strLength = str.length();
535
536     for (int i = 0; i < strLength; i++) {
537       char c = str.charAt(i);
538
539       switch (c) {
540       case '\u0000':
541         buf.append('\\');
542         buf.append('\u0000');
543         break;
544       case '\n':
545         buf.append('\\');
546         buf.append('n');
547         break;
548       case '\r':
549         buf.append('\\');
550         buf.append('r');
551         break;
552       case '\\':
553         buf.append('\\');
554         buf.append('\\');
555         break;
556       case '\'':
557         buf.append('\\');
558         buf.append('\'');
559         break;
560       case '"':
561         buf.append('\\');
562         buf.append('\"');
563         break;
564       case '\032':
565         buf.append('\\');
566         buf.append('Z');
567         break;
568       default:
569         buf.append(c);
570         break;
571       }
572     }
573
574     return buf.toString();
575   }
576
577   /**
578    * Rolls a transaction back.
579    */

580   public boolean rollBack()
581   {
582     if (_conn == null)
583       return false;
584
585     if (! _inTransaction)
586       return false;
587
588     _inTransaction = false;
589
590     try {
591       _conn.rollback();
592       _conn.setAutoCommit(true);
593       return true;
594     }
595     catch (SQLException JavaDoc e) {
596       _error.error(e);
597       return false;
598     }
599   }
600
601   public boolean setAttribute(int attribute, Value value)
602   {
603     return setAttribute(attribute, value, false);
604   }
605
606   private boolean setAttribute(int attribute, Value value, boolean isInit)
607   {
608     switch (attribute) {
609       case ATTR_AUTOCOMMIT:
610         return setAutocommit(value.toBoolean());
611
612       case ATTR_ERRMODE:
613         return _error.setErrmode(value.toInt());
614
615       case ATTR_CASE:
616         return setCase(value.toInt());
617
618       case ATTR_ORACLE_NULLS:
619         return setOracleNulls(value.toInt());
620
621       case ATTR_STRINGIFY_FETCHES:
622         return setStringifyFetches(value.toBoolean());
623
624       case ATTR_STATEMENT_CLASS:
625         return setStatementClass(value);
626     }
627
628     if (isInit) {
629       switch (attribute) {
630         // XXX: there may be more of these
631
case ATTR_TIMEOUT:
632           return setTimeout(value.toInt());
633
634         case ATTR_PERSISTENT:
635           return setPersistent(value.toBoolean());
636       }
637     }
638
639     // XXX: check what PHP does
640
_error.unsupportedAttribute(attribute);
641     return false;
642   }
643
644   /**
645    * Sets the auto commit, if true commit every statement.
646    * @return true on success, false on error.
647    */

648   private boolean setAutocommit(boolean autoCommit)
649   {
650     if (_conn == null)
651       return false;
652
653     try {
654       _conn.setAutoCommit(autoCommit);
655     }
656     catch (SQLException JavaDoc e) {
657       _error.error(e);
658       return false;
659     }
660
661     return true;
662   }
663
664   /**
665    * Force column names to a specific case.
666    *
667    * <dl>
668    * <dt>{@link CASE_LOWER}
669    * <dt>{@link CASE_NATURAL}
670    * <dt>{@link CASE_UPPER}
671    * </dl>
672    */

673   private boolean setCase(int value)
674   {
675     switch (value) {
676       case CASE_LOWER:
677       case CASE_NATURAL:
678       case CASE_UPPER:
679         throw new UnimplementedException();
680
681       default:
682         _error.unsupportedAttributeValue(value);
683         return false;
684     }
685   }
686
687   /**
688    * Sets whether or not the convert nulls and empty strings, works for
689    * all drivers.
690    *
691    * <dl>
692    * <dt> {@link NULL_NATURAL}
693    * <dd> no conversion
694    * <dt> {@link NULL_EMPTY_STRING}
695    * <dd> empty string is converted to NULL
696    * <dt> {@link NULL_TO_STRING} NULL
697    * <dd> is converted to an empty string.
698    * </dl>
699    *
700    * @return true on success, false on error.
701    */

702   private boolean setOracleNulls(int value)
703   {
704     switch (value) {
705       case NULL_NATURAL:
706       case NULL_EMPTY_STRING:
707       case NULL_TO_STRING:
708         throw new UnimplementedException();
709       default:
710         _error.warning(L.l("unknown value `{0}'", value));
711         _error.unsupportedAttributeValue(value);
712         return false;
713     }
714   }
715
716   private boolean setPersistent(boolean isPersistent)
717   {
718     return true;
719   }
720
721   private boolean setPrefetch(int prefetch)
722   {
723     throw new UnimplementedException();
724   }
725
726   /**
727    * Sets a custom statement class derived from PDOStatement.
728    *
729    * @param value an array(classname, array(constructor args)).
730    *
731    * @return true on success, false on error.
732    */

733   private boolean setStatementClass(Value value)
734   {
735     throw new UnimplementedException("ATTR_STATEMENT_CLASS");
736   }
737
738   /**
739    * Convert numeric values to strings when fetching.
740    *
741    * @return true on success, false on error.
742    */

743   private boolean setStringifyFetches(boolean stringifyFetches)
744   {
745     throw new UnimplementedException();
746   }
747
748   private boolean setTimeout(int timeoutSeconds)
749   {
750     throw new UnimplementedException();
751   }
752
753   /**
754    * Opens a connection based on the dsn.
755    */

756   private DataSource JavaDoc getDataSource(Env env, String JavaDoc dsn)
757     throws Exception JavaDoc
758   {
759     if (dsn.startsWith("mysql:"))
760       return getMysqlDataSource(env, dsn);
761     else if (dsn.startsWith("java:"))
762       return getJndiDataSource(env, dsn);
763     else if (dsn.startsWith("resin:"))
764       return getResinDataSource(env, dsn);
765     else {
766       env.error(L.l("'{0}' is an unknown PDO data source.",
767                     dsn));
768
769       return null;
770     }
771   }
772
773   /**
774    * Opens a mysql connection based on the dsn.
775    */

776   private DataSource JavaDoc getMysqlDataSource(Env env, String JavaDoc dsn)
777     throws Exception JavaDoc
778   {
779     HashMap JavaDoc<String JavaDoc,String JavaDoc> attr = parseAttr(dsn, dsn.indexOf(':'));
780
781     String JavaDoc host = attr.get("host");
782     String JavaDoc port = attr.get("port");
783     String JavaDoc dbname = attr.get("dbname");
784     String JavaDoc unixSocket = attr.get("unix_socket");
785
786     if (unixSocket != null) {
787       env.error(L.l("PDO mysql: does not support unix_socket"));
788       return null;
789     }
790
791     if (host == null)
792       host = "localhost";
793
794     if (port == null)
795       port = "3306";
796
797     if (dbname == null)
798       dbname = "test";
799
800     String JavaDoc user = attr.get("user");
801     if (user != null && _user == null)
802       _user = user;
803
804     String JavaDoc password = attr.get("password");
805     if (password != null && _password == null)
806       _password = password;
807
808     String JavaDoc driver = "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource";
809
810     String JavaDoc url = "jdbc:mysql://" + host + ":" + port + "/" + dbname;
811
812     return env.getDataSource(driver, url);
813   }
814
815   /**
816    * Opens a resin connection based on the dsn.
817    */

818   private DataSource JavaDoc getResinDataSource(Env env, String JavaDoc dsn)
819     throws Exception JavaDoc
820   {
821     String JavaDoc driver = "com.caucho.db.jdbc.ConnectionPoolDataSourceImpl";
822
823     String JavaDoc url = "jdbc:" + dsn;
824
825     return env.getDataSource(driver, url);
826   }
827
828   /**
829    * Opens a connection based on the dsn.
830    */

831   private DataSource JavaDoc getJndiDataSource(Env env, String JavaDoc dsn)
832   {
833     DataSource JavaDoc ds = null;
834
835     try {
836       Context JavaDoc ic = new InitialContext JavaDoc();
837
838       ds = (DataSource JavaDoc) ic.lookup(dsn);
839     } catch (NamingException JavaDoc e) {
840       log.log(Level.FINE, e.toString(), e);
841     }
842
843     if (ds == null)
844       env.error(L.l("'{0}' is an unknown PDO JNDI data source.",
845                     dsn));
846
847     return ds;
848   }
849
850   private HashMap JavaDoc<String JavaDoc,String JavaDoc> parseAttr(String JavaDoc dsn, int i)
851   {
852     HashMap JavaDoc<String JavaDoc,String JavaDoc> attr = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
853
854     int length = dsn.length();
855
856     for (; i < length; i++) {
857       char ch = dsn.charAt(i);
858
859       if (! Character.isJavaIdentifierStart(ch))
860         continue;
861
862       StringBuilder JavaDoc name = new StringBuilder JavaDoc();
863       for (;
864            i < length && Character.isJavaIdentifierPart((ch = dsn.charAt(i)));
865            i++) {
866         name.append(ch);
867       }
868
869       for (; i < length && ((ch = dsn.charAt(i)) == ' ' || ch == '='); i++) {
870       }
871
872       StringBuilder JavaDoc value = new StringBuilder JavaDoc();
873       for (; i < length && (ch = dsn.charAt(i)) != ' ' && ch != ';'; i++) {
874         value.append(ch);
875       }
876
877       attr.put(name.toString(), value.toString());
878     }
879
880     return attr;
881   }
882
883   public String JavaDoc toString()
884   {
885     return "PDO[" + _dsn + "]";
886   }
887 }
888
Popular Tags