Skip to content

Commit

Permalink
Apply feedback from Chris and fix the initial sync problem
Browse files Browse the repository at this point in the history
  • Loading branch information
chintannp committed Mar 2, 2022
1 parent 1ec72f3 commit ceb5c8e
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 130 deletions.
3 changes: 2 additions & 1 deletion packages/datastore-storage-adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"@aws-amplify/datastore": "3.7.6",
"react-native-sqlite-storage": "5.0.0",
"@types/react-native-sqlite-storage" : "5.0.1",
"expo-sqlite": "~10.1.0"
"expo-sqlite": "10.1.0",
"expo-file-system" : "13.1.4"
},
"jest": {
"globals": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { openDatabase, SQLResultSet, WebSQLDatabase } from 'expo-sqlite';
import { ConsoleLogger as Logger } from '@aws-amplify/core';
import { PersistentModel } from '@aws-amplify/datastore';
import { CommonSQLiteDatabase, ParameterizedStatement } from '../common/types';
import { deleteAsync, documentDirectory } from 'expo-file-system';

const logger = new Logger('ExpoSQLiteDatabase');

Expand All @@ -15,7 +16,10 @@ const DB_VERSION = '1.0';
/*
Note:
I purposely used arrow functions () => {} in this class as expo-sqlite library is not promisified
I purposely used arrow functions () => {} in this class as expo-sqlite library is not promisified.
ExpoSQLite transaction error callbacks require returning a boolean value to indicate whether the
error was handled or not. Returning a true value indicates the error was handled and does not
rollback the whole transaction.
*/

Expand All @@ -24,6 +28,7 @@ class ExpoSQLiteDatabase implements CommonSQLiteDatabase {

public async init(): Promise<void> {
// only open database once.

if (!this.db) {
this.db = openDatabase(DB_NAME, DB_VERSION, DB_DISPLAYNAME, DB_SIZE);
}
Expand All @@ -35,31 +40,32 @@ class ExpoSQLiteDatabase implements CommonSQLiteDatabase {

public async clear(): Promise<void> {
logger.debug('Clearing database');
// delete database is not supported by expo-sqlite. alternative way is to get all table names and drop them.
await this.dropAllTables();
await this.closeDB();
// delete database is not supported by expo-sqlite.
// Database file needs to be deleted using deleteAsync from expo-file-system
await deleteAsync(documentDirectory + 'SQLite/' + DB_NAME);
logger.debug('Database cleared');
// closing db is not required as we are not deleting the db.
}

public async get<T extends PersistentModel>(
statement: string,
params: (string | number)[]
): Promise<T> {
const results: any[] = await this.getAll(statement, params);
return results.length > 0 ? results[0] : undefined;
const results: T[] = await this.getAll(statement, params);
return results[0];
}

public async getAll<T extends PersistentModel>(
statement: string,
params: (string | number)[]
): Promise<T[]> {
const resultSet: SQLResultSet = await new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
this.db.readTransaction(tx => {
tx.executeSql(
statement,
params,
(_, res) => {
resolve(res);
resolve(res?.rows?._array || []);
},
(_, error) => {
reject(error);
Expand All @@ -69,7 +75,6 @@ class ExpoSQLiteDatabase implements CommonSQLiteDatabase {
);
});
});
return resultSet?.rows?._array || [];
}

public async save(
Expand All @@ -95,55 +100,96 @@ class ExpoSQLiteDatabase implements CommonSQLiteDatabase {
public async batchQuery<T = any>(
queryStatements: Set<ParameterizedStatement>
): Promise<T[]> {
const resultSet: T[] = await new Promise((resolve, reject) => {
this.db.readTransaction(tx => {
const results = [];
for (const [statement, params] of queryStatements) {
tx.executeSql(
statement,
params,
(_, res) => {
const item = res.rows.item[0];
item && results.push(item);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
return new Promise((resolveTx, rejectTx) => {
this.db.transaction(async tx => {
try {
const results = [];
// await to push all statement result into the results array.
await Promise.all(
[...queryStatements].map(
([statement, params]) =>
new Promise((resolve, reject) => {
tx.executeSql(
statement,
params,
(_, res) => {
results.push(res.rows._array[0]);
resolve(null);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
);
})
)
);
resolveTx(results);
} catch (error) {
rejectTx(error);
}
resolve(results);
});
});
return resultSet;
}

public async batchSave(
saveStatements: Set<ParameterizedStatement>,
deleteStatements?: Set<ParameterizedStatement>
): Promise<void> {
await new Promise((resolve, reject) => {
this.db.transaction(tx => {
for (const [statement, params] of saveStatements) {
tx.executeSql(statement, params, null, (_, error) => {
reject(error);
logger.warn(error);
return true;
});
}

if (deleteStatements) {
for (const [statement, params] of deleteStatements) {
tx.executeSql(statement, params, null, (_, error) => {
reject(error);
logger.warn(error);
return true;
});
return new Promise((resolveTx, rejectTx) => {
try {
this.db.transaction(async tx => {
if (saveStatements) {
// await for all sql statments promises to resolve
await Promise.all(
[...saveStatements].map(
([statement, params]) =>
new Promise((resolve, reject) =>
tx.executeSql(
statement,
params,
(_, result) => {
resolve(null);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
)
)
)
);
}
});
if (deleteStatements) {
this.db.transaction(async tx => {
await Promise.all(
[...deleteStatements].map(
([statement, params]) =>
new Promise((resolve, reject) =>
tx.executeSql(
statement,
params,
(_, result) => {
resolve(null);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
)
)
)
);
});
}
resolve(null);
});
resolveTx(null);
} catch (error) {
rejectTx(error);
}
});
}

Expand All @@ -154,43 +200,71 @@ class ExpoSQLiteDatabase implements CommonSQLiteDatabase {
const [queryStatement, queryParams] = queryParameterizedStatement;
const [deleteStatement, deleteParams] = deleteParameterizedStatement;

const results: T[] = await new Promise((resolve, reject) => {
this.db.transaction(tx => {
let tempResults;
tx.executeSql(
queryStatement,
queryParams,
(_tx, res) => {
tempResults = res?.rows?._array || [];
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
);
tx.executeSql(deleteStatement, deleteParams, null, (_, error) => {
reject(error);
logger.warn(error);
return true;
});
resolve(tempResults);
return new Promise((resolveTx, rejectTx) => {
this.db.transaction(async tx => {
try {
const result: T[] = await new Promise((resolve, reject) => {
tx.executeSql(
queryStatement,
queryParams,
(_, res) => {
resolve(res?.rows?._array || []);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
);
});
await new Promise((resolve, reject) => {
tx.executeSql(
deleteStatement,
deleteParams,
(_, result) => {
resolve(null);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
);
});
resolveTx(result);
} catch (error) {
rejectTx(error);
}
});
});
return results || [];
}

private async executeStatements(statements: string[]): Promise<void> {
await new Promise((resolve, reject) => {
this.db.transaction(tx => {
for (const statement of statements) {
tx.executeSql(statement, [], null, (_, error) => {
reject(error);
logger.warn(error);
return true;
});
return new Promise((resolveTx, rejectTx) => {
this.db.transaction(async tx => {
try {
await Promise.all(
statements.map(
statement =>
new Promise((resolve, reject) => {
tx.executeSql(
statement,
[],
(_, result) => {
resolve(null);
},
(_, error) => {
reject(error);
return true;
}
);
})
)
);
resolveTx(null);
} catch (error) {
rejectTx(error);
}
resolve(null);
});
});
}
Expand All @@ -202,39 +276,9 @@ class ExpoSQLiteDatabase implements CommonSQLiteDatabase {
// Workaround is to access the private db variable and call the close() method.
await (this.db as any)._db.close();
logger.debug('Database closed');
}
}

private async getAllTableNames(): Promise<string[]> {
const getTablesCommand =
"SELECT name FROM sqlite_master where type='table'";
const resultSet: any[] = await new Promise((resolve, reject) => {
this.db.transaction(tx => {
tx.executeSql(
getTablesCommand,
[],
(_, res) => {
resolve(res?.rows?._array || []);
},
(_, error) => {
reject(error);
logger.warn(error);
return true;
}
);
});
});

return resultSet.map(e => e.name);
}

private async dropAllTables() {
const tableNames = await this.getAllTableNames();
await this.executeStatements(
tableNames.map(tableName => {
return `DROP TABLE IF EXISTS ${tableName}`;
})
);
this.db = undefined;
}
}
}

Expand Down
Loading

0 comments on commit ceb5c8e

Please sign in to comment.