Skip to content

Commit

Permalink
[expo-sqlite] Fix Web support (#8518)
Browse files Browse the repository at this point in the history
# Why

I wanted to clear the way for #8242 to be landed (or not). I noticed that web support doesn't look like it should work and that it isn't tested anywhere.

# How

- added SQLite test suite to set run on web
- changed the way we fetch values in test suite from using `._array[0]` to `.item(0)`
- added explanations in `SQLite.types.ts` about why we may need a copy of `@types/websql`
- added `Window` declaration to types declarations
- removed `@types/websql` dependency as it's not even importable
- fixed web counterpart — `SQLite.web.ts` — by fixing the method header and gracefully handling situation where `openDatabase` is not available (I think).

# Test Plan

Test suite passes.
  • Loading branch information
sjchmiela committed May 29, 2020
1 parent 0edcd4f commit e11b798
Show file tree
Hide file tree
Showing 15 changed files with 154 additions and 112 deletions.
4 changes: 2 additions & 2 deletions apps/test-suite/TestModules.js
Expand Up @@ -69,7 +69,8 @@ export function getTestModules() {
require('./tests/HTML'),
require('./tests/FirebaseCore'),
require('./tests/FirebaseAnalytics'),
require('./tests/FirebaseRecaptcha')
require('./tests/FirebaseRecaptcha'),
optionalRequire(() => require('./tests/SQLite'))
);

if (Platform.OS === 'android') {
Expand Down Expand Up @@ -117,7 +118,6 @@ export function getTestModules() {
optionalRequire(() => require('./tests/Network')),
optionalRequire(() => require('./tests/SecureStore')),
optionalRequire(() => require('./tests/Segment')),
optionalRequire(() => require('./tests/SQLite')),
optionalRequire(() => require('./tests/Speech')),
optionalRequire(() => require('./tests/Recording')),
optionalRequire(() => require('./tests/ScreenOrientation')),
Expand Down
179 changes: 95 additions & 84 deletions apps/test-suite/tests/SQLite.js
@@ -1,5 +1,6 @@
'use strict';

import { Platform } from '@unimodules/core';
import { Asset } from 'expo-asset';
import * as FS from 'expo-file-system';
import * as SQLite from 'expo-sqlite';
Expand Down Expand Up @@ -48,7 +49,7 @@ export function test(t) {
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(3);
t.expect(results.rows._array[0].j).toBeCloseTo(23.4);
t.expect(results.rows.item(0).j).toBeCloseTo(23.4);
},
onError
);
Expand All @@ -58,41 +59,44 @@ export function test(t) {
);
});

const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
t.expect(exists).toBeTruthy();
if (Platform.OS !== 'web') {
const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
t.expect(exists).toBeTruthy();
}
});

t.it(
'should work with a downloaded .db file',
async () => {
await FS.downloadAsync(
Asset.fromModule(require('../assets/asset-db.db')).uri,
`${FS.documentDirectory}SQLite/downloaded.db`
);

const db = SQLite.openDatabase('downloaded.db');
await new Promise((resolve, reject) => {
db.transaction(
tx => {
const nop = () => {};
const onError = (tx, error) => reject(error);
tx.executeSql(
'SELECT * FROM Users',
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(3);
t.expect(results.rows._array[0].j).toBeCloseTo(23.4);
},
onError
);
},
reject,
resolve
if (Platform.OS !== 'web') {
t.it(
'should work with a downloaded .db file',
async () => {
await FS.downloadAsync(
Asset.fromModule(require('../assets/asset-db.db')).uri,
`${FS.documentDirectory}SQLite/downloaded.db`
);
});
},
30000
);

const db = SQLite.openDatabase('downloaded.db');
await new Promise((resolve, reject) => {
db.transaction(
tx => {
const onError = (tx, error) => reject(error);
tx.executeSql(
'SELECT * FROM Users',
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(3);
t.expect(results.rows._array[0].j).toBeCloseTo(23.4);
},
onError
);
},
reject,
resolve
);
});
},
30000
);
}

t.it('should be able to recreate db from scratch by deleting file', async () => {
{
Expand Down Expand Up @@ -132,12 +136,12 @@ export function test(t) {
});
}

{
if (Platform.OS !== 'web') {
const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
t.expect(exists).toBeTruthy();
}

{
if (Platform.OS !== 'web') {
await FS.deleteAsync(`${FS.documentDirectory}SQLite/test.db`);
const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
t.expect(exists).toBeFalsy();
Expand Down Expand Up @@ -211,10 +215,10 @@ export function test(t) {
'SELECT * FROM Nulling',
[],
(tx, results) => {
t.expect(results.rows._array[0].x).toBeNull();
t.expect(results.rows._array[0].y).toBeNull();
t.expect(results.rows._array[1].x).toBeNull();
t.expect(results.rows._array[1].y).toBeNull();
t.expect(results.rows.item(0).x).toBeNull();
t.expect(results.rows.item(0).y).toBeNull();
t.expect(results.rows.item(1).x).toBeNull();
t.expect(results.rows.item(1).y).toBeNull();
},
onError
);
Expand All @@ -224,54 +228,61 @@ export function test(t) {
);
});

const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
t.expect(exists).toBeTruthy();
if (Platform.OS !== 'web') {
const { exists } = await FS.getInfoAsync(`${FS.documentDirectory}SQLite/test.db`);
t.expect(exists).toBeTruthy();
}
});

