Home Manual Reference Source Test

lib/database-setup-query-builder.js

  1. /* eslint global-require:0 */
  2. import ModelRegistry from './model-registry';
  3. import {tableNameForJoin} from './utils';
  4. import Attributes from './attributes';
  5.  
  6. const {AttributeCollection, AttributeJoinedData} = Attributes;
  7.  
  8. /**
  9. The factory methods in this class assemble SQL queries that build Model
  10. tables based on their attribute schema.
  11.  
  12. @private
  13. */
  14. export default class DatabaseSetupQueryBuilder {
  15.  
  16. setupQueries() {
  17. let queries = []
  18. for (const klass of ModelRegistry.getAllConstructors()) {
  19. queries = queries.concat(this.setupQueriesForTable(klass));
  20. }
  21. return queries;
  22. }
  23.  
  24. analyzeQueries() {
  25. const queries = [];
  26.  
  27. for (const klass of ModelRegistry.getAllConstructors()) {
  28. const attributes = Object.keys(klass.attributes).map(k => klass.attributes[k]);
  29. const collectionAttributes = attributes.filter((attr) =>
  30. attr.queryable && attr instanceof AttributeCollection
  31. )
  32.  
  33. queries.push(`ANALYZE \`${klass.name}\``);
  34. collectionAttributes.forEach((attribute) => {
  35. queries.push(`ANALYZE \`${tableNameForJoin(klass, attribute.itemClass)}\``)
  36. });
  37. }
  38. return queries;
  39. }
  40.  
  41. setupQueriesForTable(klass) {
  42. const attributes = Object.keys(klass.attributes).map(k => klass.attributes[k]);
  43. let queries = [];
  44.  
  45. // Identify attributes of this class that can be matched against. These
  46. // attributes need their own columns in the table
  47. const columnAttributes = attributes.filter(attr =>
  48. attr.queryable && attr.columnSQL && attr.jsonKey !== 'id'
  49. );
  50.  
  51. const columns = ['id TEXT PRIMARY KEY', 'data BLOB']
  52. columnAttributes.forEach(attr => columns.push(attr.columnSQL()));
  53.  
  54. const columnsSQL = columns.join(',');
  55. queries.unshift(`CREATE TABLE IF NOT EXISTS \`${klass.name}\` (${columnsSQL})`);
  56. queries.push(`CREATE UNIQUE INDEX IF NOT EXISTS \`${klass.name}_id\` ON \`${klass.name}\` (\`id\`)`);
  57.  
  58. // Identify collection attributes that can be matched against. These require
  59. // JOIN tables. (Right now the only one of these is Thread.folders or
  60. // Thread.categories)
  61. const collectionAttributes = attributes.filter(attr =>
  62. attr.queryable && attr instanceof AttributeCollection
  63. );
  64. collectionAttributes.forEach((attribute) => {
  65. const joinTable = tableNameForJoin(klass, attribute.itemClass);
  66. const joinColumns = attribute.joinQueryableBy.map((name) =>
  67. klass.attributes[name].columnSQL()
  68. );
  69. joinColumns.unshift('id TEXT KEY', '`value` TEXT');
  70.  
  71. queries.push(`CREATE TABLE IF NOT EXISTS \`${joinTable}\` (${joinColumns.join(',')})`);
  72. queries.push(`CREATE INDEX IF NOT EXISTS \`${joinTable.replace('-', '_')}_id\` ON \`${joinTable}\` (\`id\` ASC)`);
  73. queries.push(`CREATE UNIQUE INDEX IF NOT EXISTS \`${joinTable.replace('-', '_')}_val_id\` ON \`${joinTable}\` (\`value\` ASC, \`id\` ASC)`);
  74. });
  75.  
  76. const joinedDataAttributes = attributes.filter(attr =>
  77. attr instanceof AttributeJoinedData
  78. )
  79.  
  80. joinedDataAttributes.forEach((attribute) => {
  81. queries.push(`CREATE TABLE IF NOT EXISTS \`${attribute.modelTable}\` (id TEXT PRIMARY KEY, \`value\` TEXT)`);
  82. });
  83.  
  84. if (klass.additionalSQLiteConfig && klass.additionalSQLiteConfig.setup) {
  85. queries = queries.concat(klass.additionalSQLiteConfig.setup());
  86. }
  87.  
  88. if (klass.searchable === true) {
  89. queries.push(this.createSearchIndexSql(klass));
  90. }
  91.  
  92. return queries;
  93. }
  94.  
  95. createSearchIndexSql(klass) {
  96. if (!klass) {
  97. throw new Error(`RxDatabase::createSearchIndex - You must provide a class`);
  98. }
  99. if (!klass.searchFields) {
  100. throw new Error(`RxDatabase::createSearchIndex - ${klass.name} must expose an array of \`searchFields\``);
  101. }
  102. const searchTableName = `${klass.name}Search`;
  103. const searchFields = klass.searchFields;
  104. return (
  105. `CREATE VIRTUAL TABLE IF NOT EXISTS \`${searchTableName}\` ` +
  106. `USING fts5(
  107. tokenize='porter unicode61',
  108. content_id UNINDEXED,
  109. ${searchFields.join(', ')}
  110. )`
  111. );
  112. }
  113. }