├── tests ├── sql │ ├── LoadRowsLimit.sql │ ├── LoadRowsLimitOffset.sql │ ├── LoadRowsParameterizedLimitOffset.sql │ ├── UpdateRow.sql │ ├── AddRow.sql │ ├── create │ │ ├── CreateTable_testTable.sql │ │ └── PopulateTable_testTable.sql │ └── InsertError.sql ├── events │ ├── CloseResultEvent.as │ ├── ExecuteResultEvent.as │ ├── ExecuteModifyErrorEvent.as │ └── ExecuteModifyResultEvent.as ├── utils │ └── CreateDatabase.as └── tests │ └── com │ └── probertson │ └── data │ ├── sqlRunnerClasses │ └── ConnectionPoolTest.as │ ├── SQLRunnerExecuteModifyTest.as │ ├── SQLRunnerExecuteStressTest.as │ ├── SQLRunnerExecuteModifyErrors.as │ ├── SQLRunnerExecuteModifyProgressHandlerTest.as │ ├── SQLRunnerExecuteTest.as │ └── SQLRunnerCloseTest.as ├── .gitignore ├── src └── com │ └── probertson │ └── data │ ├── sqlRunnerClasses │ ├── PendingStatementUnpooled.as │ ├── StatementCache.as │ ├── PendingStatement.as │ ├── PendingBatch.as │ └── ConnectionPool.as │ ├── Responder.as │ ├── QueuedStatement.as │ ├── SQLRunnerUnpooled.as │ └── SQLRunner.as └── readme.md /tests/sql/LoadRowsLimit.sql: -------------------------------------------------------------------------------- 1 | SELECT colString, 2 | colInt 3 | FROM main.testTable 4 | LIMIT 3 -------------------------------------------------------------------------------- /tests/sql/LoadRowsLimitOffset.sql: -------------------------------------------------------------------------------- 1 | SELECT colString, 2 | colInt 3 | FROM main.testTable 4 | LIMIT 4 OFFSET 3 -------------------------------------------------------------------------------- /tests/sql/LoadRowsParameterizedLimitOffset.sql: -------------------------------------------------------------------------------- 1 | SELECT colString, 2 | colInt 3 | FROM main.testTable 4 | LIMIT :limit OFFSET :offset -------------------------------------------------------------------------------- /tests/sql/UpdateRow.sql: -------------------------------------------------------------------------------- 1 | UPDATE main.testTable 2 | SET colString = :colString, 3 | colInt = :colInt 4 | WHERE colIntPK = :colIntPK -------------------------------------------------------------------------------- /tests/sql/AddRow.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO main.testTable 2 | ( 3 | colString, 4 | colInt 5 | ) 6 | VALUES 7 | ( 8 | :colString, 9 | :colInt 10 | ) -------------------------------------------------------------------------------- /tests/sql/create/CreateTable_testTable.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE main.testTable 2 | ( 3 | colIntPK int PRIMARY KEY AUTOINCREMENT, 4 | colString String NOT NULL, 5 | colInt int 6 | ) -------------------------------------------------------------------------------- /tests/sql/InsertError.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO main.testTable 2 | ( 3 | foo, -- doesn't exist, so throws an error 4 | colInt 5 | ) 6 | VALUES 7 | ( 8 | :colString, 9 | :colInt 10 | ) -------------------------------------------------------------------------------- /tests/sql/create/PopulateTable_testTable.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO main.testTable 2 | ( 3 | colString, 4 | colInt 5 | ) 6 | SELECT 'a', 0 UNION 7 | SELECT 'b', 1 UNION 8 | SELECT 'c', 2 UNION 9 | SELECT 'd', 3 UNION 10 | SELECT 'e', 4 UNION 11 | SELECT 'f', 5 UNION 12 | SELECT 'g', 6 UNION 13 | SELECT 'h', 7 UNION 14 | SELECT 'i', 8 UNION 15 | SELECT 'j', 9 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### 2 | # OSX 3 | #### 4 | *.DS_Store 5 | 6 | #### 7 | # FlashDevelop 8 | #### 9 | /air-sqlite.as3proj 10 | 11 | #### 12 | # FlashBuilder 13 | #### 14 | /.settings 15 | /bin 16 | /lib 17 | /.actionScriptProperties 18 | /.flexLibProperties 19 | /.project 20 | 21 | #### 22 | # FlexUnit 23 | #### 24 | /.FlexUnitSettings 25 | */FlexUnitApplication.mxml 26 | */FlexUnitApplication-app.xml -------------------------------------------------------------------------------- /tests/events/CloseResultEvent.as: -------------------------------------------------------------------------------- 1 | package events 2 | { 3 | import flash.events.Event; 4 | 5 | public class CloseResultEvent extends Event 6 | { 7 | // ------- Event type constants ------- 8 | 9 | public static const CLOSE:String = "close"; 10 | 11 | 12 | // ------- Constructor ------- 13 | 14 | public function CloseResultEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) 15 | { 16 | super(type, bubbles, cancelable); 17 | } 18 | 19 | 20 | // ------- Event overrides ------- 21 | 22 | override public function clone():Event 23 | { 24 | return new CloseResultEvent(type, bubbles, cancelable); 25 | } 26 | 27 | 28 | override public function toString():String 29 | { 30 | return formatToString("CloseResultEvent", "type", "bubbles", "cancelable", "eventPhase"); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tests/events/ExecuteResultEvent.as: -------------------------------------------------------------------------------- 1 | package events 2 | { 3 | import flash.data.SQLResult; 4 | import flash.events.Event; 5 | 6 | public class ExecuteResultEvent extends Event 7 | { 8 | // ------- Event type constants ------- 9 | 10 | public static const RESULT:String = "executeResult"; 11 | 12 | 13 | // ------- Constructor ------- 14 | 15 | public function ExecuteResultEvent(type:String, result:SQLResult, bubbles:Boolean=false, cancelable:Boolean=false) 16 | { 17 | super(type, bubbles, cancelable); 18 | this.result = result; 19 | } 20 | 21 | 22 | // ------- Public properties ------- 23 | 24 | public var result:SQLResult; 25 | 26 | 27 | // ------- Event overrides ------- 28 | 29 | public override function clone():Event 30 | { 31 | return new ExecuteResultEvent(type, result, bubbles, cancelable); 32 | } 33 | 34 | 35 | public override function toString():String 36 | { 37 | return formatToString("ExecuteResultEvent", "type", "bubbles", "cancelable", "eventPhase"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /tests/events/ExecuteModifyErrorEvent.as: -------------------------------------------------------------------------------- 1 | package events 2 | { 3 | import flash.errors.SQLError; 4 | import flash.events.Event; 5 | 6 | public class ExecuteModifyErrorEvent extends Event 7 | { 8 | // ------- Event type constants ------- 9 | 10 | public static const ERROR:String = "executeModifyError"; 11 | 12 | 13 | // ------- Constructor ------- 14 | 15 | public function ExecuteModifyErrorEvent(type:String, error:SQLError, bubbles:Boolean=false, cancelable:Boolean=false) 16 | { 17 | super(type, bubbles, cancelable); 18 | this.error = error; 19 | } 20 | 21 | 22 | // ------- Public properties ------- 23 | 24 | public var error:SQLError; 25 | 26 | 27 | // ------- Event overrides ------- 28 | 29 | public override function clone():Event 30 | { 31 | return new ExecuteModifyErrorEvent(type, error, bubbles, cancelable); 32 | } 33 | 34 | 35 | public override function toString():String 36 | { 37 | return formatToString("ExecuteModifyErrorEvent", "type", "bubbles", "cancelable", "eventPhase"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /tests/events/ExecuteModifyResultEvent.as: -------------------------------------------------------------------------------- 1 | package events 2 | { 3 | import flash.data.SQLResult; 4 | import flash.events.Event; 5 | 6 | public class ExecuteModifyResultEvent extends Event 7 | { 8 | // ------- Event type constants ------- 9 | 10 | public static const RESULT:String = "executeModifyResult"; 11 | 12 | 13 | // ------- Constructor ------- 14 | 15 | public function ExecuteModifyResultEvent(type:String, results:Vector., bubbles:Boolean=false, cancelable:Boolean=false) 16 | { 17 | super(type, bubbles, cancelable); 18 | this.results = results; 19 | } 20 | 21 | 22 | // ------- Public properties ------- 23 | 24 | public var results:Vector.; 25 | 26 | 27 | // ------- Event overrides ------- 28 | 29 | public override function clone():Event 30 | { 31 | return new ExecuteModifyResultEvent(type, results, bubbles, cancelable); 32 | } 33 | 34 | 35 | public override function toString():String 36 | { 37 | return formatToString("ExecuteModifyResultEvent", "type", "bubbles", "cancelable", "eventPhase"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /tests/utils/CreateDatabase.as: -------------------------------------------------------------------------------- 1 | package utils 2 | { 3 | import flash.data.SQLConnection; 4 | import flash.data.SQLStatement; 5 | import flash.filesystem.File; 6 | 7 | public class CreateDatabase 8 | { 9 | 10 | public function CreateDatabase(dbFile:File=null) 11 | { 12 | this.dbFile = dbFile; 13 | } 14 | 15 | 16 | // ------- Public properties ------- 17 | 18 | public var dbFile:File; 19 | 20 | 21 | // ------- Public methods ------- 22 | 23 | public function createDatabase():void 24 | { 25 | var conn:SQLConnection = new SQLConnection(); 26 | conn.open(dbFile); 27 | 28 | createTable(conn); 29 | 30 | conn.close(); 31 | } 32 | 33 | 34 | public function createPopulatedDatabase():void 35 | { 36 | var conn:SQLConnection = new SQLConnection(); 37 | conn.open(dbFile); 38 | 39 | createTable(conn); 40 | populateTable(conn); 41 | 42 | conn.close(); 43 | } 44 | 45 | 46 | // ------- Private methods ------- 47 | 48 | private function createTable(conn:SQLConnection):void 49 | { 50 | var stmt:SQLStatement = new SQLStatement(); 51 | stmt.sqlConnection = conn; 52 | stmt.text = CREATE_TABLE_SQL; 53 | stmt.execute(); 54 | } 55 | 56 | 57 | private function populateTable(conn:SQLConnection):void 58 | { 59 | var stmt:SQLStatement = new SQLStatement(); 60 | stmt.sqlConnection = conn; 61 | stmt.text = POPULATE_TABLE_SQL; 62 | stmt.execute(); 63 | } 64 | 65 | 66 | // ------- SQL statements ------- 67 | 68 | [Embed(source="sql/create/CreateTable_testTable.sql", mimeType="application/octet-stream")] 69 | private static const CreateTableStatementText:Class; 70 | private static const CREATE_TABLE_SQL:String = new CreateTableStatementText(); 71 | 72 | [Embed(source="sql/create/PopulateTable_testTable.sql", mimeType="application/octet-stream")] 73 | private static const PopulateTableStatementText:Class; 74 | private static const POPULATE_TABLE_SQL:String = new PopulateTableStatementText(); 75 | } 76 | } -------------------------------------------------------------------------------- /src/com/probertson/data/sqlRunnerClasses/PendingStatementUnpooled.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data.sqlRunnerClasses 26 | { 27 | import com.probertson.data.Responder; 28 | 29 | public class PendingStatementUnpooled 30 | { 31 | 32 | public function PendingStatementUnpooled(sql:String, parameters:Object, responder:Responder, itemClass:Class) 33 | { 34 | _sql = sql; 35 | _parameters = parameters; 36 | _responder = responder; 37 | _itemClass = itemClass; 38 | } 39 | 40 | 41 | // ------- Public properties ------- 42 | 43 | private var _sql:String; 44 | 45 | public function get sql():String 46 | { 47 | return _sql; 48 | } 49 | 50 | 51 | private var _parameters:Object; 52 | 53 | public function get parameters():Object 54 | { 55 | return _parameters; 56 | } 57 | 58 | 59 | private var _responder:Responder; 60 | 61 | public function get responder():Responder 62 | { 63 | return _responder; 64 | } 65 | 66 | 67 | private var _itemClass:Class; 68 | 69 | public function get itemClass():Class 70 | { 71 | return _itemClass; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/com/probertson/data/sqlRunnerClasses/StatementCache.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data.sqlRunnerClasses 26 | { 27 | import flash.data.SQLConnection; 28 | import flash.data.SQLStatement; 29 | import flash.utils.Dictionary; 30 | 31 | public class StatementCache 32 | { 33 | 34 | public function StatementCache(sql:String) 35 | { 36 | _sql = sql; 37 | } 38 | 39 | 40 | // ------- Member vars ------- 41 | private var _sql:String; 42 | private var _preferredConnections:Vector.; 43 | private var _cache:Dictionary; 44 | 45 | 46 | // ------- Public properties ------- 47 | public function get preferredConnections():Vector. 48 | { 49 | if (_preferredConnections == null || _preferredConnections.length == 0) 50 | { 51 | return null; 52 | } 53 | return _preferredConnections; 54 | } 55 | 56 | 57 | // ------- Public methods ------- 58 | public function getStatementForConnection(conn:SQLConnection):SQLStatement 59 | { 60 | var result:SQLStatement = null; 61 | if (_cache == null) 62 | { 63 | _cache = new Dictionary(); 64 | _preferredConnections = new Vector.(); 65 | } 66 | else 67 | { 68 | result = _cache[conn]; 69 | } 70 | 71 | if (result == null) 72 | { 73 | result = new SQLStatement(); 74 | result.sqlConnection = conn; 75 | result.text = _sql; 76 | _cache[conn] = result; 77 | _preferredConnections.push(conn); 78 | } 79 | 80 | return result; 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/com/probertson/data/Responder.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data 26 | { 27 | import flash.errors.SQLError; 28 | import flash.text.TextField; 29 | 30 | /** 31 | * A Responder object provides a container to specify a result handler 32 | * function and an error handler function in a single object. 33 | * 34 | *

Use a Responder object to specify the handler methods for the 35 | * SQLRunnerUnpooled.execute() method.

36 | * 37 | * @see SQLRunnerUnpooled#execute() 38 | */ 39 | public class Responder 40 | { 41 | /** 42 | * Creates a new Responder object. 43 | * 44 | * @param result The function that is called when statement 45 | * execution finishes correctly. 46 | * @param error The function that is called when an error 47 | * happens during statement execution. 48 | */ 49 | public function Responder(result:Function, error:Function=null) 50 | { 51 | _result = result; 52 | _error = error; 53 | } 54 | 55 | // ------- Public properties ------- 56 | 57 | private var _result:Function; 58 | 59 | /** 60 | * The function that is called when statement execution finishes correctly. 61 | */ 62 | public function get result():Function 63 | { 64 | return _result; 65 | } 66 | 67 | 68 | private var _error:Function; 69 | 70 | /** 71 | * The function that is called when an error happens during statement 72 | * execution. 73 | */ 74 | public function get error():Function 75 | { 76 | return _error; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/com/probertson/data/QueuedStatement.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data 26 | { 27 | /** 28 | * A QueuedStatement object bundles together the values representing a single 29 | * SQL statement that's executed as part of a batch of SQL statements. 30 | * 31 | * @see SQLRunner#executeModify() 32 | */ 33 | public class QueuedStatement 34 | { 35 | /** 36 | * Creates a new QueuedStatement object. 37 | * @param sql The SQL text of the statement to execute 38 | * @param parameters An object (associative array) containing the names 39 | * and values of the parameters used in the statement. 40 | * The parameter names are the property names of the 41 | * object, and the parameter values are the property 42 | * values. The parameter names in the sql 43 | * parameter should use a colon (":") prefix. 44 | */ 45 | public function QueuedStatement(sql:String, parameters:Object=null) 46 | { 47 | _statementText = sql; 48 | _parameters = parameters; 49 | } 50 | 51 | // ------- Public properties ------- 52 | 53 | private var _statementText:String; 54 | /** 55 | * The SQL text of the statement to execute 56 | */ 57 | public function get statementText():String { return _statementText; } 58 | 59 | 60 | 61 | private var _parameters:Object; 62 | /** 63 | * An object (associative array) containing the names and values of the 64 | * parameters used in the statement. 65 | */ 66 | public function get parameters():Object { return _parameters; } 67 | } 68 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | The air-sqlite project is a set of utility classes to make it easier to work with 2 | SQLite databases using Adobe AIR while following good practices for performance. 3 | 4 | Reference documentation is available here: [air-sqlite language reference "asdocs"](http://probertson.com/resources/projects/air-sqlite/asdoc/) 5 | 6 | For more information about the design philosophy see the project page here: [air-sqlite project page](http://probertson.com/projects/air-sqlite/) 7 | 8 | The primary utility is the SQLRunner class, which provides a way to execute SQL statements. 9 | The statements are executed using a pool of database connections so SELECT statements 10 | are executed at the same time (as long as database connections are available in the 11 | pool). 12 | 13 | SELECT example 14 | -------------- 15 | 16 | Here is a basic usage example for a SELECT statement, which uses the SQLRunner.execute() 17 | method: 18 | 19 | // setup code: 20 | // define database file location 21 | var dbFile:File = File.applicationStorageDirectory.resolvePath("myDatabase.db"); 22 | // create the SQLRunner 23 | var sqlRunner:SQLRunner = new SQLRunner(dbFile); 24 | 25 | // ... 26 | 27 | // run the statement, passing in one parameter (":employeeId" in the SQL) 28 | // the statement returns an Employee object as defined in the 4th parameter 29 | sqlRunner.execute(LOAD_EMPLOYEE_SQL, {employeeId:102}, resultHandler, Employee); 30 | 31 | private function resultHandler(result:SQLResult):void 32 | { 33 | var employee:Employee = result.data[0]; 34 | // do something with the employee data 35 | } 36 | 37 | // constant for actual SQL statement text 38 | [Embed(source="sql/LoadEmployee.sql", mimeType="application/octet-stream")] 39 | private static const LoadEmployeeStatementText:Class; 40 | private static const LOAD_EMPLOYEE_SQL:String = new LoadEmployeeStatementText(); 41 | 42 | The SQL statement for this example is as follows: 43 | 44 | SELECT firstName, 45 | lastName, 46 | email, 47 | phone 48 | FROM main.employees 49 | WHERE employeeId = :employeeId 50 | 51 | INSERT/UPDATE/DELETE example 52 | ---------------------------- 53 | 54 | Here is a basic example for an INSERT/UPDATE/DELETE statement. To execute those statements 55 | use the executeModify() method. The executeModify() method accepts a "batch" of statements 56 | (a Vector of QueuedStatement objects). If you pass more than one statement together in a batch, 57 | the batch executes as a single transaction. 58 | 59 | var insert:QueuedStatement = new QueuedStatement(INSERT_EMPLOYEE_SQL, {firstName:"John", lastName:"Smith"}); 60 | var update:QueuedStatement = new QueuedStatement(UPDATE_EMPLOYEE_SALARY_SQL, {employeeId:100, salary:1000}); 61 | var statementBatch:Vector. = Vector.([insert, update]); 62 | 63 | sqlRunner.executeModify(statementBatch, resultHandler, errorHandler, progressHandler); 64 | 65 | private function resultHandler(results:Vector.):void 66 | { 67 | // all operations done 68 | } 69 | 70 | private function errorHandler(error:SQLError):void 71 | { 72 | // something went wrong 73 | } 74 | 75 | private function progressHandler(numStepsComplete:uint, totalSteps:uint):void 76 | { 77 | var progressPercent:int = numStepsComplete / totalSteps; 78 | } 79 | 80 | // constants for actual SQL statement text 81 | [Embed(source="sql/InsertEmployee.sql", mimeType="application/octet-stream")] 82 | private static const InsertEmployeeStatementText:Class; 83 | private static const INSERT_EMPLOYEE_SQL:String = new InsertEmployeeStatementText(); 84 | 85 | [Embed(source="sql/UpdateEmployeeSalary.sql", mimeType="application/octet-stream")] 86 | private static const UpdateEmployeeSalaryStatementText:Class; 87 | private static const UPDATE_EMPLOYEE_SALARY_SQL:String = new UpdateEmployeeSalaryStatementText(); -------------------------------------------------------------------------------- /src/com/probertson/data/sqlRunnerClasses/PendingStatement.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data.sqlRunnerClasses 26 | { 27 | import flash.data.SQLConnection; 28 | import flash.data.SQLResult; 29 | import flash.data.SQLStatement; 30 | import flash.events.SQLErrorEvent; 31 | import flash.events.SQLEvent; 32 | 33 | public class PendingStatement 34 | { 35 | // ------- Constructor ------- 36 | 37 | public function PendingStatement(cache:StatementCache, parameters:Object, handler:Function, itemClass:Class, errorHandler:Function) 38 | { 39 | _cache = cache; 40 | _parameters = parameters; 41 | _handler = handler; 42 | _itemClass = itemClass; 43 | _errorHandler = errorHandler; 44 | } 45 | 46 | 47 | // ------- Member vars ------- 48 | 49 | private var _cache:StatementCache; 50 | private var _parameters:Object; 51 | private var _handler:Function; 52 | private var _errorHandler:Function; 53 | private var _itemClass:Class; 54 | private var _pool:ConnectionPool; 55 | 56 | 57 | // ------- Public properties ------- 58 | 59 | public function get statementCache():StatementCache { return _cache; } 60 | 61 | 62 | // ------- Public methods ------- 63 | 64 | public function executeWithConnection(pool:ConnectionPool, conn:SQLConnection):void 65 | { 66 | _pool = pool; 67 | 68 | var stmt:SQLStatement = _cache.getStatementForConnection(conn); 69 | stmt.addEventListener(SQLEvent.RESULT, stmt_result); 70 | stmt.addEventListener(SQLErrorEvent.ERROR, stmt_error); 71 | 72 | if (_itemClass != null) 73 | { 74 | stmt.itemClass = _itemClass; 75 | } 76 | 77 | stmt.clearParameters(); 78 | if (_parameters != null) 79 | { 80 | for (var prop:String in _parameters) 81 | { 82 | stmt.parameters[":" + prop] = _parameters[prop]; 83 | } 84 | } 85 | 86 | stmt.execute(); 87 | } 88 | 89 | 90 | // ------- Event handling ------- 91 | 92 | private function stmt_result(event:SQLEvent):void 93 | { 94 | var stmt:SQLStatement = event.target as SQLStatement; 95 | stmt.removeEventListener(SQLEvent.RESULT, stmt_result); 96 | stmt.removeEventListener(SQLErrorEvent.ERROR, stmt_error); 97 | var result:SQLResult = stmt.getResult(); 98 | _pool.returnConnection(stmt.sqlConnection); 99 | if (_handler != null) 100 | _handler(result); 101 | } 102 | 103 | 104 | private function stmt_error(event:SQLErrorEvent):void 105 | { 106 | var stmt:SQLStatement = event.target as SQLStatement; 107 | stmt.removeEventListener(SQLEvent.RESULT, stmt_result); 108 | stmt.removeEventListener(SQLErrorEvent.ERROR, stmt_error); 109 | _pool.returnConnection(stmt.sqlConnection); 110 | if (_errorHandler != null) 111 | _errorHandler(event.error); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/sqlRunnerClasses/ConnectionPoolTest.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data.sqlRunnerClasses 2 | { 3 | import com.probertson.data.sqlRunnerClasses.ConnectionPool; 4 | import com.probertson.data.sqlRunnerClasses.PendingBatch; 5 | import com.probertson.data.sqlRunnerClasses.PendingStatement; 6 | import com.probertson.data.sqlRunnerClasses.StatementCache; 7 | 8 | import flash.data.SQLResult; 9 | import flash.data.SQLStatement; 10 | import flash.errors.SQLError; 11 | import flash.events.Event; 12 | import flash.events.EventDispatcher; 13 | import flash.events.TimerEvent; 14 | import flash.filesystem.File; 15 | import flash.utils.Timer; 16 | 17 | import flexunit.framework.Assert; 18 | 19 | import org.flexunit.async.Async; 20 | 21 | import utils.CreateDatabase; 22 | 23 | public class ConnectionPoolTest extends EventDispatcher 24 | { 25 | // Reference declaration for class to test 26 | private var _connectionPool:ConnectionPool; 27 | 28 | 29 | // ------- Instance vars ------- 30 | 31 | private var _dbFile:File; 32 | private var _testCompleteTimer:Timer; 33 | 34 | 35 | // ------- Setup/cleanup ------- 36 | 37 | [Before] 38 | public function setUp():void 39 | { 40 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 41 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 42 | createDB.createPopulatedDatabase(); 43 | } 44 | 45 | 46 | [After(async, timeout="250")] 47 | public function tearDown():void 48 | { 49 | _connectionPool.close(_connectionPool_close, null); 50 | } 51 | 52 | 53 | private function _connectionPool_close():void 54 | { 55 | _connectionPool = null; 56 | var tempDir:File = _dbFile.parent; 57 | tempDir.deleteDirectory(true); 58 | } 59 | 60 | 61 | // ------- Tests ------- 62 | 63 | [Test(async, timeout="3000")] 64 | public function testAddBlockingBatchThenPendingStatement():void 65 | { 66 | addEventListener(Event.COMPLETE, Async.asyncHandler(this, testAddBlockingBatchThenPendingStatement_complete, 3000)); 67 | 68 | _connectionPool = new ConnectionPool(_dbFile, 5, null); 69 | 70 | _testCompleteTimer = new Timer(2000); 71 | _testCompleteTimer.addEventListener(TimerEvent.TIMER, testAddBlockingBatchThenPendingStatement_timer); 72 | _testCompleteTimer.start(); 73 | 74 | var stmt:SQLStatement = new SQLStatement(); 75 | stmt.text = ADD_ROW_SQL; 76 | var stmts:Vector. = Vector.([stmt, stmt, stmt]); 77 | var params:Vector. = Vector.([{colString:"Hello", colInt:7}, {colString:"World", colInt:17}, {colString:"Hello", colInt:7}]); 78 | var pendingBatch:PendingBatch = new PendingBatch(stmts, params, testAddBlockingBatchThenPendingStatement_batchResult, testAddBlockingBatchThenPendingStatement_batchError, null); 79 | _connectionPool.addBlockingBatch(pendingBatch); 80 | var pendingStatement:PendingStatement = new PendingStatement(new StatementCache(LOAD_ROWS_LIMIT_SQL), null, testAddBlockingBatchThenPendingStatement_executeResult, null, testAddBlockingBatchThenPendingStatement_executeError); 81 | _connectionPool.addPendingStatement(pendingStatement); 82 | } 83 | 84 | 85 | // handlers 86 | 87 | private var _executeModifyComplete:Boolean = false; 88 | private function testAddBlockingBatchThenPendingStatement_batchResult(results:Vector.):void 89 | { 90 | _executeModifyComplete = true; 91 | Assert.assertFalse(_executeComplete); 92 | } 93 | 94 | 95 | private function testAddBlockingBatchThenPendingStatement_batchError(error:SQLError):void 96 | { 97 | Assert.fail("Error during batch statement"); 98 | } 99 | 100 | private var _executeComplete:Boolean = false; 101 | private function testAddBlockingBatchThenPendingStatement_executeResult(result:SQLResult):void 102 | { 103 | _executeComplete = true; 104 | Assert.assertTrue(_executeModifyComplete); 105 | } 106 | 107 | private function testAddBlockingBatchThenPendingStatement_executeError(error:SQLError):void 108 | { 109 | Assert.fail("Error during pending statement call"); 110 | } 111 | 112 | private function testAddBlockingBatchThenPendingStatement_timer(event:TimerEvent):void 113 | { 114 | _testCompleteTimer.removeEventListener(TimerEvent.TIMER, testAddBlockingBatchThenPendingStatement_timer); 115 | _testCompleteTimer.stop(); 116 | 117 | dispatchEvent(new Event(Event.COMPLETE)); 118 | } 119 | 120 | private function testAddBlockingBatchThenPendingStatement_complete(event:Event, passThroughData:Object):void 121 | { 122 | Assert.assertTrue(_executeModifyComplete && _executeComplete); 123 | } 124 | 125 | 126 | // ------- SQL statements ------- 127 | 128 | [Embed(source="/sql/LoadRowsLimit.sql", mimeType="application/octet-stream")] 129 | private static const LoadRowsLimitStatementText:Class; 130 | private static const LOAD_ROWS_LIMIT_SQL:String = new LoadRowsLimitStatementText(); 131 | 132 | [Embed(source="/sql/AddRow.sql", mimeType="application/octet-stream")] 133 | private static const AddRowStatementText:Class; 134 | private static const ADD_ROW_SQL:String = new AddRowStatementText(); 135 | } 136 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/SQLRunnerExecuteModifyTest.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data 2 | { 3 | import com.probertson.data.QueuedStatement; 4 | import com.probertson.data.SQLRunner; 5 | import events.ExecuteModifyErrorEvent; 6 | import events.ExecuteModifyResultEvent; 7 | import flash.data.SQLConnection; 8 | import flash.data.SQLResult; 9 | import flash.data.SQLStatement; 10 | import flash.errors.SQLError; 11 | import flash.events.Event; 12 | import flash.events.EventDispatcher; 13 | import flash.filesystem.File; 14 | import flexunit.framework.Assert; 15 | import org.flexunit.async.Async; 16 | import utils.CreateDatabase; 17 | 18 | public class SQLRunnerExecuteModifyTest extends EventDispatcher 19 | { 20 | // Reference declaration for class to test 21 | private var _sqlRunner:SQLRunner; 22 | 23 | 24 | // ------- Instance vars ------- 25 | 26 | private var _dbFile:File; 27 | 28 | 29 | // ------- Setup/cleanup ------- 30 | 31 | [Before] 32 | public function setUp():void 33 | { 34 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 35 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 36 | createDB.createDatabase(); 37 | } 38 | 39 | 40 | [After(async, timeout="250")] 41 | public function tearDown():void 42 | { 43 | _sqlRunner.close(sqlRunner_close); 44 | } 45 | 46 | private function sqlRunner_close():void 47 | { 48 | _sqlRunner = null; 49 | var tempDir:File = _dbFile.parent; 50 | tempDir.deleteDirectory(true); 51 | } 52 | 53 | 54 | // ------- Tests ------- 55 | 56 | [Test(async, timeout="500")] 57 | public function testOneStatement():void 58 | { 59 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, testOneStatement_result2, 500)); 60 | 61 | _sqlRunner = new SQLRunner(_dbFile); 62 | var stmt:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 63 | _sqlRunner.executeModify(Vector.([stmt]), testOneStatement_result, testOneStatement_error); 64 | } 65 | 66 | // --- handlers --- 67 | 68 | private function testOneStatement_result(results:Vector.):void 69 | { 70 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 71 | } 72 | 73 | private function testOneStatement_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 74 | { 75 | Assert.assertEquals(1, event.results.length); 76 | Assert.assertEquals(1, event.results[0].rowsAffected); 77 | } 78 | 79 | private function testOneStatement_error(error:SQLError):void 80 | { 81 | Assert.fail(error.message); 82 | } 83 | 84 | 85 | [Test(async, timeout="500")] 86 | public function testTwoStatements():void 87 | { 88 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, testTwoStatements_result2, 500)); 89 | 90 | _sqlRunner = new SQLRunner(_dbFile); 91 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 92 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:9}); 93 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), testTwoStatements_result, testTwoStatements_error); 94 | } 95 | 96 | // --- handlers --- 97 | 98 | private function testTwoStatements_result(results:Vector.):void 99 | { 100 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 101 | } 102 | 103 | private function testTwoStatements_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 104 | { 105 | Assert.assertEquals(2, event.results.length); 106 | Assert.assertEquals(1, event.results[0].rowsAffected); 107 | Assert.assertEquals(1, event.results[1].rowsAffected); 108 | } 109 | 110 | private function testTwoStatements_error(error:SQLError):void 111 | { 112 | Assert.fail(error.message); 113 | } 114 | 115 | 116 | [Test(async, timeout="500")] 117 | public function testReuseStatement():void 118 | { 119 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, testReuseStatement_result2, 500)); 120 | 121 | _sqlRunner = new SQLRunner(_dbFile); 122 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 123 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 124 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), testReuseStatement_result, testReuseStatement_error); 125 | } 126 | 127 | // --- handlers --- 128 | 129 | private function testReuseStatement_result(results:Vector.):void 130 | { 131 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 132 | } 133 | 134 | private function testReuseStatement_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 135 | { 136 | // verify that the inserts happened 137 | Assert.assertEquals(2, event.results.length); 138 | Assert.assertEquals(1, event.results[0].rowsAffected); 139 | Assert.assertEquals(1, event.results[1].rowsAffected); 140 | 141 | // verify that the inserted data matches 142 | var id1:int = event.results[0].lastInsertRowID; 143 | var id2:int = event.results[1].lastInsertRowID; 144 | 145 | var conn:SQLConnection = new SQLConnection(); 146 | conn.open(_dbFile); 147 | 148 | var stmt:SQLStatement = new SQLStatement(); 149 | stmt.sqlConnection = conn; 150 | stmt.text = "SELECT colString, colInt FROM main.testTable WHERE colIntPK = :colIntPK"; 151 | var result:SQLResult; 152 | 153 | stmt.parameters[":colIntPK"] = id1; 154 | stmt.execute(); 155 | result = stmt.getResult(); 156 | Assert.assertEquals("Hello", result.data[0].colString); 157 | Assert.assertEquals(7, result.data[0].colInt); 158 | 159 | stmt.parameters[":colIntPK"] = id2; 160 | stmt.execute(); 161 | result = stmt.getResult(); 162 | Assert.assertEquals("World", result.data[0].colString); 163 | Assert.assertEquals(17, result.data[0].colInt); 164 | 165 | conn.close(); 166 | } 167 | 168 | private function testReuseStatement_error(error:SQLError):void 169 | { 170 | Assert.fail(error.message); 171 | } 172 | 173 | 174 | // ------- SQL statements ------- 175 | 176 | [Embed(source="/sql/AddRow.sql", mimeType="application/octet-stream")] 177 | private static const AddRowStatementText:Class; 178 | private static const ADD_ROW_SQL:String = new AddRowStatementText(); 179 | } 180 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/SQLRunnerExecuteStressTest.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data 2 | { 3 | import com.probertson.data.QueuedStatement; 4 | import com.probertson.data.SQLRunner; 5 | 6 | import events.ExecuteModifyResultEvent; 7 | 8 | import flash.data.SQLResult; 9 | import flash.errors.SQLError; 10 | import flash.events.Event; 11 | import flash.events.EventDispatcher; 12 | import flash.events.TimerEvent; 13 | import flash.filesystem.File; 14 | import flash.utils.Timer; 15 | import flash.utils.getTimer; 16 | 17 | import flexunit.framework.Assert; 18 | 19 | import org.flexunit.async.Async; 20 | 21 | import utils.CreateDatabase; 22 | 23 | public class SQLRunnerExecuteStressTest extends EventDispatcher 24 | { 25 | // Reference declaration for class to test 26 | private var _sqlRunner:SQLRunner; 27 | 28 | 29 | // ------- Instance vars ------- 30 | 31 | private var _dbFile:File; 32 | private var _totalExecutions:int = 0; 33 | private var _totalComplete:int = 0; 34 | 35 | 36 | // ------- Setup/cleanup ------- 37 | 38 | [Before] 39 | public function setUp():void 40 | { 41 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 42 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 43 | createDB.createDatabase(); 44 | 45 | _totalExecutions = 0; 46 | _totalComplete = 0; 47 | } 48 | 49 | 50 | [After(async, timeout="250")] 51 | public function tearDown():void 52 | { 53 | _sqlRunner.close(sqlRunner_close); 54 | } 55 | 56 | private function sqlRunner_close():void 57 | { 58 | _sqlRunner = null; 59 | var tempDir:File = _dbFile.parent; 60 | tempDir.deleteDirectory(true); 61 | } 62 | 63 | 64 | [BeforeClass] 65 | public static function setUpBeforeClass():void 66 | { 67 | } 68 | 69 | 70 | [AfterClass] 71 | public static function tearDownAfterClass():void 72 | { 73 | } 74 | 75 | 76 | // ------- Tests ------- 77 | 78 | [Ignore] 79 | [Test(async, timeout="500000")] 80 | public function testLongRunning():void 81 | { 82 | addEventListener(Event.COMPLETE, Async.asyncHandler(this, testLongRunning_result2, 500000)); 83 | 84 | _sqlRunner = new SQLRunner(_dbFile); 85 | 86 | _numTimersComplete = 0; 87 | _latestRowId = -1; 88 | 89 | var addTimer:Timer = new Timer(600, 500); 90 | addTimer.addEventListener(TimerEvent.TIMER, _addTimer_timer); 91 | addTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _timer_timerComplete); 92 | addTimer.start(); 93 | 94 | var updateTimer:Timer = new Timer(10, 20000); 95 | updateTimer.addEventListener(TimerEvent.TIMER, _updateTimer_timer); 96 | updateTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _timer_timerComplete); 97 | updateTimer.start(); 98 | 99 | var selectTimer:Timer = new Timer(10000, 20); 100 | selectTimer.addEventListener(TimerEvent.TIMER, _selectTimer_timer); 101 | selectTimer.addEventListener(TimerEvent.TIMER_COMPLETE, _timer_timerComplete); 102 | selectTimer.start(); 103 | 104 | _progressTimer = new Timer(1000); 105 | _progressTimer.addEventListener(TimerEvent.TIMER, _progressTimer_timer); 106 | _progressTimer.start(); 107 | } 108 | 109 | private var _progressTimer:Timer; 110 | 111 | // --- handlers --- 112 | 113 | private var _latestRowId:int = -1; 114 | private var _totalAdd:int = 0; 115 | private var _totalUpdate:int = 0; 116 | private var _totalSelect:int = 0; 117 | 118 | private function _addTimer_timer(event:TimerEvent):void 119 | { 120 | var stmt:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:getRandomString(), colInt:getRandomInt()}); 121 | _sqlRunner.executeModify(Vector.([stmt]), testLongRunning_result, testLongRunning_error); 122 | _totalExecutions++; 123 | _totalAdd++; 124 | } 125 | 126 | private function _updateTimer_timer(event:TimerEvent):void 127 | { 128 | var timer:Timer = event.target as Timer; 129 | if (_latestRowId > -1) 130 | { 131 | var stmt:QueuedStatement = new QueuedStatement(UPDATE_ROW_SQL, {colIntPK:_latestRowId, colString:getRandomString(), colInt:getRandomInt()}); 132 | _sqlRunner.executeModify(Vector.([stmt]), testLongRunning_result, testLongRunning_error); 133 | _totalExecutions++; 134 | _totalUpdate++; 135 | } 136 | } 137 | 138 | private function _selectTimer_timer(event:TimerEvent):void 139 | { 140 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testLongRunning_result); 141 | _totalExecutions++; 142 | _totalSelect++; 143 | } 144 | 145 | private var _lastComplete:int = 0; 146 | private function _progressTimer_timer(event:TimerEvent):void 147 | { 148 | var time:Number = Math.round(getTimer() / 100) / 10; 149 | // trace(time, "s,", _totalAdd, "add;", _totalUpdate, "update;", _totalSelect, "select;", _totalComplete, "/", _totalExecutions, (_totalComplete - _lastComplete), "since last"); 150 | _lastComplete = _totalComplete; 151 | } 152 | 153 | private var _numTimersComplete:int = 0; 154 | 155 | private function _timer_timerComplete(event:TimerEvent):void 156 | { 157 | _numTimersComplete++; 158 | 159 | var timer:Timer = event.target as Timer; 160 | timer.removeEventListener(TimerEvent.TIMER, _addTimer_timer); 161 | timer.removeEventListener(TimerEvent.TIMER, _updateTimer_timer); 162 | timer.removeEventListener(TimerEvent.TIMER, _selectTimer_timer); 163 | timer.removeEventListener(TimerEvent.TIMER_COMPLETE, _timer_timerComplete); 164 | } 165 | 166 | private function testLongRunning_result(results:Object):void 167 | { 168 | _totalComplete++; 169 | if (results is Vector.) 170 | _latestRowId = (results as Vector.)[0].lastInsertRowID; 171 | 172 | if (_numTimersComplete == 3) 173 | { 174 | _progressTimer.stop(); 175 | _progressTimer.removeEventListener(TimerEvent.TIMER, _progressTimer_timer); 176 | dispatchEvent(new Event(Event.COMPLETE)); 177 | } 178 | } 179 | 180 | private function testLongRunning_result2(event:Event, passThroughData:Object):void 181 | { 182 | Assert.assertEquals(_totalExecutions, _totalComplete); 183 | } 184 | 185 | private function testLongRunning_error(error:SQLError):void 186 | { 187 | Assert.fail(error.message); 188 | } 189 | 190 | 191 | // ------- Utility ------- 192 | 193 | private static const ALPHABET:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 194 | 195 | private function getRandomString():String 196 | { 197 | var len:int = Math.round(Math.random() * 8) + 5; 198 | 199 | var result:String = ""; 200 | 201 | for (var i:int = 0; i < len; i++) 202 | { 203 | result += ALPHABET.charAt(Math.round(Math.random() * 25)); 204 | } 205 | 206 | return result; 207 | } 208 | 209 | 210 | private function getRandomInt():int 211 | { 212 | return Math.round(Math.random() * 100); 213 | } 214 | 215 | 216 | // ------- SQL statements ------- 217 | 218 | [Embed(source="/sql/AddRow.sql", mimeType="application/octet-stream")] 219 | private static const AddRowStatementText:Class; 220 | private static const ADD_ROW_SQL:String = new AddRowStatementText(); 221 | 222 | [Embed(source="/sql/UpdateRow.sql", mimeType="application/octet-stream")] 223 | private static const UpdateRowStatementText:Class; 224 | private static const UPDATE_ROW_SQL:String = new UpdateRowStatementText(); 225 | 226 | [Embed(source="/sql/LoadRowsLimit.sql", mimeType="application/octet-stream")] 227 | private static const LoadRowsLimitStatementText:Class; 228 | private static const LOAD_ROWS_LIMIT_SQL:String = new LoadRowsLimitStatementText(); 229 | } 230 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/SQLRunnerExecuteModifyErrors.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data 2 | { 3 | import com.probertson.data.QueuedStatement; 4 | import com.probertson.data.SQLRunner; 5 | 6 | import events.ExecuteModifyErrorEvent; 7 | import events.ExecuteResultEvent; 8 | 9 | import flash.data.SQLResult; 10 | import flash.errors.SQLError; 11 | import flash.events.Event; 12 | import flash.events.EventDispatcher; 13 | import flash.events.TimerEvent; 14 | import flash.filesystem.File; 15 | import flash.utils.Timer; 16 | 17 | import flexunit.framework.Assert; 18 | 19 | import org.flexunit.async.Async; 20 | 21 | import utils.CreateDatabase; 22 | 23 | public class SQLRunnerExecuteModifyErrors extends EventDispatcher 24 | { 25 | // Reference declaration for class to test 26 | private var _sqlRunner:SQLRunner; 27 | 28 | 29 | // ------- Instance vars ------- 30 | 31 | private var _dbFile:File; 32 | private var _errorCount:int = 0; 33 | private var _expectedErrors:int = 0; 34 | private var _delayBeforeAssert:Timer; 35 | 36 | 37 | // ------- Setup/cleanup ------- 38 | 39 | [Before] 40 | public function setUp():void 41 | { 42 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 43 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 44 | createDB.createDatabase(); 45 | 46 | _errorCount = 0; 47 | _expectedErrors = 0; 48 | _delayBeforeAssert = new Timer(2000); 49 | } 50 | 51 | 52 | [After(async, timeout="250")] 53 | public function tearDown():void 54 | { 55 | _sqlRunner.close(sqlRunner_close); 56 | } 57 | 58 | private function sqlRunner_close():void 59 | { 60 | _sqlRunner = null; 61 | var tempDir:File = _dbFile.parent; 62 | tempDir.deleteDirectory(true); 63 | } 64 | 65 | 66 | // ------- Tests ------- 67 | 68 | // ----- One statment in a batch ----- 69 | 70 | [Ignore("This test doesn't really work, since it will automatically succeed when the first error hits so it doesn't really test if multiple errors are thrown.")] 71 | [Test(async, timeout="500")] 72 | public function testOneStatementErrorThrowing():void 73 | { 74 | addEventListener(ExecuteModifyErrorEvent.ERROR, Async.asyncHandler(this, testOneStatementErrorThrowing_result2, 500)); 75 | 76 | _sqlRunner = new SQLRunner(_dbFile); 77 | var stmt:QueuedStatement = new QueuedStatement(INSERT_ERROR_SQL, {colString:"Hello", colInt:7}); 78 | _sqlRunner.executeModify(Vector.([stmt]), testOneStatementErrorThrowing_result, testOneStatementErrorThrowing_error); 79 | } 80 | 81 | // --- handlers --- 82 | 83 | private function testOneStatementErrorThrowing_error(error:SQLError):void 84 | { 85 | dispatchEvent(new ExecuteModifyErrorEvent(ExecuteModifyErrorEvent.ERROR, error)); 86 | } 87 | 88 | private function testOneStatementErrorThrowing_result(results:Vector.):void 89 | { 90 | Assert.fail("Expected an error but none occurred"); 91 | } 92 | 93 | private function testOneStatementErrorThrowing_result2(event:ExecuteModifyErrorEvent, passThroughData:Object):void 94 | { 95 | Assert.assertTrue(true); 96 | } 97 | 98 | 99 | // ----- Multiple statements in a batch ----- 100 | 101 | [Ignore("This test doesn't really work, since it will automatically succeed when the first error hits so it doesn't really test if multiple errors are thrown.")] 102 | [Test(async, timeout="500")] 103 | public function testMultipleStatementsErrorThrowing():void 104 | { 105 | addEventListener(ExecuteModifyErrorEvent.ERROR, Async.asyncHandler(this, testMultipleStatementsErrorThrowing_result2, 500)); 106 | 107 | _sqlRunner = new SQLRunner(_dbFile); 108 | var stmt:QueuedStatement = new QueuedStatement(INSERT_ERROR_SQL, {colString:"Hello", colInt:7}); 109 | var stmt2:QueuedStatement = new QueuedStatement(INSERT_ERROR_SQL, {colString:"Hello", colInt:7}); 110 | _sqlRunner.executeModify(Vector.([stmt, stmt2]), testMultipleStatementsErrorThrowing_result, testMultipleStatementsErrorThrowing_error); 111 | _expectedErrors = 1; 112 | } 113 | 114 | // --- handlers --- 115 | 116 | private function testMultipleStatementsErrorThrowing_error(error:SQLError):void 117 | { 118 | _errorCount++; 119 | 120 | if (_errorCount == _expectedErrors) 121 | dispatchEvent(new ExecuteModifyErrorEvent(ExecuteModifyErrorEvent.ERROR, error)); 122 | } 123 | 124 | private function testMultipleStatementsErrorThrowing_result(results:Vector.):void 125 | { 126 | Assert.fail("Expected an error but none occurred"); 127 | } 128 | 129 | private function testMultipleStatementsErrorThrowing_result2(event:ExecuteModifyErrorEvent, passThroughData:Object):void 130 | { 131 | Assert.assertTrue(true); 132 | } 133 | 134 | 135 | // ----- Multiple statements in a batch, followed by a SELECT ----- 136 | 137 | [Test(async, timeout="5000")] 138 | public function testMultipleStatementsPlusSelectErrorHandling():void 139 | { 140 | addEventListener(Event.COMPLETE, Async.asyncHandler(this, testMultipleStatementsPlusSelectErrorHandling_result2, 5000)); 141 | 142 | _sqlRunner = new SQLRunner(_dbFile); 143 | var stmt:QueuedStatement = new QueuedStatement(INSERT_ERROR_SQL, {colString:"Hello", colInt:7}); 144 | var stmt2:QueuedStatement = new QueuedStatement(INSERT_ERROR_SQL, {colString:"Hello", colInt:7}); 145 | _sqlRunner.executeModify(Vector.([stmt, stmt2]), testMultipleStatementsPlusSelectErrorHandling_executeModifyResult, testMultipleStatementsPlusSelectErrorHandling_error); 146 | _expectedErrors = 1; 147 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testMultipleStatementsPlusSelectErrorHandling_executeResult); 148 | 149 | _delayBeforeAssert.delay = 2000; 150 | _delayBeforeAssert.addEventListener(TimerEvent.TIMER, testMultipleStatementsPlusSelectErrorHandling_timer); 151 | _delayBeforeAssert.start(); 152 | } 153 | 154 | 155 | // --- handlers --- 156 | 157 | private function testMultipleStatementsPlusSelectErrorHandling_error(error:SQLError):void 158 | { 159 | _errorCount++; 160 | } 161 | 162 | private function testMultipleStatementsPlusSelectErrorHandling_executeModifyResult(results:Vector.):void 163 | { 164 | Assert.fail("Expected an error but none occurred"); 165 | } 166 | 167 | private var _executeComplete:Boolean = false; 168 | 169 | private function testMultipleStatementsPlusSelectErrorHandling_executeResult(result:SQLResult):void 170 | { 171 | _executeComplete = true; 172 | } 173 | 174 | private function testMultipleStatementsPlusSelectErrorHandling_timer(event:TimerEvent):void 175 | { 176 | _delayBeforeAssert.removeEventListener(TimerEvent.TIMER, testMultipleStatementsPlusSelectErrorHandling_timer); 177 | _delayBeforeAssert.stop(); 178 | 179 | dispatchEvent(new Event(Event.COMPLETE)); 180 | } 181 | 182 | private function testMultipleStatementsPlusSelectErrorHandling_result2(event:Event, passThroughData:Object):void 183 | { 184 | Assert.assertEquals(_expectedErrors, _errorCount); 185 | Assert.assertTrue(_executeComplete); 186 | } 187 | 188 | 189 | // ------- SQL statements ------- 190 | 191 | [Embed(source="/sql/InsertError.sql", mimeType="application/octet-stream")] 192 | private static const InsertErrorStatementText:Class; 193 | private static const INSERT_ERROR_SQL:String = new InsertErrorStatementText(); 194 | 195 | [Embed(source="/sql/LoadRowsLimit.sql", mimeType="application/octet-stream")] 196 | private static const LoadRowsLimitStatementText:Class; 197 | private static const LOAD_ROWS_LIMIT_SQL:String = new LoadRowsLimitStatementText(); 198 | 199 | } 200 | } -------------------------------------------------------------------------------- /src/com/probertson/data/sqlRunnerClasses/PendingBatch.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data.sqlRunnerClasses 26 | { 27 | import flash.data.SQLConnection; 28 | import flash.data.SQLResult; 29 | import flash.data.SQLStatement; 30 | import flash.data.SQLTransactionLockType; 31 | import flash.errors.SQLError; 32 | import flash.events.SQLErrorEvent; 33 | import flash.events.SQLEvent; 34 | 35 | public class PendingBatch 36 | { 37 | 38 | public function PendingBatch(batch:Vector., parameters:Vector., resultHandler:Function, errorHandler:Function, progressHandler:Function=null) 39 | { 40 | _batch = batch; 41 | _parameters = parameters; 42 | _resultHandler = resultHandler; 43 | _errorHandler = errorHandler; 44 | _progressHandler = progressHandler; 45 | } 46 | 47 | 48 | // ------- Member vars ------- 49 | 50 | private var _batch:Vector.; 51 | private var _parameters:Vector.; 52 | private var _results:Vector.; 53 | private var _resultHandler:Function; 54 | private var _errorHandler:Function; 55 | private var _progressHandler:Function; 56 | private var _pool:ConnectionPool; 57 | private var _conn:SQLConnection; 58 | private var _numStatements:int = 0; 59 | private var _statementsCompleted:int = 0; 60 | private var _numSteps:int = 0; 61 | private var _stepsCompleted:int = 0; 62 | private var _error:SQLError; 63 | 64 | 65 | // ------- Public methods ------- 66 | 67 | public function executeWithConnection(pool:ConnectionPool, connection:SQLConnection):void 68 | { 69 | if (_batch == null || _batch.length == 0) 70 | { 71 | return; 72 | } 73 | 74 | _pool = pool; 75 | _conn = connection; 76 | _conn.addEventListener(SQLErrorEvent.ERROR, conn_error); 77 | _numStatements = _numSteps = _batch.length; 78 | 79 | if (_numStatements > 1) 80 | { 81 | _numSteps += 2; // 2 additional steps for opening and finishing transaction 82 | beginTransaction(); 83 | } 84 | else 85 | { 86 | executeStatements(); 87 | } 88 | } 89 | 90 | 91 | // ------- Executing batch ------- 92 | 93 | private function beginTransaction():void 94 | { 95 | _conn.addEventListener(SQLEvent.BEGIN, conn_begin); 96 | _conn.begin(SQLTransactionLockType.IMMEDIATE); 97 | } 98 | 99 | 100 | private function conn_begin(event:SQLEvent):void 101 | { 102 | _conn.removeEventListener(SQLEvent.BEGIN, conn_begin); 103 | 104 | callProgressHandler(); 105 | 106 | executeStatements(); 107 | } 108 | 109 | 110 | private function executeStatements():void 111 | { 112 | _results = new Vector.(); 113 | executeNextStatement(); 114 | } 115 | 116 | 117 | private function executeNextStatement():void 118 | { 119 | var stmt:SQLStatement = _batch.shift(); 120 | if (stmt.sqlConnection == null) 121 | { 122 | stmt.sqlConnection = _conn; 123 | } 124 | 125 | stmt.clearParameters(); 126 | var params:Object = _parameters.shift(); 127 | if (params != null) 128 | { 129 | for (var prop:String in params) 130 | { 131 | stmt.parameters[":" + prop] = params[prop]; 132 | } 133 | } 134 | 135 | stmt.addEventListener(SQLEvent.RESULT, stmt_result); 136 | stmt.addEventListener(SQLErrorEvent.ERROR, conn_error); 137 | stmt.execute(); 138 | } 139 | 140 | 141 | private function stmt_result(event:SQLEvent):void 142 | { 143 | var stmt:SQLStatement = event.target as SQLStatement; 144 | stmt.removeEventListener(SQLEvent.RESULT, stmt_result); 145 | stmt.removeEventListener(SQLErrorEvent.ERROR, conn_error); 146 | 147 | _results[_results.length] = stmt.getResult(); 148 | 149 | _statementsCompleted++; 150 | 151 | callProgressHandler(); 152 | 153 | if (_statementsCompleted < _numStatements) 154 | { 155 | executeNextStatement(); 156 | } 157 | else 158 | { 159 | if (_numStatements > 1) 160 | { 161 | commitTransaction(); 162 | } 163 | else 164 | { 165 | finish(); 166 | } 167 | } 168 | } 169 | 170 | 171 | private function commitTransaction():void 172 | { 173 | _conn.addEventListener(SQLEvent.COMMIT, conn_commit); 174 | _conn.commit(); 175 | } 176 | 177 | 178 | private function conn_commit(event:SQLEvent):void 179 | { 180 | _conn.removeEventListener(SQLEvent.COMMIT, conn_commit); 181 | 182 | callProgressHandler(); 183 | 184 | finish(); 185 | } 186 | 187 | 188 | private function finish():void 189 | { 190 | _pool.returnConnection(_conn); 191 | 192 | if (_resultHandler != null) 193 | _resultHandler(_results); 194 | 195 | cleanUp(); 196 | } 197 | 198 | 199 | // --- Error handling --- 200 | 201 | private function conn_error(event:SQLErrorEvent):void 202 | { 203 | if (event.target is SQLStatement) 204 | { 205 | SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, stmt_result); 206 | SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, conn_error); 207 | } 208 | 209 | _error = event.error; 210 | rollbackTransaction(); 211 | } 212 | 213 | 214 | private function rollbackTransaction():void 215 | { 216 | if (_conn.inTransaction) 217 | { 218 | _conn.addEventListener(SQLEvent.ROLLBACK, conn_rollback); 219 | _conn.rollback(); 220 | } 221 | else 222 | { 223 | _finishError(); 224 | } 225 | } 226 | 227 | 228 | private function conn_rollback(event:SQLEvent):void 229 | { 230 | _conn.removeEventListener(SQLEvent.ROLLBACK, conn_rollback); 231 | _finishError(); 232 | } 233 | 234 | 235 | private function _finishError():void 236 | { 237 | _pool.returnConnection(_conn); 238 | 239 | if (_errorHandler != null) 240 | _errorHandler(_error); 241 | 242 | cleanUp(); 243 | } 244 | 245 | 246 | // --- Utility --- 247 | 248 | private function callProgressHandler():void 249 | { 250 | _stepsCompleted++; 251 | 252 | if (_progressHandler != null) 253 | { 254 | _progressHandler(_stepsCompleted, _numSteps); 255 | } 256 | } 257 | 258 | 259 | private function cleanUp():void 260 | { 261 | _conn.removeEventListener(SQLErrorEvent.ERROR, conn_error); 262 | _conn = null; 263 | _pool = null; 264 | _batch = null; 265 | _parameters = null; 266 | _results = null; 267 | _progressHandler = null; 268 | _resultHandler = null; 269 | _errorHandler = null; 270 | _error = null; 271 | } 272 | } 273 | } -------------------------------------------------------------------------------- /src/com/probertson/data/SQLRunnerUnpooled.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data 26 | { 27 | import com.probertson.data.sqlRunnerClasses.PendingStatementUnpooled; 28 | 29 | import flash.data.SQLConnection; 30 | import flash.data.SQLMode; 31 | import flash.data.SQLResult; 32 | import flash.data.SQLStatement; 33 | import flash.events.SQLErrorEvent; 34 | import flash.events.SQLEvent; 35 | import flash.filesystem.File; 36 | import flash.utils.ByteArray; 37 | 38 | /** 39 | * The SQLRunnerUnpooled class executes SQL statements against a database. 40 | * Unlike with the SQLRunner class, the SQLRunnerUnpooled class only uses 41 | * one SQLConnection instance for all operations including SELECT 42 | * statements. This minimizes the memory and processor footprint although 43 | * it increases the delay when executing multiple statements at the same time. 44 | * The SQLRunnerUnpooled class is intended to be used in situations where memory 45 | * and processor power are limited, such as with mobile devices. 46 | * 47 | *

To execute a statement, call the execute() method.

48 | * 49 | * @see SQLRunner 50 | */ 51 | public class SQLRunnerUnpooled 52 | { 53 | /** 54 | * Creates a SQLRunnerUnpooled instance. 55 | * 56 | * @param databaseFile The database to use for executing statements. 57 | */ 58 | public function SQLRunnerUnpooled(databaseFile:File, encryptionKey:ByteArray=null) 59 | { 60 | _conn = new SQLConnection(); 61 | _conn.addEventListener(SQLEvent.OPEN, conn_open); 62 | _conn.openAsync(databaseFile, SQLMode.CREATE, null, false, 1024, encryptionKey); 63 | _inUse = true; 64 | 65 | // create objects ahead of time to avoid the overhead 66 | // of checking if they're null each time execute() is called. 67 | // Other cache objects won't be needed nearly as much, so 68 | // their instantiation can be deferred. 69 | _pending = new Vector.(); 70 | _cache = new Object(); 71 | } 72 | 73 | 74 | // ------- Member vars ------- 75 | 76 | private var _conn:SQLConnection; 77 | private var _inUse:Boolean; 78 | private var _cache:Object; 79 | private var _current:PendingStatementUnpooled; 80 | private var _pending:Vector.; 81 | private var _closeHandler:Function; 82 | //private var _batchStmtCache:Object; 83 | 84 | 85 | // ------- Public methods ------- 86 | 87 | /** 88 | * Executes a SQL SELECT query asynchronously. If a SQLConnection is 89 | * available, the query begins executing immediately. Otherwise, it is added to 90 | * a queue of pending queries that are executed in request order. 91 | * 92 | * @param sql The text of the SQL statement to execute. 93 | * @param parameters An object whose properties contain the values of the parameters 94 | * that are used in executing the SQL statement. 95 | * @param responder The responder containing the callback functions that are called when the statement execution 96 | * finishes (or fails). Both functions are optional. The responder's result function should define one parameter, a SQLResult 97 | * object. When the statement is executed, the SQLResult object containing 98 | * the results of the statement execution is passed to this function. The responder's 99 | * status function should define one parameter, a SQLError object. 100 | * @param itemClass A class that has properties corresponding to the columns in the 101 | * SELECT statement. In the resulting data set, each 102 | * result row is represented as an instance of this class. 103 | * 104 | * @see Responder 105 | */ 106 | public function execute(sql:String, parameters:Object, responder:Responder, itemClass:Class=null):void 107 | { 108 | var stmtData:PendingStatementUnpooled = new PendingStatementUnpooled(sql, parameters, responder, itemClass); 109 | 110 | _pending[_pending.length] = stmtData; 111 | 112 | checkPending(); 113 | } 114 | 115 | 116 | /** 117 | * Waits until all pending statements execute, then closes all open connections to 118 | * the database. 119 | * 120 | * @param resultHandler A function that's called when connections are closed. 121 | * No argument values are passed to the function. 122 | */ 123 | public function close(resultHandler:Function):void 124 | { 125 | _closeHandler = resultHandler; 126 | checkPending(); 127 | } 128 | 129 | 130 | // ------- Pending statements ------- 131 | private function checkPending():void 132 | { 133 | // standard (read-only) statements 134 | if (_pending.length > 0) 135 | { 136 | if (!_inUse) 137 | { 138 | _current = _pending.shift(); 139 | 140 | var stmt:SQLStatement = _cache[_current.sql]; 141 | if (stmt == null) 142 | { 143 | stmt = new SQLStatement(); 144 | stmt.sqlConnection = _conn; 145 | stmt.text = _current.sql; 146 | _cache[_current.sql] = stmt; 147 | } 148 | 149 | stmt.addEventListener(SQLEvent.RESULT, stmt_result); 150 | stmt.addEventListener(SQLErrorEvent.ERROR, stmt_error); 151 | 152 | if (_current.itemClass != null) 153 | { 154 | stmt.itemClass = _current.itemClass; 155 | } 156 | 157 | stmt.clearParameters(); 158 | if (_current.parameters != null) 159 | { 160 | for (var prop:String in _current.parameters) 161 | { 162 | stmt.parameters[":" + prop] = _current.parameters[prop]; 163 | } 164 | } 165 | 166 | stmt.execute(); 167 | 168 | _inUse = true; 169 | return; 170 | } 171 | else 172 | { 173 | // The connection isn't available 174 | return; 175 | } 176 | } 177 | 178 | // if there aren't any pending requests and there is a pending close 179 | // request, close the connections 180 | if (_closeHandler != null) 181 | { 182 | _conn.addEventListener(SQLEvent.CLOSE, conn_close); 183 | _conn.close(); 184 | } 185 | } 186 | 187 | 188 | // ------- Event handling ------- 189 | 190 | private function conn_open(event:SQLEvent):void 191 | { 192 | _conn.removeEventListener(SQLEvent.OPEN, conn_open); 193 | returnConnection(); 194 | } 195 | 196 | 197 | private function stmt_result(event:SQLEvent):void 198 | { 199 | var stmt:SQLStatement = event.target as SQLStatement; 200 | stmt.removeEventListener(SQLEvent.RESULT, stmt_result); 201 | stmt.removeEventListener(SQLErrorEvent.ERROR, stmt_error); 202 | var result:SQLResult = stmt.getResult(); 203 | if (_current.responder.result != null) 204 | { 205 | _current.responder.result(result); 206 | } 207 | returnConnection(); 208 | } 209 | 210 | 211 | private function stmt_error(event:SQLErrorEvent):void 212 | { 213 | var stmt:SQLStatement = event.target as SQLStatement; 214 | stmt.removeEventListener(SQLEvent.RESULT, stmt_result); 215 | stmt.removeEventListener(SQLErrorEvent.ERROR, stmt_error); 216 | if (_current.responder.error != null) 217 | { 218 | _current.responder.error(event.error); 219 | } 220 | returnConnection(); 221 | } 222 | 223 | 224 | private function conn_close(event:SQLEvent):void 225 | { 226 | _conn.removeEventListener(SQLEvent.CLOSE, conn_close); 227 | 228 | _closeHandler(); 229 | 230 | _closeHandler = null; 231 | } 232 | 233 | 234 | // ------- Private methods ------- 235 | 236 | private function returnConnection():void 237 | { 238 | _inUse = false; 239 | checkPending(); 240 | } 241 | } 242 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/SQLRunnerExecuteModifyProgressHandlerTest.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data 2 | { 3 | import com.probertson.data.QueuedStatement; 4 | import com.probertson.data.SQLRunner; 5 | 6 | import events.ExecuteModifyResultEvent; 7 | 8 | import flash.data.SQLResult; 9 | import flash.events.EventDispatcher; 10 | import flash.filesystem.File; 11 | 12 | import flexunit.framework.Assert; 13 | 14 | import org.flexunit.async.Async; 15 | 16 | import utils.CreateDatabase; 17 | 18 | public class SQLRunnerExecuteModifyProgressHandlerTest extends EventDispatcher 19 | { 20 | // ------- Instance to test ------- 21 | 22 | private var _sqlRunner:SQLRunner; 23 | 24 | 25 | // ------- Instance vars ------- 26 | 27 | private var _dbFile:File; 28 | private var _callCount:int = 0; 29 | private var _numComplete:int = 0; 30 | 31 | 32 | // ------- Setup/Teardown ------- 33 | 34 | [Before] 35 | public function setUp():void 36 | { 37 | _callCount = 0; 38 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 39 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 40 | createDB.createDatabase(); 41 | } 42 | 43 | 44 | [After(async, timeout="250")] 45 | public function tearDown():void 46 | { 47 | _sqlRunner.close(sqlRunner_close); 48 | } 49 | 50 | private function sqlRunner_close():void 51 | { 52 | _sqlRunner = null; 53 | var tempDir:File = _dbFile.parent; 54 | tempDir.deleteDirectory(true); 55 | } 56 | 57 | 58 | // ------- Test methods ------- 59 | 60 | [Test(async, timeout="500")] 61 | public function test_withOneStatement_executeModify_callsProgressHandler_once():void 62 | { 63 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, test_withOneStatement_executeModify_callsProgressHandler_once_result2, 500)); 64 | _sqlRunner = new SQLRunner(_dbFile); 65 | var stmt:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 66 | _sqlRunner.executeModify(Vector.([stmt]), 67 | test_withOneStatement_executeModify_callsProgressHandler_once_result, 68 | null, 69 | test_withOneStatement_executeModify_callsProgressHandler_once_progress); 70 | } 71 | 72 | private function test_withOneStatement_executeModify_callsProgressHandler_once_progress(numComplete:int, total:int):void 73 | { 74 | _callCount++; 75 | } 76 | 77 | private function test_withOneStatement_executeModify_callsProgressHandler_once_result(results:Vector.):void 78 | { 79 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 80 | } 81 | 82 | private function test_withOneStatement_executeModify_callsProgressHandler_once_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 83 | { 84 | Assert.assertEquals(1, _callCount); 85 | } 86 | 87 | 88 | [Test(async, timeout="500")] 89 | public function test_withTwoStatements_executeModify_callsProgressHandler_fourTimes():void 90 | { 91 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, test_withTwoStatements_executeModify_callsProgressHandler_fourTimes_result2, 500)); 92 | _sqlRunner = new SQLRunner(_dbFile); 93 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 94 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 95 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), 96 | test_withTwoStatements_executeModify_callsProgressHandler_fourTimes_result, 97 | null, 98 | test_withTwoStatements_executeModify_callsProgressHandler_fourTimes_progress); 99 | } 100 | 101 | private function test_withTwoStatements_executeModify_callsProgressHandler_fourTimes_progress(numComplete:int, total:int):void 102 | { 103 | _callCount++; 104 | } 105 | 106 | private function test_withTwoStatements_executeModify_callsProgressHandler_fourTimes_result(results:Vector.):void 107 | { 108 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 109 | } 110 | 111 | private function test_withTwoStatements_executeModify_callsProgressHandler_fourTimes_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 112 | { 113 | Assert.assertEquals(4, _callCount); 114 | } 115 | 116 | 117 | [Test(async, timeout="500")] 118 | public function test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne():void 119 | { 120 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne_result2, 500)); 121 | _sqlRunner = new SQLRunner(_dbFile); 122 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 123 | _sqlRunner.executeModify(Vector.([stmt1]), 124 | test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne_result, 125 | null, 126 | test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne_progress); 127 | } 128 | 129 | private function test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne_progress(numComplete:int, total:int):void 130 | { 131 | if (_callCount == 0) 132 | { 133 | _numComplete = numComplete; 134 | } 135 | _callCount++; 136 | } 137 | 138 | private function test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne_result(results:Vector.):void 139 | { 140 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 141 | } 142 | 143 | private function test_withOneStatement_executeModify_callsProgressHandler_withCompleteArgumentEqualToOne_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 144 | { 145 | Assert.assertEquals(1, _numComplete); 146 | } 147 | 148 | 149 | [Test(async, timeout="500")] 150 | public function test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne():void 151 | { 152 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne_result2, 500)); 153 | _sqlRunner = new SQLRunner(_dbFile); 154 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 155 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 156 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), 157 | test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne_result, 158 | null, 159 | test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne_progress); 160 | } 161 | 162 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne_progress(numComplete:int, total:int):void 163 | { 164 | _callCount++; 165 | if (_callCount == 1) 166 | { 167 | _numComplete = numComplete; 168 | } 169 | } 170 | 171 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne_result(results:Vector.):void 172 | { 173 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 174 | } 175 | 176 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheFirstTime_withCompleteArgumentEqualToOne_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 177 | { 178 | Assert.assertEquals(1, _numComplete); 179 | } 180 | 181 | 182 | [Test(async, timeout="500")] 183 | public function test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo():void 184 | { 185 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo_result2, 500)); 186 | _sqlRunner = new SQLRunner(_dbFile); 187 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 188 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 189 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), 190 | test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo_result, 191 | null, 192 | test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo_progress); 193 | } 194 | 195 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo_progress(numComplete:int, total:int):void 196 | { 197 | _callCount++; 198 | if (_callCount == 2) 199 | { 200 | _numComplete = numComplete; 201 | } 202 | } 203 | 204 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo_result(results:Vector.):void 205 | { 206 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 207 | } 208 | 209 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheSecondTime_withCompleteArgumentEqualToTwo_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 210 | { 211 | Assert.assertEquals(2, _numComplete); 212 | } 213 | 214 | 215 | [Test(async, timeout="500")] 216 | public function test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree():void 217 | { 218 | addEventListener(ExecuteModifyResultEvent.RESULT, Async.asyncHandler(this, test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree_result2, 500)); 219 | _sqlRunner = new SQLRunner(_dbFile); 220 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 221 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 222 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), 223 | test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree_result, 224 | null, 225 | test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree_progress); 226 | } 227 | 228 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree_progress(numComplete:int, total:int):void 229 | { 230 | _callCount++; 231 | if (_callCount == 3) 232 | { 233 | _numComplete = numComplete; 234 | } 235 | } 236 | 237 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree_result(results:Vector.):void 238 | { 239 | dispatchEvent(new ExecuteModifyResultEvent(ExecuteModifyResultEvent.RESULT, results)); 240 | } 241 | 242 | private function test_withTwoStatements_executeModify_callsProgressHandlerTheThirdTime_withCompleteArgumentEqualToThree_result2(event:ExecuteModifyResultEvent, passThroughData:Object):void 243 | { 244 | Assert.assertEquals(3, _numComplete); 245 | } 246 | 247 | 248 | // ------- SQL statements ------- 249 | 250 | [Embed(source="/sql/AddRow.sql", mimeType="application/octet-stream")] 251 | private static const AddRowStatementText:Class; 252 | private static const ADD_ROW_SQL:String = new AddRowStatementText(); 253 | } 254 | } -------------------------------------------------------------------------------- /src/com/probertson/data/sqlRunnerClasses/ConnectionPool.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | Acknowledgement: 26 | Some of the implementation techniques used here were inspired by the ConnectionPool 27 | example accompanying Daniel Rinehart's article "User experience considerations 28 | with SQLite operations" (http://www.adobe.com/devnet/air/flex/articles/air_sql_operations.html). 29 | */ 30 | package com.probertson.data.sqlRunnerClasses 31 | { 32 | import flash.data.SQLConnection; 33 | import flash.data.SQLMode; 34 | import flash.errors.SQLErrorOperation; 35 | import flash.events.SQLErrorEvent; 36 | import flash.events.SQLEvent; 37 | import flash.filesystem.File; 38 | import flash.utils.ByteArray; 39 | import flash.utils.Dictionary; 40 | 41 | public class ConnectionPool 42 | { 43 | 44 | // ------- Constructor ------- 45 | 46 | public function ConnectionPool(dbFile:File, maxSize:int, encryptionKey:ByteArray) 47 | { 48 | _dbFile = dbFile; 49 | _maxSize = maxSize; 50 | _encryptionKey = encryptionKey; 51 | _available = new Vector.(); 52 | _inUse = new Dictionary(); 53 | _pending = new Vector.(); 54 | } 55 | 56 | 57 | // ------- Member vars ------- 58 | 59 | private var _dbFile:File; 60 | // Standard (pooled) connections 61 | private var _maxSize:int = 5; 62 | private var _encryptionKey:ByteArray = null; 63 | private var _available:Vector.; 64 | private var _inUse:Dictionary; 65 | private var _totalConnections:int = 0; 66 | private var _numConnectionsBeingOpened:int = 0; 67 | private var _pending:Vector.; 68 | // Batch/blocking (write/modify) connection 69 | private var _blocked:Boolean = false; 70 | private var _blockingPending:Vector.; 71 | private var _blockedPending:Vector.; 72 | private var _blockingConnection:SQLConnection; 73 | // Closing 74 | private var _pendingClose:Boolean = false; 75 | private var _pendingCloseHandler:Function; 76 | private var _pendingCloseErrorHandler:Function; 77 | private var _pendingCloseCount:int = 0; 78 | 79 | 80 | // ------- Public properties ------- 81 | 82 | public function get numConnections():int 83 | { 84 | return _totalConnections; 85 | } 86 | 87 | 88 | private var _connectionErrorHandler:Function; 89 | public function get connectionErrorHandler():Function { return _connectionErrorHandler; } 90 | public function set connectionErrorHandler(value:Function):void 91 | { 92 | if (_connectionErrorHandler == value) 93 | return; 94 | _connectionErrorHandler = value; 95 | } 96 | 97 | 98 | // ------- Public methods ------- 99 | 100 | /** 101 | * Adds a pending statement to the execution queue. If a connection is available 102 | * it is executed immediately. Otherwise statements are executed in the 103 | * order they're requested, as database connections become available. 104 | */ 105 | public function addPendingStatement(pendingStatement:PendingStatement):void 106 | { 107 | // TODO: throw an error or dispatch an event if there is a pending close 108 | if (!_blocked) 109 | { 110 | _pending.push(pendingStatement); 111 | checkPending(); 112 | } 113 | else 114 | { 115 | if (_blockedPending == null) 116 | { 117 | _blockedPending = new Vector.(); 118 | } 119 | _blockedPending.push(pendingStatement); 120 | } 121 | } 122 | 123 | 124 | /** 125 | * Requests a blocking connection -- a connection that's guaranteed to be 126 | * the only one in use at one time. This connection is appropriate to 127 | * use for executing statements that change data or the database structure, 128 | * such as INSERT, UPDATE, DELETE, 129 | * CREATE ..., ALTER ..., DROP ..., etc. 130 | */ 131 | public function addBlockingBatch(batch:PendingBatch):void 132 | { 133 | // TODO: throw an error or dispatch an event if there is a pending close 134 | if (_blockingPending == null) 135 | { 136 | _blockingPending = new Vector.(); 137 | } 138 | 139 | _blockingPending.push(batch); 140 | 141 | if (_blockingConnection == null) 142 | { 143 | _blocked = true; 144 | _blockingConnection = new SQLConnection(); 145 | _blockingConnection.addEventListener(SQLEvent.OPEN, conn_open); 146 | _blockingConnection.addEventListener(SQLErrorEvent.ERROR, conn_openError); 147 | 148 | _blockingConnection.openAsync(_dbFile, SQLMode.CREATE, null, false, 1024, _encryptionKey); 149 | } 150 | else 151 | { 152 | checkPending(); 153 | } 154 | } 155 | 156 | 157 | public function close(handler:Function, errorHandler:Function):void 158 | { 159 | _pendingClose = true; 160 | _pendingCloseHandler = handler; 161 | _pendingCloseErrorHandler = errorHandler; 162 | 163 | checkPending(); 164 | } 165 | 166 | 167 | /** 168 | * Returns a SQLConnection to the pool, indicating that it is no longer 169 | * being used and can be made available to pending or incoming connection 170 | * requests. 171 | * 172 | * @param connection The SQLConnection that is no longer in use. 173 | */ 174 | internal function returnConnection(connection:SQLConnection):void 175 | { 176 | if (connection != _blockingConnection) 177 | { 178 | _inUse[connection] = false; 179 | _available.push(connection); 180 | } 181 | else 182 | { 183 | _blocked = false; 184 | if (_blockedPending != null && _blockedPending.length > 0) 185 | { 186 | _pending = _blockedPending.splice(0, _blockedPending.length); 187 | } 188 | } 189 | 190 | checkPending(); 191 | } 192 | 193 | 194 | // ------- Pending statements ------- 195 | 196 | private function checkPending():void 197 | { 198 | // standard (read-only) statements 199 | while (_pending.length > 0) 200 | { 201 | var conn:SQLConnection; 202 | 203 | if (_available.length > 0) 204 | { 205 | var stmt:PendingStatement = _pending.shift(); 206 | 207 | if (stmt.statementCache.preferredConnections != null) 208 | { 209 | for (var i:int = 0, len:int = stmt.statementCache.preferredConnections.length; i < len; i++) 210 | { 211 | conn = stmt.statementCache.preferredConnections[i]; 212 | if (!_inUse[conn]) 213 | { 214 | var index:int = _available.indexOf(conn); 215 | _available.splice(index, 1); 216 | break; 217 | } 218 | else 219 | { 220 | conn = null; 221 | } 222 | } 223 | } 224 | 225 | if (conn == null) 226 | { 227 | conn = _available.shift(); 228 | } 229 | 230 | _inUse[conn] = true; 231 | stmt.executeWithConnection(this, conn); 232 | } 233 | else if (_totalConnections < _maxSize && _pending.length > _numConnectionsBeingOpened) 234 | { 235 | // increment the total here, so that while the connection 236 | // is being opened further requests don't cause additional 237 | // connections to be created 238 | _numConnectionsBeingOpened++; 239 | _totalConnections++; 240 | conn = new SQLConnection(); 241 | conn.addEventListener(SQLEvent.OPEN, conn_open); 242 | conn.addEventListener(SQLErrorEvent.ERROR, conn_openError); 243 | 244 | conn.openAsync(_dbFile, SQLMode.READ, null, false, 1024, _encryptionKey); 245 | return; 246 | } 247 | else 248 | { 249 | // No connections are available, and we've reached the 250 | // maximum allotment 251 | return; 252 | } 253 | } 254 | 255 | // once there are no more pending read requests, execute a blocking 256 | // batch if there is one 257 | if (!_blocked && _blockingPending != null && _blockingPending.length > 0 && _blockingConnection.connected) 258 | { 259 | _blocked = true; 260 | var pendingBatch:PendingBatch = _blockingPending.shift(); 261 | pendingBatch.executeWithConnection(this, _blockingConnection); 262 | return; 263 | } 264 | 265 | // if there aren't any pending requests or currently executing statements 266 | // and there is a pending close request, close the connections 267 | if (_pendingClose && 268 | _pending.length == 0 && 269 | (_available.length == _totalConnections) && 270 | !_blocked && 271 | (_blockingPending == null || _blockingPending.length == 0)) 272 | { 273 | closeAll(); 274 | } 275 | } 276 | 277 | 278 | private function conn_open(event:SQLEvent):void 279 | { 280 | var conn:SQLConnection = event.target as SQLConnection; 281 | conn.removeEventListener(SQLEvent.OPEN, conn_open); 282 | conn.removeEventListener(SQLErrorEvent.ERROR, conn_openError); 283 | 284 | if (conn != _blockingConnection) 285 | { 286 | _numConnectionsBeingOpened--; 287 | returnConnection(conn); 288 | } 289 | else 290 | { 291 | _blocked = false; 292 | checkPending(); 293 | } 294 | } 295 | 296 | 297 | private function conn_openError(event:SQLErrorEvent):void 298 | { 299 | // only handle open errors. Close errors are handled by the close 300 | // error handler. Errors that happen during 301 | // statement operations (such as begin, commit, rollback, execute) 302 | // are handled by the statement error handlers. 303 | if (event.error.operation != SQLErrorOperation.OPEN) 304 | return; 305 | 306 | var conn:SQLConnection = SQLConnection(event.target); 307 | conn.removeEventListener(SQLErrorEvent.ERROR, conn_openError); 308 | conn.removeEventListener(SQLEvent.OPEN, conn_open); 309 | 310 | if (_connectionErrorHandler != null) 311 | { 312 | _connectionErrorHandler(event.error); 313 | } 314 | else 315 | { 316 | throw(event.error); 317 | } 318 | } 319 | 320 | 321 | private function closeAll():void 322 | { 323 | _pendingCloseCount = 0; 324 | 325 | for each (var conn:SQLConnection in _available) 326 | { 327 | conn.addEventListener(SQLEvent.CLOSE, conn_close); 328 | conn.addEventListener(SQLErrorEvent.ERROR, conn_closeError); 329 | conn.close(); 330 | _pendingCloseCount++; 331 | } 332 | 333 | if (_blockingConnection != null && _blockingConnection.connected) 334 | { 335 | _blockingConnection.addEventListener(SQLEvent.CLOSE, conn_close); 336 | _blockingConnection.close(); 337 | _pendingCloseCount++; 338 | } 339 | 340 | if (_pendingCloseCount == 0) 341 | { 342 | _finishClosing(); 343 | } 344 | } 345 | 346 | 347 | private function conn_close(event:SQLEvent):void 348 | { 349 | var conn:SQLConnection = event.target as SQLConnection; 350 | conn.removeEventListener(SQLEvent.CLOSE, conn_close); 351 | conn.removeEventListener(SQLErrorEvent.ERROR, conn_closeError); 352 | 353 | _pendingCloseCount--; 354 | 355 | if (_pendingCloseCount == 0) 356 | { 357 | _finishClosing(); 358 | } 359 | } 360 | 361 | 362 | private function conn_closeError(event:SQLErrorEvent):void 363 | { 364 | if (event.error.operation != SQLErrorOperation.CLOSE) 365 | return; 366 | 367 | var conn:SQLConnection = event.target as SQLConnection; 368 | conn.removeEventListener(SQLEvent.CLOSE, conn_close); 369 | conn.removeEventListener(SQLErrorEvent.ERROR, conn_closeError); 370 | 371 | _pendingCloseErrorHandler(event.error); 372 | } 373 | 374 | 375 | private function _finishClosing():void 376 | { 377 | while (_pending.length > 0) 378 | _pending.shift(); 379 | while (_available.length > 0) 380 | _available.shift(); 381 | while (_blockedPending != null && _blockedPending.length > 0) 382 | _blockedPending.shift(); 383 | while (_blockingPending != null && _blockingPending.length > 0) 384 | _blockingPending.shift(); 385 | 386 | _blockingConnection = null; 387 | 388 | _pendingCloseHandler(); 389 | } 390 | } 391 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/SQLRunnerExecuteTest.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data 2 | { 3 | import com.probertson.data.QueuedStatement; 4 | import com.probertson.data.SQLRunner; 5 | 6 | import events.ExecuteResultEvent; 7 | 8 | import flash.data.SQLResult; 9 | import flash.errors.SQLError; 10 | import flash.events.Event; 11 | import flash.events.EventDispatcher; 12 | import flash.events.TimerEvent; 13 | import flash.filesystem.File; 14 | import flash.utils.Timer; 15 | 16 | import flexunit.framework.Assert; 17 | 18 | import org.flexunit.async.Async; 19 | 20 | import utils.CreateDatabase; 21 | 22 | public class SQLRunnerExecuteTest extends EventDispatcher 23 | { 24 | // Reference declaration for class to test 25 | private var _sqlRunner:SQLRunner; 26 | 27 | 28 | // ------- Instance vars ------- 29 | 30 | private var _dbFile:File; 31 | private var _testCompleteTimer:Timer; 32 | 33 | 34 | // ------- Setup/cleanup ------- 35 | 36 | [Before] 37 | public function setUp():void 38 | { 39 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 40 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 41 | createDB.createPopulatedDatabase(); 42 | } 43 | 44 | [After(async, timeout="250")] 45 | public function tearDown():void 46 | { 47 | _sqlRunner.close(sqlRunner_close); 48 | } 49 | 50 | private function sqlRunner_close():void 51 | { 52 | _sqlRunner = null; 53 | var tempDir:File = _dbFile.parent; 54 | tempDir.deleteDirectory(true); 55 | } 56 | 57 | 58 | // ------- Tests ------- 59 | 60 | // ----- Multiple simultaneous SQL statements ----- 61 | 62 | [Test(async, timeout="500")] 63 | public function testConnectionCreation():void 64 | { 65 | addEventListener(ExecuteResultEvent.RESULT, Async.asyncHandler(this, testConnectionCreation_result2, 500)); 66 | 67 | _sqlRunner = new SQLRunner(_dbFile, 5); 68 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testConnectionCreation_result); 69 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testConnectionCreation_result); 70 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testConnectionCreation_result); 71 | _pendingCount = 3; 72 | } 73 | 74 | // --- handlers --- 75 | 76 | private var _pendingCount:int = 0; 77 | 78 | private function testConnectionCreation_result(result:SQLResult):void 79 | { 80 | dispatchEvent(new ExecuteResultEvent(ExecuteResultEvent.RESULT, result)); 81 | } 82 | 83 | private function testConnectionCreation_result2(event:ExecuteResultEvent, passThroughData:Object):void 84 | { 85 | Assert.assertTrue(_sqlRunner.numConnections == _pendingCount); 86 | } 87 | 88 | 89 | 90 | [Test(async, timeout="500")] 91 | public function testConnectionCreationLimit():void 92 | { 93 | addEventListener(ExecuteResultEvent.RESULT, Async.asyncHandler(this, testConnectionCreationLimit_result2, 500)); 94 | 95 | _sqlRunner = new SQLRunner(_dbFile, 2); 96 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testConnectionCreationLimit_result); 97 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testConnectionCreationLimit_result); 98 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testConnectionCreationLimit_result); 99 | } 100 | 101 | // --- handlers --- 102 | 103 | private function testConnectionCreationLimit_result(result:SQLResult):void 104 | { 105 | dispatchEvent(new ExecuteResultEvent(ExecuteResultEvent.RESULT, result)); 106 | } 107 | 108 | private function testConnectionCreationLimit_result2(event:ExecuteResultEvent, passThroughData:Object):void 109 | { 110 | Assert.assertTrue(_sqlRunner.numConnections == 2); 111 | } 112 | 113 | 114 | // ----- SELECT added while INSERT/UPDATE/DELETE is running ----- 115 | 116 | [Ignore("This test isn't working. It should probably be rewritten as a test of the ConnectionPool class")] 117 | [Test(async, timeout="3000")] 118 | public function testExecuteDuringExecuteModify():void 119 | { 120 | addEventListener(Event.COMPLETE, Async.asyncHandler(this, testExecuteDuringExecuteModify_complete, 3000)); 121 | 122 | _sqlRunner = new SQLRunner(_dbFile); 123 | 124 | _testCompleteTimer = new Timer(2000); 125 | _testCompleteTimer.addEventListener(TimerEvent.TIMER, testExecuteDuringExecuteModify_timer); 126 | _testCompleteTimer.start(); 127 | 128 | // pre-create the SQLConnections 129 | var stmt:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 130 | _sqlRunner.executeModify(Vector.([stmt]), testExecuteDuringExecuteModify_preCreate1Result, testExecuteDuringExecuteModify_preCreate1Error); 131 | } 132 | 133 | 134 | // --- handlers --- 135 | 136 | private function testExecuteDuringExecuteModify_preCreate1Result(results:Vector.):void 137 | { 138 | // pre-create the execute() connection 139 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testExecuteDuringExecuteModify_preCreate2Result, null, testExecuteDuringExecuteModify_preCreate2Error); 140 | } 141 | 142 | private function testExecuteDuringExecuteModify_preCreate1Error(error:SQLError):void 143 | { 144 | Assert.fail("An error occurred while pre-creating the executeModify connection"); 145 | } 146 | 147 | private function testExecuteDuringExecuteModify_preCreate2Result(result:SQLResult):void 148 | { 149 | // execute the modify statement that's actually part of the test 150 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 151 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 152 | var stmt3:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 153 | var stmt4:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 154 | var stmt5:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 155 | var stmt6:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 156 | _sqlRunner.executeModify(Vector.([stmt1, stmt2, stmt3, stmt4, stmt5, stmt6]), testExecuteDuringExecuteModify_executeModifyResult, testExecuteDuringExecuteModify_executeModifyError, testExecuteDuringExecuteModify_executeModifyProgress); 157 | } 158 | 159 | private function testExecuteDuringExecuteModify_preCreate2Error(error:SQLError):void 160 | { 161 | Assert.fail("An error occurred while pre-creating the executeModify connection"); 162 | } 163 | 164 | private var _executeModifyComplete:Boolean = false; 165 | private function testExecuteDuringExecuteModify_executeModifyResult(results:Vector.):void 166 | { 167 | _executeModifyComplete = true; 168 | } 169 | 170 | private var _executeCalled:Boolean = false; 171 | private function testExecuteDuringExecuteModify_executeModifyProgress(complete:uint, total:uint):void 172 | { 173 | // call execute() here, so we know we're in the middle of the transaction 174 | if (!_executeCalled && complete > 2) 175 | { 176 | _executeCalled = true; 177 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testExecuteDuringExecuteModify_executeResult, null, testExecuteDuringExecuteModify_executeError); 178 | } 179 | } 180 | 181 | private function testExecuteDuringExecuteModify_executeModifyError(error:SQLError):void 182 | { 183 | Assert.fail("Error during executeModify() statement"); 184 | } 185 | 186 | private var _executeComplete:Boolean = false; 187 | private function testExecuteDuringExecuteModify_executeResult(result:SQLResult):void 188 | { 189 | _executeComplete = true; 190 | } 191 | 192 | private function testExecuteDuringExecuteModify_executeError(error:SQLError):void 193 | { 194 | Assert.fail("Error during execute() call"); 195 | } 196 | 197 | private function testExecuteDuringExecuteModify_timer(event:TimerEvent):void 198 | { 199 | _testCompleteTimer.removeEventListener(TimerEvent.TIMER, testExecuteDuringExecuteModify_timer); 200 | _testCompleteTimer.stop(); 201 | 202 | dispatchEvent(new Event(Event.COMPLETE)); 203 | } 204 | 205 | private function testExecuteDuringExecuteModify_complete(event:Event, passThroughData:Object):void 206 | { 207 | Assert.assertTrue(_executeModifyComplete && _executeComplete); 208 | } 209 | 210 | 211 | 212 | // ----- LIMIT statement ----- 213 | 214 | [Test(async, timeout="500")] 215 | public function testLimit():void 216 | { 217 | addEventListener(ExecuteResultEvent.RESULT, Async.asyncHandler(this, testLimit_result2, 500)); 218 | 219 | _sqlRunner = new SQLRunner(_dbFile); 220 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testLimit_result); 221 | } 222 | 223 | // --- handlers --- 224 | 225 | private function testLimit_result(result:SQLResult):void 226 | { 227 | dispatchEvent(new ExecuteResultEvent(ExecuteResultEvent.RESULT, result)); 228 | } 229 | 230 | private function testLimit_result2(event:ExecuteResultEvent, passThroughData:Object):void 231 | { 232 | Assert.assertEquals(3, event.result.data.length); 233 | Assert.assertEquals("a", event.result.data[0].colString); 234 | Assert.assertEquals(0, event.result.data[0].colInt); 235 | Assert.assertEquals("b", event.result.data[1].colString); 236 | Assert.assertEquals(1, event.result.data[1].colInt); 237 | Assert.assertEquals("c", event.result.data[2].colString); 238 | Assert.assertEquals(2, event.result.data[2].colInt); 239 | } 240 | 241 | 242 | // ----- LIMIT..OFFSET statement ----- 243 | 244 | [Test(async, timeout="500")] 245 | public function testLimitOffset():void 246 | { 247 | addEventListener(ExecuteResultEvent.RESULT, Async.asyncHandler(this, testLimitOffset_result2, 500)); 248 | 249 | _sqlRunner = new SQLRunner(_dbFile); 250 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testLimitOffset_result); 251 | } 252 | 253 | // --- handlers --- 254 | 255 | private function testLimitOffset_result(result:SQLResult):void 256 | { 257 | dispatchEvent(new ExecuteResultEvent(ExecuteResultEvent.RESULT, result)); 258 | } 259 | 260 | private function testLimitOffset_result2(event:ExecuteResultEvent, passThroughData:Object):void 261 | { 262 | Assert.assertEquals(4, event.result.data.length); 263 | Assert.assertEquals("d", event.result.data[0].colString); 264 | Assert.assertEquals(3, event.result.data[0].colInt); 265 | Assert.assertEquals("e", event.result.data[1].colString); 266 | Assert.assertEquals(4, event.result.data[1].colInt); 267 | Assert.assertEquals("f", event.result.data[2].colString); 268 | Assert.assertEquals(5, event.result.data[2].colInt); 269 | Assert.assertEquals("g", event.result.data[3].colString); 270 | Assert.assertEquals(6, event.result.data[3].colInt); 271 | } 272 | 273 | 274 | // ----- Parameterized LIMIT..OFFSET statement ----- 275 | 276 | [Test(async, timeout="500")] 277 | public function testParameterizedLimitOffset():void 278 | { 279 | addEventListener(ExecuteResultEvent.RESULT, Async.asyncHandler(this, testParameterizedLimitOffset_result2, 500)); 280 | 281 | _sqlRunner = new SQLRunner(_dbFile); 282 | _sqlRunner.execute(LOAD_ROWS_PARAMETERIZED_LIMIT_OFFSET_SQL, {limit:7, offset:2}, testParameterizedLimitOffset_result); 283 | } 284 | 285 | // --- handlers --- 286 | 287 | private function testParameterizedLimitOffset_result(result:SQLResult):void 288 | { 289 | dispatchEvent(new ExecuteResultEvent(ExecuteResultEvent.RESULT, result)); 290 | } 291 | 292 | private function testParameterizedLimitOffset_result2(event:ExecuteResultEvent, passThroughData:Object):void 293 | { 294 | Assert.assertEquals(7, event.result.data.length); 295 | Assert.assertEquals("c", event.result.data[0].colString); 296 | Assert.assertEquals(2, event.result.data[0].colInt); 297 | Assert.assertEquals("d", event.result.data[1].colString); 298 | Assert.assertEquals(3, event.result.data[1].colInt); 299 | Assert.assertEquals("e", event.result.data[2].colString); 300 | Assert.assertEquals(4, event.result.data[2].colInt); 301 | Assert.assertEquals("f", event.result.data[3].colString); 302 | Assert.assertEquals(5, event.result.data[3].colInt); 303 | Assert.assertEquals("g", event.result.data[4].colString); 304 | Assert.assertEquals(6, event.result.data[4].colInt); 305 | Assert.assertEquals("h", event.result.data[5].colString); 306 | Assert.assertEquals(7, event.result.data[5].colInt); 307 | Assert.assertEquals("i", event.result.data[6].colString); 308 | Assert.assertEquals(8, event.result.data[6].colInt); 309 | } 310 | 311 | 312 | // ------- SQL statements ------- 313 | 314 | [Embed(source="/sql/LoadRowsLimit.sql", mimeType="application/octet-stream")] 315 | private static const LoadRowsLimitStatementText:Class; 316 | private static const LOAD_ROWS_LIMIT_SQL:String = new LoadRowsLimitStatementText(); 317 | 318 | [Embed(source="/sql/LoadRowsLimitOffset.sql", mimeType="application/octet-stream")] 319 | private static const LoadRowsLimitOffsetStatementText:Class; 320 | private static const LOAD_ROWS_LIMIT_OFFSET_SQL:String = new LoadRowsLimitOffsetStatementText(); 321 | 322 | [Embed(source="/sql/LoadRowsParameterizedLimitOffset.sql", mimeType="application/octet-stream")] 323 | private static const LoadRowsParameterizedLimitOffsetStatementText:Class; 324 | private static const LOAD_ROWS_PARAMETERIZED_LIMIT_OFFSET_SQL:String = new LoadRowsParameterizedLimitOffsetStatementText(); 325 | 326 | [Embed(source="/sql/AddRow.sql", mimeType="application/octet-stream")] 327 | private static const AddRowStatementText:Class; 328 | private static const ADD_ROW_SQL:String = new AddRowStatementText(); 329 | } 330 | } -------------------------------------------------------------------------------- /src/com/probertson/data/SQLRunner.as: -------------------------------------------------------------------------------- 1 | /* 2 | For the latest version of this code, visit: 3 | http://probertson.com/projects/air-sqlite/ 4 | 5 | Copyright (c) 2009-2011 H. Paul Robertson 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | */ 25 | package com.probertson.data 26 | { 27 | import com.probertson.data.sqlRunnerClasses.ConnectionPool; 28 | import com.probertson.data.sqlRunnerClasses.PendingBatch; 29 | import com.probertson.data.sqlRunnerClasses.PendingStatement; 30 | import com.probertson.data.sqlRunnerClasses.StatementCache; 31 | 32 | import flash.data.SQLStatement; 33 | import flash.filesystem.File; 34 | import flash.utils.ByteArray; 35 | 36 | /** 37 | * The SQLRunner class executes SQL statements against a database using a 38 | * pool of database connections to improve execution speed. SELECT 39 | * statements are executed using a pool of connections. Because each 40 | * database connection executes in its own thread, this allows multiple 41 | * statements to execute at the same time. To optimize query performance, 42 | * statement objects are cached and reused when running the same query 43 | * multiple times. 44 | * 45 | *

To execute a SELECT statement, call the execute() method. 46 | * To execute several SELECT statements, just call execute() multiple 47 | * times. The statements will execute in order, using a pool of database 48 | * connections.

49 | * 50 | *

To execute an INSERT, UPDATE, or DELETE statement (or several of those 51 | * together in a transaction) call the executeModify() method. 52 | * If you pass one statement to executeModify(), it runs in its 53 | * own transaction. If you pass multiple statements as a batch in one 54 | * executeModify() call, they run as a transaction. Either they 55 | * all succeed or they are all reverted.

56 | * 57 | *

Here is a basic usage example for a SELECT statement, which is done using the 58 | * execute() method:

59 | * 60 | *
    61 | *
  1. Create a File object containing the location of the database file.
  2. 62 | *
  3. Create the SQLRunner instance
  4. 63 | *
  5. Call the execute() method, passing in the SQL, an object with parameter values, and the result handler method.
  6. 64 | *
65 | * 66 | * 67 | * // setup code: 68 | * // define database file location 69 | * var dbFile:File = File.applicationStorageDirectory.resolvePath("myDatabase.db"); 70 | * // create the SQLRunner 71 | * var sqlRunner:SQLRunner = new SQLRunner(dbFile); 72 | * 73 | * // ... 74 | * 75 | * // run the statement, passing in one parameter (":employeeId" in the SQL) 76 | * // the statement returns an Employee object as defined in the 4th parameter 77 | * sqlRunner.execute(LOAD_EMPLOYEE_SQL, {employeeId:102}, resultHandler, Employee); 78 | * 79 | * private function resultHandler(result:SQLResult):void 80 | * { 81 | * var employee:Employee = result.data[0]; 82 | * // do something with the employee data 83 | * } 84 | * 85 | * // constant for actual SQL statement text 86 | * [Embed(source="sql/LoadEmployee.sql", mimeType="application/octet-stream")] 87 | * private static const LoadEmployeeStatementText:Class; 88 | * private static const LOAD_EMPLOYEE_SQL:String = new LoadEmployeeStatementText(); 89 | * 90 | * 91 | *

Here is a basic example for an INSERT/UPDATE/DELETE statement. To execute those 92 | * statements use the executeModify() method. The executeModify() method accepts a 93 | * "batch" of statements (a Vector of QueuedStatement objects). If you pass more than 94 | * one statement together in a batch, the batch executes as a single transaction.

95 | * 96 | * 97 | * var insert:QueuedStatement = new QueuedStatement(INSERT_EMPLOYEE_SQL, {firstName:"John", lastName:"Smith"}); 98 | * var update:QueuedStatement = new QueuedStatement(UPDATE_EMPLOYEE_SALARY_SQL, {employeeId:100, salary:1000}); 99 | * var statementBatch:Vector.<QueuedStatement> = Vector.<QueuedStatement>([insert, update]); 100 | * 101 | * sqlRunner.executeModify(statementBatch, resultHandler, errorHandler, progressHandler); 102 | * 103 | * private function resultHandler(results:Vector.<SQLResult>):void 104 | * { 105 | * // all operations done 106 | * } 107 | * 108 | * private function errorHandler(error:SQLError):void 109 | * { 110 | * // something went wrong 111 | * } 112 | * 113 | * private function progressHandler(numStepsComplete:uint, totalSteps:uint):void 114 | * { 115 | * var progressPercent:int = numStepsComplete / totalSteps; 116 | * } 117 | * 118 | * // constants for actual SQL statement text 119 | * [Embed(source="sql/InsertEmployee.sql", mimeType="application/octet-stream")] 120 | * private static const InsertEmployeeStatementText:Class; 121 | * private static const INSERT_EMPLOYEE_SQL:String = new InsertEmployeeStatementText(); 122 | * 123 | * [Embed(source="sql/UpdateEmployeeSalary.sql", mimeType="application/octet-stream")] 124 | * private static const UpdateEmployeeSalaryStatementText:Class; 125 | * private static const UPDATE_EMPLOYEE_SALARY_SQL:String = new UpdateEmployeeSalaryStatementText(); 126 | * 127 | */ 128 | public class SQLRunner 129 | { 130 | // ------- Constructor ------- 131 | 132 | /** 133 | * Creates a SQLRunner instance. 134 | * 135 | * @param databaseFile The file location of the database. If a database doesn't 136 | * exist at that location it is created. 137 | * @param maxPoolSize The maximum number of SQLConnection instances to 138 | * use to execute SELECT statements. More connections 139 | * in the pool means more statements execute at the 140 | * same time, which increases speed but also increases 141 | * memory and processor use. The default pool size is 5. 142 | */ 143 | public function SQLRunner(databaseFile:File, maxPoolSize:int=5, encryptionKey:ByteArray=null) 144 | { 145 | _connectionPool = new ConnectionPool(databaseFile, maxPoolSize, encryptionKey); 146 | // create this cache object ahead of time to avoid the overhead 147 | // of checking if it's null each time execute() is called. 148 | // Other cache objects won't be needed nearly as much, so 149 | // their instantiation can be deferred. 150 | _stmtCache = new Object(); 151 | } 152 | 153 | 154 | // ------- Member vars ------- 155 | 156 | private var _connectionPool:ConnectionPool; 157 | private var _stmtCache:Object; 158 | private var _batchStmtCache:Object; 159 | 160 | 161 | // ------- Public properties ------- 162 | 163 | /** 164 | * The total number of database connections currently created. This 165 | * includes connections in the pool (waiting to be used) as well as 166 | * connections that are currently in use. 167 | */ 168 | public function get numConnections():int 169 | { 170 | return _connectionPool.numConnections; 171 | } 172 | 173 | 174 | /** 175 | * Set this property to specify a function that is called when an error happens 176 | * while attempting to open a database connection. 177 | * 178 | *

When an error occurs while trying to connect to a database, the specified function is 179 | * called with one argument, the SQLError object for the error. If no function 180 | * is specified for this property, the error is thrown, resulting in an 181 | * unhandled error in a debugger environment.

182 | */ 183 | public function get connectionErrorHandler():Function 184 | { 185 | return _connectionPool.connectionErrorHandler; 186 | } 187 | public function set connectionErrorHandler(value:Function):void 188 | { 189 | _connectionPool.connectionErrorHandler = value; 190 | } 191 | 192 | 193 | // ------- Public methods ------- 194 | 195 | /** 196 | * Executes a SQL SELECT query asynchronously. If a SQLConnection is 197 | * available, the query begins executing immediately. Otherwise, it is added to 198 | * a queue of pending queries that are executed in request order. 199 | * 200 | *

To execute several SELECT statements, just call execute() multiple 201 | * times. The statements will execute in order, using the pool of database 202 | * connections.

203 | * 204 | * @param sql The text of the SQL statement to execute. 205 | * @param parameters An object whose properties contain the values of the parameters 206 | * that are used in executing the SQL statement. 207 | * @param handler The callback function that's called when the statement execution 208 | * finishes. This function should define one parameter, a SQLResult 209 | * object. When the statement is executed, the SQLResult object containing 210 | * the results of the statement execution is passed to this function. 211 | * @param itemClass A class that has properties corresponding to the columns in the 212 | * SELECT statement. In the resulting data set, each 213 | * result row is represented as an instance of this class. 214 | * @param errorHandler The callback function that's called when an error occurs 215 | * during the statement's execution. A single argument is passed 216 | * to the errorHandler function: a SQLError object containing 217 | * information about the error that happened. 218 | */ 219 | public function execute(sql:String, parameters:Object, handler:Function, itemClass:Class=null, errorHandler:Function=null):void 220 | { 221 | var stmt:StatementCache = _stmtCache[sql]; 222 | if (stmt == null) 223 | { 224 | stmt = new StatementCache(sql); 225 | _stmtCache[sql] = stmt; 226 | } 227 | var pending:PendingStatement = new PendingStatement(stmt, parameters, handler, itemClass, errorHandler); 228 | _connectionPool.addPendingStatement(pending); 229 | } 230 | 231 | 232 | /** 233 | * Executes one or more "data modification" statements (INSERT, UPDATE, and 234 | * DELETE statements). Multiple statements can be passed as a batch, in which case 235 | * the statements are executed within a transaction. 236 | * 237 | *

If you pass one statement (a Vector with only one element) to 238 | * the executeModify() method, it runs in its 239 | * own transaction. If you pass multiple statements as a batch in one 240 | * executeModify() call, they run as a transaction. Either they 241 | * all succeed or they are all reverted.

242 | * 243 | *

The executeModify() method isn't meant to run SELECT statements. 244 | * If you want to run several SELECT statements at the same time, just call 245 | * the execute() method multiple times, once per statement. If you 246 | * want to run one or more INSERT or UPDATE statements (for example) and 247 | * then run a SELECT statement immediately after those statements finish, 248 | * call executeModify() with the INSERT/UPDATE statements 249 | * and then call execute() with the SELECT statement. (You 250 | * don't need to wait for the executeModify() result before 251 | * calling execute(). The statements will run in the order 252 | * they're called, with the INSERT/UPDATE transaction first and the 253 | * SELECT statement afterward.)

254 | * 255 | * @param batch The set of SQL statements to execute, defined as QueuedStatement 256 | * objects. 257 | * @param resultHandler The function that's called when the batch processing finishes. 258 | * This function is called with one argument, a Vector of 259 | * SQLResult objects returned by the batch operations. 260 | * @param errorHandler The function that's called when an error occurs in the batch. 261 | * The function is called with one argument, a SQLError object. 262 | * @param progressHandler A function that's called each time progress is made in executing 263 | * the batch (including after opening the transaction and after 264 | * each statement execution). This function is called with two 265 | * uint arguments: The number of steps completed, 266 | * and the total number of execution steps. (Each "step" is either 267 | * a statement to be executed, or the opening or closing of the 268 | * transaction.) 269 | */ 270 | public function executeModify(statementBatch:Vector., resultHandler:Function, errorHandler:Function, progressHandler:Function=null):void 271 | { 272 | var len:int = statementBatch.length; 273 | var statements:Vector. = new Vector.(len); 274 | var parameters:Vector. = new Vector.(len); 275 | 276 | if (_batchStmtCache == null) 277 | { 278 | _batchStmtCache = new Object(); 279 | } 280 | 281 | for (var i:int = 0; i < len; i++) 282 | { 283 | var sql:String = statementBatch[i].statementText; 284 | var stmt:SQLStatement = _batchStmtCache[sql]; 285 | if (stmt == null) 286 | { 287 | stmt = new SQLStatement(); 288 | stmt.text = sql; 289 | _batchStmtCache[sql] = stmt; 290 | } 291 | 292 | statements[i] = stmt; 293 | parameters[i] = statementBatch[i].parameters; 294 | } 295 | 296 | var pendingBatch:PendingBatch = new PendingBatch(statements, parameters, resultHandler, errorHandler, progressHandler); 297 | _connectionPool.addBlockingBatch(pendingBatch); 298 | } 299 | 300 | 301 | /** 302 | * Waits until all pending statements execute, then closes all open connections to 303 | * the database. 304 | * 305 | *

Once you've called close(), you shouldn't use the SQLRunner 306 | * instance anymore. Instead, create a new SQLRunner object if you need to 307 | * access the same database again.

308 | * 309 | * @param resultHandler A function that's called when connections are closed. 310 | * No argument values are passed to the function. 311 | * 312 | * @param errorHandler A function that's called when an error occurs during 313 | * the close operation. A SQLError object is passed as 314 | * an argument to the function. 315 | */ 316 | public function close(resultHandler:Function, errorHandler:Function=null):void 317 | { 318 | _connectionPool.close(resultHandler, errorHandler); 319 | } 320 | } 321 | } -------------------------------------------------------------------------------- /tests/tests/com/probertson/data/SQLRunnerCloseTest.as: -------------------------------------------------------------------------------- 1 | package tests.com.probertson.data 2 | { 3 | import com.probertson.data.QueuedStatement; 4 | import com.probertson.data.SQLRunner; 5 | 6 | import events.CloseResultEvent; 7 | 8 | import flash.data.SQLResult; 9 | import flash.errors.SQLError; 10 | import flash.events.EventDispatcher; 11 | import flash.filesystem.File; 12 | 13 | import flexunit.framework.Assert; 14 | 15 | import org.flexunit.async.Async; 16 | import org.hamcrest.mxml.collection.InArray; 17 | 18 | import utils.CreateDatabase; 19 | 20 | public class SQLRunnerCloseTest extends EventDispatcher 21 | { 22 | // Reference declaration for class to test 23 | private var _sqlRunner:SQLRunner; 24 | 25 | 26 | // ------- Instance vars ------- 27 | 28 | private var _dbFile:File; 29 | private var _numExecutions:int = 0; 30 | private var _numExecutionsHalfway:int = 0; 31 | private var _executionCompleteCount:int = 0; 32 | 33 | 34 | // ------- Setup/cleanup ------- 35 | 36 | [Before] 37 | public function setUp():void 38 | { 39 | _dbFile = File.createTempDirectory().resolvePath("test.db"); 40 | var createDB:CreateDatabase = new CreateDatabase(_dbFile); 41 | createDB.createDatabase(); 42 | _numExecutions = 0; 43 | _numExecutionsHalfway = 0; 44 | _executionCompleteCount = 0; 45 | } 46 | 47 | [After] 48 | public function tearDown():void 49 | { 50 | _sqlRunner = null; 51 | var tempDir:File = _dbFile.parent; 52 | tempDir.deleteDirectory(true); 53 | } 54 | 55 | [BeforeClass] 56 | public static function setUpBeforeClass():void 57 | { 58 | } 59 | 60 | [AfterClass] 61 | public static function tearDownAfterClass():void 62 | { 63 | } 64 | 65 | 66 | // ------- Tests ------- 67 | 68 | // ----- Test basic closing ----- 69 | 70 | [Test(async, timeout="500")] 71 | public function testClose():void 72 | { 73 | addEventListener(CloseResultEvent.CLOSE, Async.asyncHandler(this, testClose_result2, 500)); 74 | 75 | _sqlRunner = new SQLRunner(_dbFile); 76 | _sqlRunner.close(testClose_result); 77 | } 78 | 79 | // --- handlers --- 80 | 81 | private function testClose_result():void 82 | { 83 | dispatchEvent(new CloseResultEvent(CloseResultEvent.CLOSE)); 84 | } 85 | 86 | private function testClose_result2(event:CloseResultEvent, passThroughData:Object):void 87 | { 88 | Assert.assertTrue(true); 89 | } 90 | 91 | 92 | // ----- Test that statements execute before closing ----- 93 | 94 | [Test(async, timeout="500")] 95 | public function testCloseAfterExecute():void 96 | { 97 | addEventListener(CloseResultEvent.CLOSE, Async.asyncHandler(this, testCloseAfterExecute_result2, 500)); 98 | 99 | _sqlRunner = new SQLRunner(_dbFile); 100 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterExecute_execute_result); 101 | _numExecutions++; 102 | _sqlRunner.close(testCloseAfterExecute_result); 103 | } 104 | 105 | // --- handlers --- 106 | 107 | private function testCloseAfterExecute_execute_result(result:SQLResult):void 108 | { 109 | _executionCompleteCount++; 110 | } 111 | 112 | private function testCloseAfterExecute_result():void 113 | { 114 | dispatchEvent(new CloseResultEvent(CloseResultEvent.CLOSE)); 115 | } 116 | 117 | private function testCloseAfterExecute_result2(event:CloseResultEvent, passThroughData:Object):void 118 | { 119 | Assert.assertEquals(_numExecutions, _executionCompleteCount); 120 | } 121 | 122 | 123 | [Test(async, timeout="500")] 124 | public function testCloseAfterMultipleExecute():void 125 | { 126 | addEventListener(CloseResultEvent.CLOSE, Async.asyncHandler(this, testCloseAfterMultipleExecute_result2, 500)); 127 | 128 | _sqlRunner = new SQLRunner(_dbFile); 129 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMultipleExecute_execute_result); 130 | _numExecutions++; 131 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMultipleExecute_execute_result); 132 | _numExecutions++; 133 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMultipleExecute_execute_result); 134 | _numExecutions++; 135 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMultipleExecute_execute_result); 136 | _numExecutions++; 137 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMultipleExecute_execute_result); 138 | _numExecutions++; 139 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMultipleExecute_execute_result); 140 | _numExecutions++; 141 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMultipleExecute_execute_result); 142 | _numExecutions++; 143 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMultipleExecute_execute_result); 144 | _numExecutions++; 145 | _sqlRunner.close(testCloseAfterMultipleExecute_result); 146 | } 147 | 148 | // --- handlers --- 149 | 150 | private function testCloseAfterMultipleExecute_execute_result(result:SQLResult):void 151 | { 152 | _executionCompleteCount++; 153 | } 154 | 155 | private function testCloseAfterMultipleExecute_result():void 156 | { 157 | dispatchEvent(new CloseResultEvent(CloseResultEvent.CLOSE)); 158 | } 159 | 160 | private function testCloseAfterMultipleExecute_result2(event:CloseResultEvent, passThroughData:Object):void 161 | { 162 | Assert.assertEquals(_numExecutions, _executionCompleteCount); 163 | } 164 | 165 | 166 | // ----- Test that executeModify() statements execute before closing ----- 167 | 168 | [Test(async, timeout="500")] 169 | public function testCloseAfterExecuteModify():void 170 | { 171 | addEventListener(CloseResultEvent.CLOSE, Async.asyncHandler(this, testCloseAfterExecuteModify_result2, 500)); 172 | 173 | _sqlRunner = new SQLRunner(_dbFile); 174 | 175 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 176 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 177 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), testCloseAfterExecuteModify_executeModify_result, testCloseAfterExecuteModify_executeModify_error); 178 | _numExecutions++; 179 | 180 | var stmt3:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hola", colInt:15}); 181 | _sqlRunner.executeModify(Vector.([stmt3]), testCloseAfterExecuteModify_executeModify_result, testCloseAfterExecuteModify_executeModify_error); 182 | _numExecutions++; 183 | 184 | var stmt4:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Mundo", colInt:99}); 185 | _sqlRunner.executeModify(Vector.([stmt4]), testCloseAfterExecuteModify_executeModify_result, testCloseAfterExecuteModify_executeModify_error); 186 | _numExecutions++; 187 | 188 | _sqlRunner.close(testCloseAfterExecuteModify_result); 189 | } 190 | 191 | // --- handlers --- 192 | 193 | private function testCloseAfterExecuteModify_executeModify_result(results:Vector.):void 194 | { 195 | _executionCompleteCount++; 196 | } 197 | 198 | private function testCloseAfterExecuteModify_executeModify_error(error:SQLError):void 199 | { 200 | Assert.fail(error.message); 201 | } 202 | 203 | private function testCloseAfterExecuteModify_result():void 204 | { 205 | dispatchEvent(new CloseResultEvent(CloseResultEvent.CLOSE)); 206 | } 207 | 208 | private function testCloseAfterExecuteModify_result2(event:CloseResultEvent, passThroughData:Object):void 209 | { 210 | Assert.assertEquals(_numExecutions, _executionCompleteCount); 211 | } 212 | 213 | 214 | [Test(async, timeout="500")] 215 | public function testCloseAfterMixedExecute():void 216 | { 217 | addEventListener(CloseResultEvent.CLOSE, Async.asyncHandler(this, testCloseAfterMixedExecute_result2, 500)); 218 | 219 | _sqlRunner = new SQLRunner(_dbFile); 220 | 221 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 222 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 223 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), testCloseAfterMixedExecute_executeModify_result, testCloseAfterMixedExecute_executeModify_error); 224 | _numExecutions++; 225 | 226 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMixedExecute_execute_result); 227 | _numExecutions++; 228 | 229 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMixedExecute_execute_result); 230 | _numExecutions++; 231 | 232 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMixedExecute_execute_result); 233 | _numExecutions++; 234 | 235 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMixedExecute_execute_result); 236 | _numExecutions++; 237 | 238 | var stmt3:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hola", colInt:15}); 239 | _sqlRunner.executeModify(Vector.([stmt3]), testCloseAfterMixedExecute_executeModify_result, testCloseAfterMixedExecute_executeModify_error); 240 | _numExecutions++; 241 | 242 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMixedExecute_execute_result); 243 | _numExecutions++; 244 | 245 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMixedExecute_execute_result); 246 | _numExecutions++; 247 | 248 | var stmt4:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Mundo", colInt:99}); 249 | _sqlRunner.executeModify(Vector.([stmt4]), testCloseAfterMixedExecute_executeModify_result, testCloseAfterMixedExecute_executeModify_error); 250 | _numExecutions++; 251 | 252 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseAfterMixedExecute_execute_result); 253 | _numExecutions++; 254 | 255 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseAfterMixedExecute_execute_result); 256 | _numExecutions++; 257 | 258 | _sqlRunner.close(testCloseAfterMixedExecute_result); 259 | } 260 | 261 | // --- handlers --- 262 | 263 | private function testCloseAfterMixedExecute_execute_result(result:SQLResult):void 264 | { 265 | _executionCompleteCount++; 266 | } 267 | 268 | private function testCloseAfterMixedExecute_executeModify_result(results:Vector.):void 269 | { 270 | _executionCompleteCount++; 271 | } 272 | 273 | private function testCloseAfterMixedExecute_executeModify_error(error:SQLError):void 274 | { 275 | Assert.fail(error.message); 276 | } 277 | 278 | private function testCloseAfterMixedExecute_result():void 279 | { 280 | dispatchEvent(new CloseResultEvent(CloseResultEvent.CLOSE)); 281 | } 282 | 283 | private function testCloseAfterMixedExecute_result2(event:CloseResultEvent, passThroughData:Object):void 284 | { 285 | Assert.assertEquals(_numExecutions, _executionCompleteCount); 286 | } 287 | 288 | 289 | // ----- Test executing, closing, then re-opening and closing again ----- 290 | 291 | [Test(async, timeout="5000")] 292 | public function testCloseOpenClose():void 293 | { 294 | addEventListener(CloseResultEvent.CLOSE, Async.asyncHandler(this, testCloseOpenClose_result2, 5000)); 295 | 296 | _sqlRunner = new SQLRunner(_dbFile); 297 | 298 | var stmt1:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hello", colInt:7}); 299 | var stmt2:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"World", colInt:17}); 300 | _sqlRunner.executeModify(Vector.([stmt1, stmt2]), testCloseOpenClose_stmt1_result, testCloseOpenClose_executeModify_error); 301 | _numExecutions++; 302 | _numExecutionsHalfway++; 303 | } 304 | 305 | private function testCloseOpenClose_stmt1_result(results:Vector.):void 306 | { 307 | _executionCompleteCount++; 308 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseOpenClose_execute1_result); 309 | _numExecutions++; 310 | _numExecutionsHalfway++; 311 | } 312 | 313 | private function testCloseOpenClose_execute1_result(result:SQLResult):void 314 | { 315 | _executionCompleteCount++; 316 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseOpenClose_execute2_result); 317 | _numExecutions++; 318 | _numExecutionsHalfway++; 319 | } 320 | 321 | private function testCloseOpenClose_execute2_result(result:SQLResult):void 322 | { 323 | _executionCompleteCount++; 324 | var stmt3:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Hola", colInt:15}); 325 | _sqlRunner.executeModify(Vector.([stmt3]), testCloseOpenClose_stmt3_result, testCloseOpenClose_executeModify_error); 326 | _numExecutions++; 327 | _numExecutionsHalfway++; 328 | } 329 | 330 | private function testCloseOpenClose_stmt3_result(results:Vector.):void 331 | { 332 | _executionCompleteCount++; 333 | _sqlRunner.close(testCloseOpenClose_close1_result); 334 | } 335 | 336 | private function testCloseOpenClose_close1_result():void 337 | { 338 | Assert.assertEquals(_numExecutionsHalfway, _executionCompleteCount); 339 | _sqlRunner = new SQLRunner(_dbFile); 340 | 341 | _execution3Complete = 0; 342 | 343 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseOpenClose_execute3_result); 344 | _numExecutions++; 345 | 346 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseOpenClose_execute3_result); 347 | _numExecutions++; 348 | 349 | _sqlRunner.execute(LOAD_ROWS_LIMIT_SQL, null, testCloseOpenClose_execute3_result); 350 | _numExecutions++; 351 | 352 | _sqlRunner.execute(LOAD_ROWS_LIMIT_OFFSET_SQL, null, testCloseOpenClose_execute3_result); 353 | _numExecutions++; 354 | 355 | _stmt4Complete = false; 356 | var stmt4:QueuedStatement = new QueuedStatement(ADD_ROW_SQL, {colString:"Mundo", colInt:99}); 357 | _sqlRunner.executeModify(Vector.([stmt4]), testCloseOpenClose_stmt4_result, testCloseOpenClose_executeModify_error); 358 | _numExecutions++; 359 | } 360 | 361 | private var _execution3Complete:int = 0; 362 | 363 | private function testCloseOpenClose_execute3_result(result:SQLResult):void 364 | { 365 | _executionCompleteCount++; 366 | _execution3Complete++; 367 | 368 | if (_execution3Complete == 4) 369 | _checkReadyToClose(); 370 | } 371 | 372 | 373 | private var _stmt4Complete:Boolean = false; 374 | 375 | private function testCloseOpenClose_stmt4_result(results:Vector.):void 376 | { 377 | _executionCompleteCount++; 378 | _stmt4Complete = true; 379 | _checkReadyToClose(); 380 | } 381 | 382 | private function _checkReadyToClose():void 383 | { 384 | if (_execution3Complete == 4 && _stmt4Complete) 385 | _sqlRunner.close(testCloseOpenClose_result); 386 | } 387 | 388 | // --- handlers --- 389 | 390 | private function testCloseOpenClose_executeModify_error(error:SQLError):void 391 | { 392 | Assert.fail(error.message); 393 | } 394 | 395 | private function testCloseOpenClose_result():void 396 | { 397 | dispatchEvent(new CloseResultEvent(CloseResultEvent.CLOSE)); 398 | } 399 | 400 | private function testCloseOpenClose_result2(event:CloseResultEvent, passThroughData:Object):void 401 | { 402 | Assert.assertEquals(_numExecutions, _executionCompleteCount); 403 | } 404 | 405 | 406 | // ------- SQL statements ------- 407 | 408 | [Embed(source="/sql/LoadRowsLimit.sql", mimeType="application/octet-stream")] 409 | private static const LoadRowsLimitStatementText:Class; 410 | private static const LOAD_ROWS_LIMIT_SQL:String = new LoadRowsLimitStatementText(); 411 | 412 | [Embed(source="/sql/LoadRowsLimitOffset.sql", mimeType="application/octet-stream")] 413 | private static const LoadRowsLimitOffsetStatementText:Class; 414 | private static const LOAD_ROWS_LIMIT_OFFSET_SQL:String = new LoadRowsLimitOffsetStatementText(); 415 | 416 | [Embed(source="/sql/LoadRowsParameterizedLimitOffset.sql", mimeType="application/octet-stream")] 417 | private static const LoadRowsParameterizedLimitOffsetStatementText:Class; 418 | private static const LOAD_ROWS_PARAMETERIZED_LIMIT_OFFSET_SQL:String = new LoadRowsParameterizedLimitOffsetStatementText(); 419 | 420 | [Embed(source="/sql/AddRow.sql", mimeType="application/octet-stream")] 421 | private static const AddRowStatementText:Class; 422 | private static const ADD_ROW_SQL:String = new AddRowStatementText(); 423 | } 424 | } --------------------------------------------------------------------------------