t.it('should support PRAGMA statements', async () => {
const db = SQLite.openDatabase('test.db');
await new Promise((resolve, reject) => {
db.transaction(
tx => {
const nop = () => {};
const onError = (tx, error) => reject(error);
// Do not try to test PRAGMA statements support in web
// as it is expected to not be working.
// See https://stackoverflow.com/a/10298712
if (Platform.OS !== 'web') {
t.it('should support PRAGMA statements', async () => {
const db = SQLite.openDatabase('test.db');
await new Promise((resolve, reject) => {
db.transaction(
tx => {
const nop = () => {};
const onError = (tx, error) => reject(error);

tx.executeSql('DROP TABLE IF EXISTS SomeTable;', [], nop, onError);
tx.executeSql(
'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
[],
nop,
onError
);
// a result-returning pragma
tx.executeSql(
'PRAGMA table_info(SomeTable);',
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(2);
t.expect(results.rows._array[0].name).toEqual('id');
t.expect(results.rows._array[1].name).toEqual('name');
},
onError
);
// a no-result pragma
tx.executeSql('PRAGMA case_sensitive_like = true;', [], nop, onError);
// a setter/getter pragma
tx.executeSql('PRAGMA user_version = 123;', [], nop, onError);
tx.executeSql(
'PRAGMA user_version;',
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(1);
t.expect(results.rows._array[0].user_version).toEqual(123);
},
onError
);
},
reject,
resolve
);
tx.executeSql('DROP TABLE IF EXISTS SomeTable;', [], nop, onError);
tx.executeSql(
'CREATE TABLE IF NOT EXISTS SomeTable (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(64));',
[],
nop,
onError
);
// a result-returning pragma
tx.executeSql(
'PRAGMA table_info(SomeTable);',
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(2);
t.expect(results.rows.item(0).name).toEqual('id');
t.expect(results.rows.item(1).name).toEqual('name');
},
onError
);
// a no-result pragma
tx.executeSql('PRAGMA case_sensitive_like = true;', [], nop, onError);
// a setter/getter pragma
tx.executeSql('PRAGMA user_version = 123;', [], nop, onError);
tx.executeSql(
'PRAGMA user_version;',
[],
(tx, results) => {
t.expect(results.rows.length).toEqual(1);
t.expect(results.rows.item(0).user_version).toEqual(123);
},
onError
);
},
reject,
resolve
);
});
});
});
}
});
}
2 changes: 2 additions & 0 deletions packages/expo-sqlite/CHANGELOG.md
Expand Up @@ -8,6 +8,8 @@

### 🐛 Bug fixes

- Fixed support for using `expo-sqlite` on Web ([#8518](https://github.com/expo/expo/pull/8518) by [@sjchmiela](https://github.com/sjchmiela))

## 8.2.0 — 2020-05-27

*This version does not introduce any user-facing changes.*
3 changes: 3 additions & 0 deletions packages/expo-sqlite/build/SQLite.types.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion packages/expo-sqlite/build/SQLite.types.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-sqlite/build/SQLite.types.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions packages/expo-sqlite/build/SQLite.web.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions packages/expo-sqlite/build/SQLite.web.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-sqlite/build/SQLite.web.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion packages/expo-sqlite/package.json
Expand Up @@ -44,7 +44,6 @@
},
"dependencies": {
"@expo/websql": "^1.0.1",
"@types/websql": "^0.0.27",
"lodash": "^4.17.15"
},
"devDependencies": {
Expand Down
17 changes: 16 additions & 1 deletion packages/expo-sqlite/src/SQLite.types.ts
@@ -1,4 +1,19 @@
// Definitions by: TeamworkGuy2 <https://github.com/TeamworkGuy2>
// Definitions copied from `@types/websql` as we want
// to expose a custom version of the API that:
// - uses primitive `string` instead of `String`
// - excludes some methods that are not exposed by our API.
//
// Original definitions by: TeamworkGuy2 <https://github.com/TeamworkGuy2>

export interface Window {
openDatabase?: (
name: string,
version: string,
displayName: string,
estimatedSize: number,
creationCallback?: DatabaseCallback
) => Database;
}

export interface DatabaseCallback {
(database: Database): void;
Expand Down
19 changes: 17 additions & 2 deletions packages/expo-sqlite/src/SQLite.web.ts
@@ -1,2 +1,17 @@
export const { openDatabase } = global;
export default { openDatabase };
import { UnavailabilityError } from '@unimodules/core';

import { Window, DatabaseCallback } from './SQLite.types';

export function openDatabase(
name: string,
version: string = '1.0',
description: string = name,
size: number = 1,
callback?: DatabaseCallback
) {
const typedWindow: Window = window as Window;
if ('openDatabase' in typedWindow && typedWindow.openDatabase) {
return typedWindow.openDatabase(name, version, description, size, callback);
}
throw new UnavailabilityError('window', 'openDatabase');
}
1 change: 0 additions & 1 deletion packages/expo-sqlite/src/ts-declarations/global.d.ts

This file was deleted.

7 changes: 1 addition & 6 deletions packages/expo-sqlite/src/ts-declarations/process.d.ts
@@ -1,6 +1 @@
declare const process: {
env: {
NODE_ENV: string;
};
[key: string]: any;
};
declare const process: Record<string, any>;
5 changes: 0 additions & 5 deletions yarn.lock
Expand Up @@ -2949,11 +2949,6 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"

"@types/websql@^0.0.27":
version "0.0.27"
resolved "https://registry.yarnpkg.com/@types/websql/-/websql-0.0.27.tgz#621a666a7f02018e7cbb4abab956a25736c27d71"
integrity sha1-Yhpman8CAY58u0q6uVaiVzbCfXE=

"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
Expand Down

0 comments on commit e11b798

Please sign in to comment.