-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
/
ExpoQueryRunner.ts
152 lines (137 loc) · 5.91 KB
/
ExpoQueryRunner.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import {QueryRunnerAlreadyReleasedError} from "../../error/QueryRunnerAlreadyReleasedError";
import {QueryFailedError} from "../../error/QueryFailedError";
import {AbstractSqliteQueryRunner} from "../sqlite-abstract/AbstractSqliteQueryRunner";
import {TransactionAlreadyStartedError} from "../../error/TransactionAlreadyStartedError";
import {TransactionNotStartedError} from "../../error/TransactionNotStartedError";
import {ExpoDriver} from "./ExpoDriver";
import {Broadcaster} from "../../subscriber/Broadcaster";
// Needed to satisfy the Typescript compiler
interface IResultSet {
insertId: number | undefined;
rowsAffected: number;
rows: {
length: number;
item: (idx: number) => any;
_array: any[];
};
}
interface ITransaction {
executeSql: (
sql: string,
args: any[] | undefined,
ok: (tsx: ITransaction, resultSet: IResultSet) => void,
fail: (tsx: ITransaction, err: any) => void
) => void;
}
/**
* Runs queries on a single sqlite database connection.
*/
export class ExpoQueryRunner extends AbstractSqliteQueryRunner {
/**
* Database driver used by connection.
*/
driver: ExpoDriver;
/**
* Database transaction object
*/
private transaction?: ITransaction;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(driver: ExpoDriver) {
super();
this.driver = driver;
this.connection = driver.connection;
this.broadcaster = new Broadcaster(this);
}
/**
* Starts transaction. Within Expo, all database operations happen in a
* transaction context, so issuing a `BEGIN TRANSACTION` command is
* redundant and will result in the following error:
*
* `Error: Error code 1: cannot start a transaction within a transaction`
*
* Instead, we keep track of a `Transaction` object in `this.transaction`
* and continue using the same object until we wish to commit the
* transaction.
*/
async startTransaction(): Promise<void> {
if (this.isTransactionActive && typeof this.transaction !== "undefined")
throw new TransactionAlreadyStartedError();
this.isTransactionActive = true;
}
/**
* Commits transaction.
* Error will be thrown if transaction was not started.
* Since Expo will automatically commit the transaction once all the
* callbacks of the transaction object have been completed, "committing" a
* transaction in this driver's context means that we delete the transaction
* object and set the stage for the next transaction.
*/
async commitTransaction(): Promise<void> {
if (!this.isTransactionActive && typeof this.transaction === "undefined")
throw new TransactionNotStartedError();
this.isTransactionActive = false;
this.transaction = undefined;
}
/**
* Rollbacks transaction.
* Error will be thrown if transaction was not started.
* This method's functionality is identical to `commitTransaction()` because
* the transaction lifecycle is handled within the Expo transaction object.
* Issuing separate statements for `COMMIT` or `ROLLBACK` aren't necessary.
*/
async rollbackTransaction(): Promise<void> {
if (!this.isTransactionActive && typeof this.transaction === "undefined")
throw new TransactionNotStartedError();
this.isTransactionActive = false;
this.transaction = undefined;
}
/**
* Executes a given SQL query.
*/
query(query: string, parameters?: any[]): Promise<any> {
if (this.isReleased)
throw new QueryRunnerAlreadyReleasedError();
return new Promise<any>(async (ok, fail) => {
const databaseConnection = await this.connect();
this.driver.connection.logger.logQuery(query, parameters, this);
const queryStartTime = +new Date();
// All Expo SQL queries are executed in a transaction context
databaseConnection.transaction((transaction: ITransaction) => {
if (typeof this.transaction === "undefined") {
this.startTransaction();
this.transaction = transaction;
}
this.transaction.executeSql(query, parameters, (t: ITransaction, result: IResultSet) => {
// log slow queries if maxQueryExecution time is set
const maxQueryExecutionTime = this.driver.connection.options.maxQueryExecutionTime;
const queryEndTime = +new Date();
const queryExecutionTime = queryEndTime - queryStartTime;
if (maxQueryExecutionTime && queryExecutionTime > maxQueryExecutionTime) {
this.driver.connection.logger.logQuerySlow(queryExecutionTime, query, parameters, this);
}
// return id of inserted row, if query was insert statement.
if (query.substr(0, 11) === "INSERT INTO") {
ok(result.insertId);
}
else {
let resultSet = [];
for (let i = 0; i < result.rows.length; i++) {
resultSet.push(result.rows.item(i));
}
ok(resultSet);
}
}, (t: ITransaction, err: any) => {
this.driver.connection.logger.logQueryError(err, query, parameters, this);
fail(new QueryFailedError(query, parameters, err));
});
}, (err: any) => {
this.rollbackTransaction();
}, () => {
this.isTransactionActive = false;
this.transaction = undefined;
});
});
}
}