/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.database.sqlite; import android.content.Context; import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import java.io.File; import java.util.ArrayList; import java.util.HashMap; public class DatabaseConnectionPoolTest extends AndroidTestCase { private static final String TAG = "DatabaseConnectionPoolTest"; private static final int MAX_CONN = 5; private static final String TEST_SQL = "select * from test where i = ? AND j = 1"; private static final String[] TEST_SQLS = new String[] { TEST_SQL, TEST_SQL + 1, TEST_SQL + 2, TEST_SQL + 3, TEST_SQL + 4 }; private SQLiteDatabase mDatabase; private File mDatabaseFile; private DatabaseConnectionPool mTestPool; @Override protected void setUp() throws Exception { super.setUp(); File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); mDatabaseFile = new File(dbDir, "database_test.db"); if (mDatabaseFile.exists()) { mDatabaseFile.delete(); } mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null); assertNotNull(mDatabase); mDatabase.execSQL("create table test (i int, j int);"); mTestPool = new DatabaseConnectionPool(mDatabase); assertNotNull(mTestPool); } @Override protected void tearDown() throws Exception { mTestPool.close(); mDatabase.close(); mDatabaseFile.delete(); super.tearDown(); } @SmallTest public void testGetAndRelease() { mTestPool.setMaxPoolSize(MAX_CONN); // connections should be lazily created. assertEquals(0, mTestPool.getSize()); // MAX pool size should be set to MAX_CONN assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // get a connection SQLiteDatabase db = mTestPool.get(TEST_SQL); // pool size should be one - since only one should be allocated for the above get() assertEquals(1, mTestPool.getSize()); assertEquals(mDatabase, db.mParentConnObj); // no free connections should be available assertEquals(0, mTestPool.getFreePoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db)); // release the connection mTestPool.release(db); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(1, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertTrue(mTestPool.isDatabaseObjFree(db)); // release the same object again and expect IllegalStateException try { mTestPool.release(db); fail("illegalStateException expected"); } catch (IllegalStateException e ) { // expected. } } /** * get all connections from the pool and ask for one more. * should get one of the connections already got so far. */ @SmallTest public void testGetAllConnAndOneMore() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); ArrayList dbObjs = new ArrayList(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQL); assertFalse(dbObjs.contains(db)); dbObjs.add(db); assertEquals(mDatabase, db.mParentConnObj); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // pool is maxed out and no free connections. ask for one more connection SQLiteDatabase db1 = mTestPool.get(TEST_SQL); // make sure db1 is one of the existing ones assertTrue(dbObjs.contains(db1)); // pool size should remain at MAX_CONN assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db1 but since it is allocated 2 times, it should still remain 'busy' mTestPool.release(db1); assertFalse(mTestPool.isDatabaseObjFree(db1)); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release all connections for (int i = 0; i < MAX_CONN; i++) { mTestPool.release(dbObjs.get(i)); } // all objects in the pool should be freed now assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } /** * same as above except that each connection has different SQL statement associated with it. */ @SmallTest public void testConnRetrievalForPreviouslySeenSql() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap dbObjs = new HashMap(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); assertFalse(dbObjs.values().contains(db)); dbObjs.put(TEST_SQLS[i], db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // pool is maxed out and no free connections. ask for one more connection // use a previously seen SQL statement String testSql = TEST_SQLS[MAX_CONN - 1]; SQLiteDatabase db1 = mTestPool.get(testSql); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // make sure db1 is one of the existing ones assertTrue(dbObjs.values().contains(db1)); assertEquals(db1, dbObjs.get(testSql)); // do the same again SQLiteDatabase db2 = mTestPool.get(testSql); // make sure db1 is one of the existing ones assertEquals(db2, dbObjs.get(testSql)); // pool size should remain at MAX_CONN assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db1 but since the same connection is allocated 3 times, // it should still remain 'busy' mTestPool.release(db1); assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql))); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release db2 but since the same connection is allocated 2 times, // it should still remain 'busy' mTestPool.release(db2); assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql))); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release all connections for (int i = 0; i < MAX_CONN; i++) { mTestPool.release(dbObjs.get(TEST_SQLS[i])); } // all objects in the pool should be freed now assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } private void executeSqlOnDatabaseConn(SQLiteDatabase db, String sql) { // get the given sql be compiled on the given database connection. // this will help DatabaseConenctionPool figure out if a given SQL statement // is already cached by a database connection. ClassToTestSqlCompilationAndCaching c = ClassToTestSqlCompilationAndCaching.create(db, sql); c.close(); } /** * get a connection for a SQL statement 'blah'. (connection_s) * make sure the pool has at least one free connection even after this get(). * and get a connection for the same SQL again. * this connection should be different from connection_s. * even though there is a connection with the given SQL pre-compiled, since is it not free * AND since the pool has free connections available, should get a new connection. */ @SmallTest public void testGetConnForTheSameSql() { mTestPool.setMaxPoolSize(MAX_CONN); SQLiteDatabase db = mTestPool.get(TEST_SQL); executeSqlOnDatabaseConn(db, TEST_SQL); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(1, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db)); SQLiteDatabase db1 = mTestPool.get(TEST_SQL); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertFalse(mTestPool.isDatabaseObjFree(db1)); assertFalse(db1.equals(db)); mTestPool.release(db); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); mTestPool.release(db1); assertEquals(2, mTestPool.getFreePoolSize()); assertEquals(2, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } /** * get the same connection N times and release it N times. * this tests DatabaseConnectionPool.PoolObj.mNumHolders */ @SmallTest public void testGetSameConnNtimesAndReleaseItNtimes() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap dbObjs = new HashMap(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); assertFalse(dbObjs.values().contains(db)); dbObjs.put(TEST_SQLS[i], db); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // every connection in the pool should have numHolders = 1 for (int i = 0; i < MAX_CONN; i ++) { assertEquals(1, mTestPool.getPool().get(i).getNumHolders()); } // pool is maxed out and no free connections. ask for one more connection // use a previously seen SQL statement String testSql = TEST_SQLS[MAX_CONN - 1]; SQLiteDatabase db1 = mTestPool.get(testSql); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // make sure db1 is one of the existing ones assertTrue(dbObjs.values().contains(db1)); assertEquals(db1, dbObjs.get(testSql)); assertFalse(mTestPool.isDatabaseObjFree(db1)); DatabaseConnectionPool.PoolObj poolObj = mTestPool.getPool().get(db1.mConnectionNum - 1); int numHolders = poolObj.getNumHolders(); assertEquals(2, numHolders); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // get the same connection N times more int N = 100; for (int i = 0; i < N; i++) { SQLiteDatabase db2 = mTestPool.get(testSql); assertEquals(db1, db2); assertFalse(mTestPool.isDatabaseObjFree(db2)); // numHolders for this object should be now up by 1 int prev = numHolders; numHolders = poolObj.getNumHolders(); assertEquals(prev + 1, numHolders); } // release it N times for (int i = 0; i < N; i++) { mTestPool.release(db1); int prev = numHolders; numHolders = poolObj.getNumHolders(); assertEquals(prev - 1, numHolders); assertFalse(mTestPool.isDatabaseObjFree(db1)); } // the connection should still have 2 more holders assertFalse(mTestPool.isDatabaseObjFree(db1)); assertEquals(2, poolObj.getNumHolders()); assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // release 2 more times mTestPool.release(db1); mTestPool.release(db1); assertEquals(0, poolObj.getNumHolders()); assertEquals(1, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); assertTrue(mTestPool.isDatabaseObjFree(db1)); } @SmallTest public void testStressTest() { mTestPool.setMaxPoolSize(MAX_CONN); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); HashMap dbMap = new HashMap(); for (int i = 0; i < MAX_CONN; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]); assertFalse(dbMap.containsKey(db)); dbMap.put(db, 1); executeSqlOnDatabaseConn(db, TEST_SQLS[i]); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // ask for lot more connections but since the pool is maxed out, we should start receiving // connections that we already got so far for (int i = MAX_CONN; i < 1000; i++) { SQLiteDatabase db = mTestPool.get(TEST_SQL + i); assertTrue(dbMap.containsKey(db)); int k = dbMap.get(db); dbMap.put(db, ++k); } assertEquals(0, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); // print the distribution of the database connection handles received, should be uniform. for (SQLiteDatabase d : dbMap.keySet()) { Log.i(TAG, "connection # " + d.mConnectionNum + ", numHolders: " + dbMap.get(d)); } // print the pool info Log.i(TAG, mTestPool.toString()); // release all for (SQLiteDatabase d : dbMap.keySet()) { int num = dbMap.get(d); for (int i = 0; i < num; i++) { mTestPool.release(d); } } assertEquals(MAX_CONN, mTestPool.getFreePoolSize()); assertEquals(MAX_CONN, mTestPool.getSize()); assertEquals(MAX_CONN, mTestPool.getMaxPoolSize()); } }