Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6db6ebf
refactor markAccessed functionality to take an id along with a modelName
Jan 23, 2018
a108a74
lint fixes
Jan 23, 2018
73d8085
use map for accessed models, use idAttribute for getting ids
Feb 1, 2018
09a0681
remove some assignments from the query function
Feb 1, 2018
c01c163
refactor memoize function to compare models at the instance level ins…
Feb 20, 2018
bf91076
check equality of items list in memoize
Feb 21, 2018
c4942ea
add missing semicolons
Feb 21, 2018
b06233c
Re-add functionality to record full table scans and memoize accordingly
haveyaseen May 6, 2018
6b3fc60
fix linting errors
haveyaseen May 6, 2018
b7c00a7
prevent mutations during row updates to ensure that references change
haveyaseen May 7, 2018
689a195
adjust performance requirements to account for marking model instance…
haveyaseen May 7, 2018
2152cfd
rename defaultState to emptyState in testReduxIntegrations
haveyaseen May 8, 2018
b6e9c17
do not compare length of objects, get their keys first instead
haveyaseen May 8, 2018
d70f95f
prevent batched mutations for row deletions to enable reference inval…
haveyaseen May 8, 2018
442ea75
further decrease performance requirements due to disabling of batched…
haveyaseen May 8, 2018
73a3d6d
explain and refactor selector memoization function
haveyaseen May 8, 2018
e2a3fb0
remove unnecessary array spreading
haveyaseen May 8, 2018
2681e9e
rename helper functions in memoize to clarify what they do
haveyaseen May 8, 2018
7acda06
reorder functions in Session class to be more consistent
haveyaseen May 8, 2018
2583efd
add more comments explaining instance-based memoization
haveyaseen May 8, 2018
d28bd42
mark tables as fully scanned when evaluating QuerySet with empty clau…
haveyaseen May 9, 2018
49313c5
decrease performance requirements for removing many-to-many relations…
haveyaseen May 9, 2018
585fd4c
make clause related functions global
haveyaseen May 15, 2018
3fa9158
only perform full table scan if no clause filters by the id attribute
haveyaseen May 15, 2018
6b64068
place if conditions in indented parentheses
haveyaseen May 15, 2018
052b16b
decrease performance requirements for big data lookup/creation on Tra…
haveyaseen May 15, 2018
bc5db1a
use process.hrtime() for profiling instead of Date.prototype.getTime()
haveyaseen May 15, 2018
bbcb821
increase big-data perf requirements to 15s for removing and querying …
haveyaseen May 15, 2018
22435ab
extract measureMs to test utils
haveyaseen May 16, 2018
349e256
increase threshold for Travis CI perf drastically
haveyaseen May 16, 2018
cf029aa
Revert "increase threshold for Travis CI perf drastically"
haveyaseen Jun 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,12 @@ const Model = class Model {
return this.session.getDataForModel(this.modelName);
}

