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 | } |