lib/database-setup-query-builder.js
/* eslint global-require:0 */
import ModelRegistry from './model-registry';
import {tableNameForJoin} from './utils';
import Attributes from './attributes';
const {AttributeCollection, AttributeJoinedData} = Attributes;
/**
The factory methods in this class assemble SQL queries that build Model
tables based on their attribute schema.
@private
*/
export default class DatabaseSetupQueryBuilder {
setupQueries() {
let queries = []
for (const klass of ModelRegistry.getAllConstructors()) {
queries = queries.concat(this.setupQueriesForTable(klass));
}
return queries;
}
analyzeQueries() {
const queries = [];
for (const klass of ModelRegistry.getAllConstructors()) {
const attributes = Object.keys(klass.attributes).map(k => klass.attributes[k]);
const collectionAttributes = attributes.filter((attr) =>
attr.queryable && attr instanceof AttributeCollection
)
queries.push(`ANALYZE \`${klass.name}\``);
collectionAttributes.forEach((attribute) => {
queries.push(`ANALYZE \`${tableNameForJoin(klass, attribute.itemClass)}\``)
});
}
return queries;
}
setupQueriesForTable(klass) {
const attributes = Object.keys(klass.attributes).map(k => klass.attributes[k]);
let queries = [];
// Identify attributes of this class that can be matched against. These
// attributes need their own columns in the table
const columnAttributes = attributes.filter(attr =>
attr.queryable && attr.columnSQL && attr.jsonKey !== 'id'
);
const columns = ['id TEXT PRIMARY KEY', 'data BLOB']
columnAttributes.forEach(attr => columns.push(attr.columnSQL()));
const columnsSQL = columns.join(',');
queries.unshift(`CREATE TABLE IF NOT EXISTS \`${klass.name}\` (${columnsSQL})`);
queries.push(`CREATE UNIQUE INDEX IF NOT EXISTS \`${klass.name}_id\` ON \`${klass.name}\` (\`id\`)`);
// Identify collection attributes that can be matched against. These require
// JOIN tables. (Right now the only one of these is Thread.folders or
// Thread.categories)
const collectionAttributes = attributes.filter(attr =>
attr.queryable && attr instanceof AttributeCollection
);
collectionAttributes.forEach((attribute) => {
const joinTable = tableNameForJoin(klass, attribute.itemClass);
const joinColumns = attribute.joinQueryableBy.map((name) =>
klass.attributes[name].columnSQL()
);
joinColumns.unshift('id TEXT KEY', '`value` TEXT');
queries.push(`CREATE TABLE IF NOT EXISTS \`${joinTable}\` (${joinColumns.join(',')})`);
queries.push(`CREATE INDEX IF NOT EXISTS \`${joinTable.replace('-', '_')}_id\` ON \`${joinTable}\` (\`id\` ASC)`);
queries.push(`CREATE UNIQUE INDEX IF NOT EXISTS \`${joinTable.replace('-', '_')}_val_id\` ON \`${joinTable}\` (\`value\` ASC, \`id\` ASC)`);
});
const joinedDataAttributes = attributes.filter(attr =>
attr instanceof AttributeJoinedData
)
joinedDataAttributes.forEach((attribute) => {
queries.push(`CREATE TABLE IF NOT EXISTS \`${attribute.modelTable}\` (id TEXT PRIMARY KEY, \`value\` TEXT)`);
});
if (klass.additionalSQLiteConfig && klass.additionalSQLiteConfig.setup) {
queries = queries.concat(klass.additionalSQLiteConfig.setup());
}
if (klass.searchable === true) {
queries.push(this.createSearchIndexSql(klass));
}
return queries;
}
createSearchIndexSql(klass) {
if (!klass) {
throw new Error(`RxDatabase::createSearchIndex - You must provide a class`);
}
if (!klass.searchFields) {
throw new Error(`RxDatabase::createSearchIndex - ${klass.name} must expose an array of \`searchFields\``);
}
const searchTableName = `${klass.name}Search`;
const searchFields = klass.searchFields;
return (
`CREATE VIRTUAL TABLE IF NOT EXISTS \`${searchTableName}\` ` +
`USING fts5(
tokenize='porter unicode61',
content_id UNINDEXED,
${searchFields.join(', ')}
)`
);
}
}