static markAccessed() {
this.session.markAccessed(this);
static markAccessed(ids) {
this.session.markAccessed(this.modelName, ids);
}

static markFullTableScanned() {
this.session.markFullTableScanned(this.modelName);
}

/**
Expand Down
117 changes: 94 additions & 23 deletions src/Session.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getBatchToken } from 'immutable-ops';

import { SUCCESS } from './constants';
import { warnDeprecated } from './utils';
import { SUCCESS, UPDATE, DELETE } from './constants';
import { warnDeprecated, clauseFiltersByAttribute } from './utils';

const Session = class Session {
/**
Expand All @@ -22,7 +22,6 @@ const Session = class Session {
this.withMutations = !!withMutations;
this.batchToken = batchToken || getBatchToken();

this._accessedModels = {};
this.modelData = {};

this.models = schema.getModelClasses();
Expand All @@ -38,23 +37,46 @@ const Session = class Session {
});
}

markAccessed(modelName) {
this.getDataForModel(modelName).accessed = true;
}

get accessedModels() {
return this.sessionBoundModels
.filter(model => !!this.getDataForModel(model.modelName).accessed)
.map(model => model.modelName);
}

getDataForModel(modelName) {
if (!this.modelData[modelName]) {
this.modelData[modelName] = {};
}
return this.modelData[modelName];
}

markAccessed(modelName, modelIds = []) {
const data = this.getDataForModel(modelName);
if (!data.accessedInstances) {
data.accessedInstances = {};
}
modelIds.forEach((id) => {
data.accessedInstances[id] = true;
});
}

get accessedModelInstances() {
return this.sessionBoundModels
.filter(({ modelName }) => !!this.getDataForModel(modelName).accessedInstances)
.reduce(
(result, { modelName }) => ({
...result,
[modelName]: this.getDataForModel(modelName).accessedInstances,
}),
{}
);
}

markFullTableScanned(modelName) {
const data = this.getDataForModel(modelName);
data.fullTableScanned = true;
}

get fullTableScannedModels() {
return this.sessionBoundModels
.filter(({ modelName }) => !!this.getDataForModel(modelName).fullTableScanned)
.map(({ modelName }) => modelName);
}

/**
* Applies update to a model state.
*
Expand All @@ -63,24 +85,73 @@ const Session = class Session {
* `type`, `payload`.
*/
applyUpdate(updateSpec) {
const { batchToken, withMutations } = this;
const tx = { batchToken, withMutations };
const tx = this._getTransaction(updateSpec);
const result = this.db.update(updateSpec, tx, this.state);
const { status, state } = result;
const { status, state, payload } = result;

if (status === SUCCESS) {
this.state = state;
} else {
if (status !== SUCCESS) {
throw new Error(`Applying update failed: ${result.toString()}`);
}

return result.payload;
this.state = state;

return payload;
}

query(querySpec) {
const { table } = querySpec;
this.markAccessed(table);
return this.db.query(querySpec, this.state);
const result = this.db.query(querySpec, this.state);

this._markAccessedByQuery(querySpec, result);

return result;
}

_getTransaction(updateSpec) {
const { withMutations } = this;
const { action } = updateSpec;
let batchToken = this.batchToken;
if ([UPDATE, DELETE].includes(action)) {
batchToken = getBatchToken();
}
return { batchToken, withMutations };
}

_markAccessedByQuery(querySpec, result) {
const { table, clauses } = querySpec;
const { rows } = result;

const idAttribute = this[table].idAttribute;
const accessedIds = new Set(rows.map(
row => row[idAttribute]
));

const anyClauseFilteredById = clauses.some((clause) => {
if (!clauseFiltersByAttribute(clause, idAttribute)) {
return false;
}
/**
* we previously knew which row we wanted to access,
* so there was no need to scan the entire table
*/
const id = clause.payload[idAttribute];
accessedIds.add(id);
return true;
});

if (anyClauseFilteredById) {
/**
* clauses have been ordered so that an indexed one was
* the first to be evaluated, and thus only the row
* with the specified id has actually been accessed
*/
this.markAccessed(table, accessedIds);
} else {
/**
* any other clause would have caused a full table scan,
* even if we specified an empty clauses array
*/
this.markFullTableScanned(table);
}
}

// DEPRECATED AND REMOVED METHODS
Expand Down
4 changes: 4 additions & 0 deletions src/db/Database.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ function update(tables, updateSpec, tx, state) {

if (action === UPDATE) {
nextTableState = table.update(tx, currTableState, rows, payload);
// return updated rows
resultPayload = query(tables, querySpec, state).rows;
} else if (action === DELETE) {
nextTableState = table.delete(tx, currTableState, rows);
// return original rows that we just deleted
resultPayload = rows;
} else {
throw new Error(`Database received unknown update type: ${action}`);
}
Expand Down
32 changes: 11 additions & 21 deletions src/db/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import sortBy from 'lodash/sortBy';
import ops from 'immutable-ops';

import { FILTER, EXCLUDE, ORDER_BY } from '../constants';
import { includes } from '../utils';
import { includes, clauseFiltersByAttribute, clauseReducesResultSetSize } from '../utils';


const DEFAULT_OPTS = {
Expand Down Expand Up @@ -105,31 +105,30 @@ const Table = class Table {
return this.accessList(branch);
}

const { idAttribute } = this;

const optimallyOrderedClauses = sortBy(clauses, (clause) => {
if (this._willClauseUseIndexedAttribute(clause)) {
if (clauseFiltersByAttribute(clause, idAttribute)) {
return 1;
}

if (this._willClauseReduceResultSetSize(clause)) {
if (clauseReducesResultSetSize(clause)) {
return 2;
}

return 3;
});

const { idAttribute } = this;
const reducer = (rows, clause) => {
const { type, payload } = clause;
if (!rows) {
if (type === FILTER && payload.hasOwnProperty(idAttribute)) {
if (clauseFiltersByAttribute(clause, idAttribute)) {
const id = payload[idAttribute];
if (id !== null && id !== undefined) {
// Payload specified a primary key; Since that is
// unique, we can directly return that.
return this.idExists(branch, id)
? [this.accessId(branch, id)]
: [];
}
// Payload specified a primary key; Since that is
// unique, we can directly return that.
return this.idExists(branch, id)
? [this.accessId(branch, id)]
: [];
}

return reducer(this.accessList(branch), clause);
Expand All @@ -154,15 +153,6 @@ const Table = class Table {
return optimallyOrderedClauses.reduce(reducer, undefined);
}

_willClauseUseIndexedAttribute(clause) {
return clause.type === FILTER &&
clause.payload.hasOwnProperty(this.idAttribute);
}

_willClauseReduceResultSetSize({ type }) {
return type === FILTER || type === EXCLUDE;
}

/**
* Returns the default state for the data structure.
* @return {Object} The default state for this {@link Backend} instance's data structure
Expand Down
Loading