Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add warning message when trying to use SQLite with CPK enabled #11027

Merged
merged 13 commits into from
Mar 17, 2023
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import sqlite3 from 'sqlite3';
sqlite3.verbose();

import SQLiteAdapter from '../src/SQLiteAdapter/SQLiteAdapter';
import SQLiteDatabase from '../src/SQLiteAdapter/SQLiteDatabase';
import { ParameterizedStatement } from '../src/common/types';
Expand All @@ -10,14 +7,22 @@ import {
PersistentModelConstructor,
initSchema as initSchemaType,
} from '@aws-amplify/datastore';
import { Model, Post, Comment, testSchema } from './helpers';
import {
Model,
Post,
Comment,
testSchema,
InnerSQLiteDatabase,
} from './helpers';
import { SyncEngine } from '@aws-amplify/datastore/lib-esm/sync';
import Observable from 'zen-observable';
import {
pause,
addCommonQueryTests,
} from '../../datastore/__tests__/commonAdapterTests';

let innerSQLiteDatabase;

jest.mock('@aws-amplify/datastore/src/sync/datastoreConnectivity', () => {
return {
status: () => Observable.of(false) as any,
Expand All @@ -30,7 +35,8 @@ jest.mock('@aws-amplify/datastore/src/sync/datastoreConnectivity', () => {
jest.mock('react-native-sqlite-storage', () => {
return {
async openDatabase(name, version, displayname, size) {
return new InnerSQLiteDatabase();
innerSQLiteDatabase = new InnerSQLiteDatabase();
return innerSQLiteDatabase;
},
async deleteDatabase(name) {},
enablePromise(enabled) {},
Expand All @@ -40,84 +46,12 @@ jest.mock('react-native-sqlite-storage', () => {

let initSchema: typeof initSchemaType;
let DataStore: typeof DataStoreType;
let sqlog: any[];

/**
* A lower-level SQLite wrapper to test SQLiteAdapter against.
* It's intended to be fast, using an in-memory database.
*/
class InnerSQLiteDatabase {
private innerDB;

constructor() {
this.innerDB = new sqlite3.Database(':memory:');
}

async executeSql(
statement,
params: any[] = [],
callback: ((...args) => Promise<any>) | undefined = undefined,
logger = undefined
) {
sqlog.push(`${statement}; ${JSON.stringify(params)}`);
if (statement.trim().toLowerCase().startsWith('select')) {
return new Promise(async resolve => {
const rows: any[] = [];
const resultSet = {
rows: {
get length() {
return rows.length;
},
raw: () => rows,
},
};

await this.innerDB.each(
statement,
params,
async (err, row) => {
if (err) {
console.error('SQLite ERROR', new Error(err));
console.warn(statement, params);
}
rows.push(row);
},
() => {
resolve([resultSet]);
}
);

if (typeof callback === 'function') await callback(this, resultSet);
});
} else {
return await this.innerDB.run(statement, params, err => {
if (typeof callback === 'function') {
callback(err);
} else if (err) {
console.error('calback', err);
throw err;
}
});
}
}

async transaction(fn) {
return this.innerDB.serialize(await fn(this));
}

async readTransaction(fn) {
return this.innerDB.serialize(await fn(this));
}

async close() {}
}

describe('SQLiteAdapter', () => {
let Comment: PersistentModelConstructor<Comment>;
let Model: PersistentModelConstructor<Model>;
let Post: PersistentModelConstructor<Post>;
let syncEngine: SyncEngine;
sqlog = [];

/**
* Gets all mutations currently in the outbox. This should include ALL
Expand Down Expand Up @@ -176,8 +110,6 @@ describe('SQLiteAdapter', () => {
// prevents the mutation process from clearing the mutation queue, which
// allows us to observe the state of mutations.
(syncEngine as any).mutationsProcessor.isReady = () => false;

sqlog = [];
});

describe('sanity checks', () => {
Expand All @@ -187,7 +119,7 @@ describe('SQLiteAdapter', () => {

it('is logging SQL statements during normal operation', async () => {
await DataStore.query(Post);
expect(sqlog.length).toBeGreaterThan(0);
expect(innerSQLiteDatabase.sqlog.length).toBeGreaterThan(0);
});

it('can batchSave', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SQLiteCPKDisabled.test.ts and SQLiteCPKEnabled.test.ts exist in two separate
* files in order to test DataStore with two separate schemas.
* jest.isolateModules was not correctly isolating the instances of DataStore
* resulting in the DataStore singleton being shared between the tests.
* The schema can only be initialized once in DataStore, making it impossible to
* test the effect of different schemas.
*
* Both files should be removed when CPK support is added.
*/
import Observable from 'zen-observable';
import SQLiteAdapter from '../src/SQLiteAdapter/SQLiteAdapter';
import { testSchema, InnerSQLiteDatabase } from './helpers';
import { initSchema, DataStore } from '@aws-amplify/datastore';

jest.mock('@aws-amplify/datastore/src/sync/datastoreConnectivity', () => {
return {
status: () => Observable.of(false) as any,
unsubscribe: () => {},
socketDisconnected: () => {},
};
});

jest.mock('react-native-sqlite-storage', () => {
return {
async openDatabase(name, version, displayname, size) {
return new InnerSQLiteDatabase();
},
async deleteDatabase(name) {},
enablePromise(enabled) {},
DEBUG(debug) {},
};
});

describe('SQLite CPK Disabled', () => {
test('does not log error when schema is generated with targetName (cpk disabled)', async () => {
console.error = jest.fn();
const { initSchema, DataStore } = require('@aws-amplify/datastore');
DataStore.configure({
storageAdapter: SQLiteAdapter,
});
initSchema(testSchema());
await DataStore.start();
expect(console.error as any).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* SQLiteCPKDisabled.test.ts and SQLiteCPKEnabled.test.ts exist in two separate
* files in order to test DataStore with two separate schemas.
* jest.isolateModules was not correctly isolating the instances of DataStore
* resulting in the DataStore singleton being shared between the tests.
* The schema can only be initialized once in DataStore, making it impossible to
* test the effect of different schemas.
*
* Both files should be removed when CPK support is added.
*/
import Observable from 'zen-observable';
import SQLiteAdapter from '../src/SQLiteAdapter/SQLiteAdapter';
import { testSchema, InnerSQLiteDatabase } from './helpers';
import { initSchema, DataStore } from '@aws-amplify/datastore';

jest.mock('@aws-amplify/datastore/src/sync/datastoreConnectivity', () => {
return {
status: () => Observable.of(false) as any,
unsubscribe: () => {},
socketDisconnected: () => {},
};
});

jest.mock('react-native-sqlite-storage', () => {
return {
async openDatabase(name, version, displayname, size) {
return new InnerSQLiteDatabase();
},
async deleteDatabase(name) {},
enablePromise(enabled) {},
DEBUG(debug) {},
};
});

describe('SQLite SPK Enabled', () => {
test('logs error when schema is generated with targetNames (cpk enabled)', async () => {
console.error = jest.fn();
const { initSchema, DataStore } = require('@aws-amplify/datastore');
DataStore.configure({
storageAdapter: SQLiteAdapter,
});
console.log(JSON.stringify(testSchema(), null, 2));
initSchema({
...testSchema(),
models: {
Post: {
name: 'Post',
fields: {
id: {
name: 'id',
isArray: false,
type: 'ID',
isRequired: true,
attributes: [],
},
title: {
name: 'title',
isArray: false,
type: 'String',
isRequired: true,
attributes: [],
},
comments: {
name: 'comments',
isArray: true,
type: {
model: 'Comment',
},
isRequired: true,
attributes: [],
isArrayNullable: true,
association: {
connectionType: 'HAS_MANY',
associatedWith: 'postId',
},
},
},
syncable: true,
pluralName: 'Posts',
attributes: [
{
type: 'model',
properties: {},
},
],
},
Comment: {
name: 'Comment',
fields: {
id: {
name: 'id',
isArray: false,
type: 'ID',
isRequired: true,
attributes: [],
},
content: {
name: 'content',
isArray: false,
type: 'String',
isRequired: true,
attributes: [],
},
post: {
name: 'post',
isArray: false,
type: {
model: 'Post',
},
isRequired: false,
attributes: [],
association: {
connectionType: 'BELONGS_TO',
targetNames: ['postId'],
},
},
},
syncable: true,
pluralName: 'Comments',
attributes: [
{
type: 'model',
properties: {},
},
{
type: 'key',
properties: {
name: 'byPost',
fields: ['postId'],
},
},
],
},
},
});
await DataStore.start();
expect((console.error as any).mock.calls[0][0]).toMatch(
'The SQLite adapter does not support schemas using custom primary key. Set `graphQLTransformer.respectPrimaryKeyAttributesOnConnectionField in `amplify/cli.json` to false to disable custom primary key. To regenerate your API, add or remove an empty newline to your GraphQL schema (to change the computed hash) then run `amplify push`.'
);
});
});
Loading