| 1 | /* ============================================================= |
| 2 | * SmallSQL : a free Java DBMS library for the Java(tm) platform |
| 3 | * ============================================================= |
| 4 | * |
| 5 | * (C) Copyright 2004-2006, by Volker Berlin. |
| 6 | * |
| 7 | * Project Info: http://www.smallsql.de/ |
| 8 | * |
| 9 | * This library is free software; you can redistribute it and/or modify it |
| 10 | * under the terms of the GNU Lesser General Public License as published by |
| 11 | * the Free Software Foundation; either version 2.1 of the License, or |
| 12 | * (at your option) any later version. |
| 13 | * |
| 14 | * This library is distributed in the hope that it will be useful, but |
| 15 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| 16 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
| 17 | * License for more details. |
| 18 | * |
| 19 | * You should have received a copy of the GNU Lesser General Public |
| 20 | * License along with this library; if not, write to the Free Software |
| 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
| 22 | * USA. |
| 23 | * |
| 24 | * [Java is a trademark or registered trademark of Sun Microsystems, Inc. |
| 25 | * in the United States and other countries.] |
| 26 | * |
| 27 | * --------------- |
| 28 | * Database.java |
| 29 | * --------------- |
| 30 | * Author: Volker Berlin |
| 31 | * |
| 32 | */ |
| 33 | package smallsql.database; |
| 34 | |
| 35 | import java.util.*; |
| 36 | import java.io.*; |
| 37 | import java.sql.*; |
| 38 | |
| 39 | |
| 40 | final class Database { |
| 41 | |
| 42 | /* |
| 43 | Es gibt nur eine Instance dieser Klasse pro Datenbank. Sie wird geschert zwischen |
| 44 | allen Connections zu dieser Datenbank und allen Threads. Deshalb muß der Zugriff |
| 45 | auf diese Klasse Thread sicher sein. |
| 46 | |
| 47 | Hier werden vor allem Table Definitionen und Locks gespeichert. |
| 48 | */ |
| 49 | |
| 50 | static private HashMap databases = new HashMap(); |
| 51 | |
| 52 | final private HashMap tableViews = new HashMap(); |
| 53 | private String name; |
| 54 | private File directory; |
| 55 | private RandomAccessFile master; |
| 56 | final private WeakHashMap connections = new WeakHashMap(); |
| 57 | |
| 58 | |
| 59 | /** |
| 60 | * Get a instance of the Database Class. If the Datbase with the given name is not open |
| 61 | * then it will be open. |
| 62 | */ |
| 63 | static Database getDatabase( String name, SSConnection con, boolean create ) throws SQLException{ |
| 64 | if(name == null) return null; |
| 65 | if(name.startsWith("file:")) |
| 66 | name = name.substring(5); |
| 67 | if(File.separatorChar == '\\') |
| 68 | name = name.replace( '/', File.separatorChar); |
| 69 | else |
| 70 | name = name.replace( '\\', File.separatorChar); |
| 71 | synchronized(databases){ |
| 72 | Database db = (Database)databases.get(name); |
| 73 | if(db == null){ |
| 74 | if(create && !new File(name).isDirectory()){ |
| 75 | CommandCreateDatabase command = new CommandCreateDatabase(con.log,name); |
| 76 | command.execute(con,null); |
| 77 | } |
| 78 | db = new Database(name); |
| 79 | databases.put( name, db); |
| 80 | } |
| 81 | db.connections.put(con, null); |
| 82 | return db; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | |
| 87 | private static Database getDatabase(SSConnection con, String name) throws SQLException{ |
| 88 | return name == null ? |
| 89 | con.getDatabase(false) : |
| 90 | getDatabase( name, con, false ); |
| 91 | } |
| 92 | |
| 93 | |
| 94 | private Database(String name ) throws SQLException{ |
| 95 | try{ |
| 96 | this.name = name; |
| 97 | directory = new File(name); |
| 98 | if(!directory.isDirectory()){ |
| 99 | throw Utils.createSQLException("Database '" + name + "' does not exists."); |
| 100 | } |
| 101 | directory = directory.getAbsoluteFile(); |
| 102 | File file = new File( directory, Utils.MASTER_FILENAME); |
| 103 | if(!file.exists()) |
| 104 | throw Utils.createSQLException("Directory '" + name + "' is not a SmallSQL database."); |
| 105 | master = new RandomAccessFile( file, "rw"); |
| 106 | }catch(Exception e){ |
| 107 | throw Utils.createSQLException(e); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | String getName(){ |
| 112 | return name; |
| 113 | } |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | /** |
| 119 | * Remove a connection from this database. |
| 120 | */ |
| 121 | static final void closeConnection(SSConnection con) throws SQLException{ |
| 122 | synchronized(databases){ |
| 123 | Iterator iterator = databases.values().iterator(); |
| 124 | while(iterator.hasNext()){ |
| 125 | Database database = (Database)iterator.next(); |
| 126 | WeakHashMap connections = database.connections; |
| 127 | connections.remove(con); |
| 128 | if(connections.size() == 0){ |
| 129 | try { |
| 130 | database.close(); |
| 131 | } catch (Exception e) { |
| 132 | throw Utils.createSQLException(e); |
| 133 | } |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | |
| 140 | /** |
| 141 | * Close this Database and all tables and views. |
| 142 | */ |
| 143 | final void close() throws Exception{ |
| 144 | databases.remove(this.name); |
| 145 | synchronized(tableViews){ |
| 146 | Iterator iterator = tableViews.values().iterator(); |
| 147 | while(iterator.hasNext()){ |
| 148 | TableView tableView = (TableView)iterator.next(); |
| 149 | tableView.close(); |
| 150 | iterator.remove(); |
| 151 | } |
| 152 | } |
| 153 | master.close(); |
| 154 | } |
| 155 | |
| 156 | static TableView getTableView(SSConnection con, String catalog, String tableName) throws SQLException{ |
| 157 | return getDatabase( con, catalog).getTableView( con, tableName); |
| 158 | } |
| 159 | |
| 160 | |
| 161 | /** |
| 162 | * Return a TableView object. If the TableView object is not loaded then it load it. |
| 163 | * @param con |
| 164 | * @param tableName |
| 165 | * @return ever a valid TableView object and never null. |
| 166 | * @throws SQLException if the table or view does not exists |
| 167 | */ |
| 168 | TableView getTableView(SSConnection con, String tableName) throws SQLException{ |
| 169 | synchronized(tableViews){ |
| 170 | TableView tableView = (TableView)tableViews.get(tableName); |
| 171 | if(tableView == null){ |
| 172 | // FIXME hier sollt nur die eine Tabelle gelockt sein und nicht alle Tabellen, Tabelle lesen sollte auserhalb des globalen syn sein. |
| 173 | tableView = TableView.load(con, this, tableName); |
| 174 | tableViews.put( tableName, tableView); |
| 175 | } |
| 176 | return tableView; |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | |
| 181 | static void dropTable(SSConnection con, String catalog, String tableName) throws Exception{ |
| 182 | getDatabase( con, catalog).dropTable( con, tableName); |
| 183 | } |
| 184 | |
| 185 | |
| 186 | void dropTable(SSConnection con, String tableName) throws Exception{ |
| 187 | synchronized(tableViews){ |
| 188 | Table table = (Table)tableViews.get( tableName ); |
| 189 | if(table != null){ |
| 190 | tableViews.remove( tableName ); |
| 191 | table.drop(con); |
| 192 | }else{ |
| 193 | Table.drop( this, tableName ); |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | |
| 199 | static void dropView(SSConnection con, String catalog, String tableName) throws Exception{ |
| 200 | getDatabase( con, catalog).dropView(tableName); |
| 201 | } |
| 202 | |
| 203 | |
| 204 | void dropView(String viewName) throws Exception{ |
| 205 | synchronized(tableViews){ |
| 206 | Object view = tableViews.remove( viewName ); |
| 207 | if(view != null && !(view instanceof View)) |
| 208 | throw Utils.createSQLException("Cannot use DROP VIEW with '" + viewName + "' because it does not is a view."); |
| 209 | |
| 210 | View.drop( this, viewName ); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | |
| 215 | /** |
| 216 | * @param con current Connections |
| 217 | * @param name the name of the new Table |
| 218 | * @param columns the column descriptions of the table |
| 219 | * @param indexes the indexes of the new table |
| 220 | * @param foreignKeys |
| 221 | * @throws Exception |
| 222 | */ |
| 223 | void createTable(SSConnection con, String name, Columns columns, IndexDescriptions indexes, ForeignKeys foreignKeys) throws Exception{ |
| 224 | // createFile() can run only one Thread success (it is atomic) |
| 225 | // Thats the create of the Table does not need in the Synchronized. |
| 226 | for(int i=0; i<foreignKeys.size(); i++){ |
| 227 | ForeignKey foreignKey = foreignKeys.get(i); |
| 228 | TableView pkTable = getTableView(con, foreignKey.pkTable); |
| 229 | if(!(pkTable instanceof Table)){ |
| 230 | throw Utils.createSQLException("'" + foreignKey.pkTable +"' is not a table."); |
| 231 | } |
| 232 | } |
| 233 | Table table = new Table( this, con, name, columns, indexes, foreignKeys); |
| 234 | synchronized(tableViews){ |
| 235 | tableViews.put( name, table); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | |
| 240 | void createView(String name, String sql) throws Exception{ |
| 241 | // createFile() can run only one Thread success (it is atomic) |
| 242 | // Thats the create of the View does not need in the Synchronized. |
| 243 | new View( this, name, sql); |
| 244 | } |
| 245 | |
| 246 | |
| 247 | /** |
| 248 | * Create a list of all available Databases from the point of the current |
| 249 | * Database or current working directory |
| 250 | * @param database - current database |
| 251 | * @return |
| 252 | */ |
| 253 | static Object[][] getCatalogs(Database database){ |
| 254 | List catalogs = new ArrayList(); |
| 255 | File baseDir = (database != null) ? |
| 256 | database.directory.getParentFile() : |
| 257 | new File("."); |
| 258 | File dirs[] = baseDir.listFiles(); |
| 259 | if(dirs != null) |
| 260 | for(int i=0; i<dirs.length; i++){ |
| 261 | if(dirs[i].isDirectory()){ |
| 262 | if(new File(dirs[i], Utils.MASTER_FILENAME).exists()){ |
| 263 | Object[] catalog = new Object[1]; |
| 264 | catalog[0] = dirs[i].getPath(); |
| 265 | catalogs.add(catalog); |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | Object[][] result = new Object[catalogs.size()][]; |
| 270 | catalogs.toArray(result); |
| 271 | return result; |
| 272 | } |
| 273 | |
| 274 | |
| 275 | Strings getTables(String tablePattern){ |
| 276 | Strings list = new Strings(); |
| 277 | File dirs[] = directory.listFiles(); |
| 278 | if(dirs != null) |
| 279 | if(tablePattern == null) tablePattern = "%"; |
| 280 | tablePattern += Utils.TABLE_VIEW_EXTENTION; |
| 281 | for(int i=0; i<dirs.length; i++){ |
| 282 | String name = dirs[i].getName(); |
| 283 | if(Utils.like(name, tablePattern)){ |
| 284 | list.add(name.substring( 0, name.length()-Utils.TABLE_VIEW_EXTENTION.length() )); |
| 285 | } |
| 286 | } |
| 287 | return list; |
| 288 | } |
| 289 | |
| 290 | |
| 291 | Object[][] getColumns( SSConnection con, String tablePattern, String colPattern) throws Exception{ |
| 292 | List rows = new ArrayList(); |
| 293 | Strings tables = getTables(tablePattern); |
| 294 | for(int i=0; i<tables.size(); i++){ |
| 295 | String tableName = tables.get(i); |
| 296 | try{ |
| 297 | TableView tab = getTableView( con, tableName); |
| 298 | Columns cols = tab.columns; |
| 299 | for(int c=0; c<cols.size(); c++){ |
| 300 | Column col = cols.get(c); |
| 301 | Object[] row = new Object[18]; |
| 302 | row[0] = getName(); //TABLE_CAT |
| 303 | //TABLE_SCHEM |
| 304 | row[2] = tableName; //TABLE_NAME |
| 305 | row[3] = col.getName(); //COLUMN_NAME |
| 306 | row[4] = Utils.getShort( SQLTokenizer.getSQLDataType( col.getDataType() )); //DATA_TYPE |
| 307 | row[5] = SQLTokenizer.getKeyWord( col.getDataType() ); //TYPE_NAME |
| 308 | row[6] = Utils.getInteger(col.getColumnSize());//COLUMN_SIZE |
| 309 | //BUFFER_LENGTH |
| 310 | row[8] = Utils.getInteger(col.getScale());//DECIMAL_DIGITS |
| 311 | row[9] = Utils.getInteger(10); //NUM_PREC_RADIX |
| 312 | row[10]= Utils.getInteger(col.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls); //NULLABLE |
| 313 | //REMARKS |
| 314 | row[12]= col.getDefaultDefinition(); //COLUMN_DEF |
| 315 | //SQL_DATA_TYPE |
| 316 | //SQL_DATETIME_SUB |
| 317 | row[15]= row[6]; //CHAR_OCTET_LENGTH |
| 318 | row[16]= Utils.getInteger(i); //ORDINAL_POSITION |
| 319 | row[17]= col.isNullable() ? "YES" : "NO"; //IS_NULLABLE |
| 320 | rows.add(row); |
| 321 | } |
| 322 | }catch(Exception e){ |
| 323 | //invalid Tables and View will not show |
| 324 | } |
| 325 | } |
| 326 | Object[][] result = new Object[rows.size()][]; |
| 327 | rows.toArray(result); |
| 328 | return result; |
| 329 | } |
| 330 | |
| 331 | |
| 332 | Object[][] getReferenceKeys(SSConnection con, String pkTable, String fkTable) throws SQLException{ |
| 333 | List rows = new ArrayList(); |
| 334 | Strings tables = (pkTable != null) ? getTables(pkTable) : getTables(fkTable); |
| 335 | for(int t=0; t<tables.size(); t++){ |
| 336 | String tableName = tables.get(t); |
| 337 | TableView tab = getTableView( con, tableName); |
| 338 | if(!(tab instanceof Table)) continue; |
| 339 | ForeignKeys references = ((Table)tab).references; |
| 340 | for(int i=0; i<references.size(); i++){ |
| 341 | ForeignKey foreignKey = references.get(i); |
| 342 | IndexDescription pk = foreignKey.pk; |
| 343 | IndexDescription fk = foreignKey.fk; |
| 344 | if((pkTable == null || pkTable.equals(foreignKey.pkTable)) && |
| 345 | (fkTable == null || fkTable.equals(foreignKey.fkTable))){ |
| 346 | Strings columnsPk = pk.getColumns(); |
| 347 | Strings columnsFk = fk.getColumns(); |
| 348 | for(int c=0; c<columnsPk.size(); c++){ |
| 349 | Object[] row = new Object[14]; |
| 350 | row[0] = getName(); //PKTABLE_CAT |
| 351 | //PKTABLE_SCHEM |
| 352 | row[2] = foreignKey.pkTable; //PKTABLE_NAME |
| 353 | row[3] = columnsPk.get(c); //PKCOLUMN_NAME |
| 354 | row[4] = getName(); //FKTABLE_CAT |
| 355 | //FKTABLE_SCHEM |
| 356 | row[6] = foreignKey.fkTable; //FKTABLE_NAME |
| 357 | row[7] = columnsFk.get(c); //FKCOLUMN_NAME |
| 358 | row[8] = Utils.getShort(c+1); //KEY_SEQ |
| 359 | row[9] = Utils.getShort(foreignKey.updateRule);//UPDATE_RULE |
| 360 | row[10]= Utils.getShort(foreignKey.deleteRule); //DELETE_RULE |
| 361 | row[11]= fk.getName(); //FK_NAME |
| 362 | row[12]= pk.getName(); //PK_NAME |
| 363 | row[13]= Utils.getShort(DatabaseMetaData.importedKeyNotDeferrable); //DEFERRABILITY |
| 364 | rows.add(row); |
| 365 | } |
| 366 | } |
| 367 | } |
| 368 | } |
| 369 | Object[][] result = new Object[rows.size()][]; |
| 370 | rows.toArray(result); |
| 371 | return result; |
| 372 | } |
| 373 | |
| 374 | |
| 375 | Object[][] getBestRowIdentifier(SSConnection con, String table) throws SQLException{ |
| 376 | List rows = new ArrayList(); |
| 377 | Strings tables = getTables(table); |
| 378 | for(int t=0; t<tables.size(); t++){ |
| 379 | String tableName = tables.get(t); |
| 380 | TableView tab = getTableView( con, tableName); |
| 381 | if(!(tab instanceof Table)) continue; |
| 382 | IndexDescriptions indexes = ((Table)tab).indexes; |
| 383 | for(int i=0; i<indexes.size(); i++){ |
| 384 | IndexDescription index = indexes.get(i); |
| 385 | if(index.isUnique()){ |
| 386 | Strings columns = index.getColumns(); |
| 387 | for(int c=0; c<columns.size(); c++){ |
| 388 | String columnName = columns.get(c); |
| 389 | Column column = tab.findColumn(columnName); |
| 390 | Object[] row = new Object[8]; |
| 391 | row[0] = Utils.getShort(DatabaseMetaData.bestRowSession);//SCOPE |
| 392 | row[1] = columnName; //COLUMN_NAME |
| 393 | final int dataType = column.getDataType(); |
| 394 | row[2] = Utils.getInteger(dataType);//DATA_TYPE |
| 395 | row[3] = SQLTokenizer.getKeyWord(dataType);//TYPE_NAME |
| 396 | row[4] = Utils.getInteger(column.getPrecision()); //COLUMN_SIZE |
| 397 | //BUFFER_LENGTH |
| 398 | row[6] = Utils.getShort(column.getScale()); //DECIMAL_DIGITS |
| 399 | row[7] = Utils.getShort(DatabaseMetaData.bestRowNotPseudo);//PSEUDO_COLUMN |
| 400 | rows.add(row); |
| 401 | } |
| 402 | } |
| 403 | } |
| 404 | } |
| 405 | Object[][] result = new Object[rows.size()][]; |
| 406 | rows.toArray(result); |
| 407 | return result; |
| 408 | } |
| 409 | |
| 410 | |
| 411 | Object[][] getPrimaryKeys(SSConnection con, String table) throws SQLException{ |
| 412 | List rows = new ArrayList(); |
| 413 | Strings tables = getTables(table); |
| 414 | for(int t=0; t<tables.size(); t++){ |
| 415 | String tableName = tables.get(t); |
| 416 | TableView tab = getTableView( con, tableName); |
| 417 | if(!(tab instanceof Table)) continue; |
| 418 | IndexDescriptions indexes = ((Table)tab).indexes; |
| 419 | for(int i=0; i<indexes.size(); i++){ |
| 420 | IndexDescription index = indexes.get(i); |
| 421 | if(index.isPrimary()){ |
| 422 | Strings columns = index.getColumns(); |
| 423 | for(int c=0; c<columns.size(); c++){ |
| 424 | Object[] row = new Object[6]; |
| 425 | row[0] = getName(); //TABLE_CAT |
| 426 | //TABLE_SCHEM |
| 427 | row[2] = tableName; //TABLE_NAME |
| 428 | row[3] = columns.get(c); //COLUMN_NAME |
| 429 | row[4] = Utils.getShort(c+1); //KEY_SEQ |
| 430 | row[5] = index.getName(); //PK_NAME |
| 431 | rows.add(row); |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | } |
| 436 | Object[][] result = new Object[rows.size()][]; |
| 437 | rows.toArray(result); |
| 438 | return result; |
| 439 | } |
| 440 | |
| 441 | |
| 442 | Object[][] getIndexInfo( SSConnection con, String table, boolean unique) throws SQLException { |
| 443 | List rows = new ArrayList(); |
| 444 | Strings tables = getTables(table); |
| 445 | Short type = Utils.getShort( DatabaseMetaData.tableIndexOther ); |
| 446 | for(int t=0; t<tables.size(); t++){ |
| 447 | String tableName = tables.get(t); |
| 448 | TableView tab = getTableView( con, tableName); |
| 449 | if(!(tab instanceof Table)) continue; |
| 450 | IndexDescriptions indexes = ((Table)tab).indexes; |
| 451 | for(int i=0; i<indexes.size(); i++){ |
| 452 | IndexDescription index = indexes.get(i); |
| 453 | Strings columns = index.getColumns(); |
| 454 | for(int c=0; c<columns.size(); c++){ |
| 455 | Object[] row = new Object[13]; |
| 456 | row[0] = getName(); //TABLE_CAT |
| 457 | //TABLE_SCHEM |
| 458 | row[2] = tableName; //TABLE_NAME |
| 459 | row[3] = Boolean.valueOf(!index.isUnique());//NON_UNIQUE |
| 460 | //INDEX_QUALIFIER |
| 461 | row[5] = index.getName(); //INDEX_NAME |
| 462 | row[6] = type; //TYPE |
| 463 | row[7] = Utils.getShort(c+1); //ORDINAL_POSITION |
| 464 | row[8] = columns.get(c); //COLUMN_NAME |
| 465 | //ASC_OR_DESC |
| 466 | //CARDINALITY |
| 467 | //PAGES |
| 468 | //FILTER_CONDITION |
| 469 | rows.add(row); |
| 470 | } |
| 471 | } |
| 472 | } |
| 473 | Object[][] result = new Object[rows.size()][]; |
| 474 | rows.toArray(result); |
| 475 | return result; |
| 476 | } |
| 477 | } |