1 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 ; 44 import javax.naming.InitialContext ; 45 import javax.naming.NamingException ; 46 import javax.sql.DataSource ; 47 import java.sql.Connection ; 48 import java.sql.ResultSet ; 49 import java.sql.SQLException ; 50 import java.sql.Statement ; 51 import java.util.HashMap ; 52 import java.util.logging.Level ; 53 import java.util.logging.Logger ; 54 55 58 public class PDO implements java.io.Closeable { 59 private static final Logger 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 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 _dsn; 134 private String _user; 135 private String _password; 136 137 private final PDOError _error; 138 139 private Connection _conn; 140 141 private Statement _lastStatement; 142 private PDOStatement _lastPDOStatement; 143 private String _lastInsertId; 144 145 private boolean _inTransaction; 146 147 public PDO(Env env, 148 String dsn, 149 @Optional String user, 150 @Optional String 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 _env.addClose(this); 161 162 try { 163 DataSource 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 e) { 171 throw e; 172 } catch (Exception e) { 173 env.warning("A link to the server could not be established."); 174 _error.error(e); 175 } 176 } 177 178 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 e) { 196 _error.error(e); 197 return false; 198 } 199 } 200 201 private void closeStatements() 202 { 203 Statement 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 t) { 214 log.log(Level.WARNING, t.toString(), t); 215 } 216 217 } 218 219 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 e) { 240 _error.error(e); 241 } 242 243 return result; 244 } 245 246 public void close() 247 { 248 Connection conn = _conn; 249 250 _conn = null; 251 252 closeStatements(); 253 254 if (conn != null) { 255 try { 256 conn.close(); 257 } 258 catch (SQLException e) { 259 log.log(Level.WARNING, e.toString(), e); 260 } 261 } 262 } 263 264 public String errorCode() 265 { 266 return _error.errorCode(); 267 } 268 269 public ArrayValue errorInfo() 270 { 271 return _error.errorInfo(); 272 } 273 274 277 public int exec(String query) 278 throws SQLException 279 { 280 if (_conn == null) 281 return -1; 282 283 closeStatements(); 284 285 Statement 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 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 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 e) { 325 _error.error(e); 326 327 return -1; 328 } finally { 329 try { 330 if (stmt != null) 331 stmt.close(); 332 } catch (SQLException 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 return BooleanValue.FALSE; 372 373 } 374 } 375 376 379 private boolean getAutocommit() 380 { 381 if (_conn == null) 382 return true; 383 384 try { 385 return _conn.getAutoCommit(); 386 } 387 catch (SQLException 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 getServerInfo() 419 { 420 throw new UnimplementedException(); 421 } 422 423 private String getServerVersion() 425 { 426 throw new UnimplementedException(); 427 } 428 429 private int getTimeout() 430 { 431 throw new UnimplementedException(); 432 } 433 434 public String lastInsertId(@Optional String 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 lastInsertId = null; 443 444 if (_lastPDOStatement != null) 445 lastInsertId = _lastPDOStatement.lastInsertId(name); 446 else if (_lastStatement != null) { 447 ResultSet resultSet = null; 448 449 try { 450 resultSet = _lastStatement.getGeneratedKeys(); 451 452 if (resultSet.next()) 453 lastInsertId = resultSet.getString(1); 454 } 455 catch (SQLException ex) { 456 _error.error(ex); 457 } 458 finally { 459 try { 460 if (resultSet != null) 461 resultSet.close(); 462 } 463 catch (SQLException 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 477 @ReturnNullAsFalse 478 public PDOStatement prepare(String 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 e) { 492 _error.error(e); 493 494 return null; 495 } 496 } 497 498 501 public Value query(String 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 e) { 513 _error.error(e); 514 515 return BooleanValue.FALSE; 516 } 517 } 518 519 522 public String quote(String query, @Optional int parameterType) 523 { 524 return "'" + real_escape_string(query) + "'"; 525 } 526 527 530 public String real_escape_string(String str) 531 { 532 StringBuilder buf = new StringBuilder (); 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 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 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 case ATTR_TIMEOUT: 632 return setTimeout(value.toInt()); 633 634 case ATTR_PERSISTENT: 635 return setPersistent(value.toBoolean()); 636 } 637 } 638 639 _error.unsupportedAttribute(attribute); 641 return false; 642 } 643 644 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 e) { 657 _error.error(e); 658 return false; 659 } 660 661 return true; 662 } 663 664 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 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 733 private boolean setStatementClass(Value value) 734 { 735 throw new UnimplementedException("ATTR_STATEMENT_CLASS"); 736 } 737 738 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 756 private DataSource getDataSource(Env env, String dsn) 757 throws Exception 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 776 private DataSource getMysqlDataSource(Env env, String dsn) 777 throws Exception 778 { 779 HashMap <String ,String > attr = parseAttr(dsn, dsn.indexOf(':')); 780 781 String host = attr.get("host"); 782 String port = attr.get("port"); 783 String dbname = attr.get("dbname"); 784 String 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 user = attr.get("user"); 801 if (user != null && _user == null) 802 _user = user; 803 804 String password = attr.get("password"); 805 if (password != null && _password == null) 806 _password = password; 807 808 String driver = "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource"; 809 810 String url = "jdbc:mysql://" + host + ":" + port + "/" + dbname; 811 812 return env.getDataSource(driver, url); 813 } 814 815 818 private DataSource getResinDataSource(Env env, String dsn) 819 throws Exception 820 { 821 String driver = "com.caucho.db.jdbc.ConnectionPoolDataSourceImpl"; 822 823 String url = "jdbc:" + dsn; 824 825 return env.getDataSource(driver, url); 826 } 827 828 831 private DataSource getJndiDataSource(Env env, String dsn) 832 { 833 DataSource ds = null; 834 835 try { 836 Context ic = new InitialContext (); 837 838 ds = (DataSource ) ic.lookup(dsn); 839 } catch (NamingException 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 <String ,String > parseAttr(String dsn, int i) 851 { 852 HashMap <String ,String > attr = new HashMap <String ,String >(); 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 name = new StringBuilder (); 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 value = new StringBuilder (); 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 toString() 884 { 885 return "PDO[" + _dsn + "]"; 886 } 887 } 888 | Popular Tags |