Home Reference Source

lib/Schema.js

  1. import {InvalidSchemaError} from './errors';
  2.  
  3.  
  4. /** @ignore */
  5. function normalize(schema) {
  6. const allowedOptions = new Set(['primary', 'unique', 'normal', 'ngram', 'fulltext', 'word']);
  7.  
  8. const result = {};
  9. for (const col in schema) {
  10. result[col] = {};
  11.  
  12. if (typeof schema[col] === 'object') {
  13. for (const opt in schema[col]) {
  14. if (!allowedOptions.has(opt)) {
  15. throw new InvalidSchemaError(opt + ' is unknown option', col);
  16. }
  17. result[col][opt] = schema[col][opt];
  18. }
  19. } else if (typeof schema[col] === 'string') {
  20. if (!allowedOptions.has(schema[col])) {
  21. throw new InvalidSchemaError(schema[col] + ' is unknown option', col);
  22. }
  23. result[col][schema[col]] = true;
  24. } else {
  25. throw new InvalidSchemaError((typeof schema[col]) + ' is invalid option type', col);
  26. }
  27. }
  28. return result;
  29. }
  30.  
  31.  
  32. /** @ignore */
  33. function schemaCheck(schema) {
  34. let primaryKey = null;
  35.  
  36. for (const col in schema) {
  37. if (schema[col].primary !== undefined) {
  38. if (typeof schema[col].primary !== 'boolean') {
  39. throw new InvalidSchemaError('"primary" option must be boolean', col);
  40. }
  41. if (schema[col].primary) {
  42. if (primaryKey !== null) {
  43. throw new InvalidSchemaError('can not use multiple primary key', [col, primaryKey]);
  44. }
  45. primaryKey = col;
  46. }
  47. }
  48.  
  49. if (schema[col].unique !== undefined) {
  50. if (typeof schema[col].unique !== 'boolean') {
  51. throw new InvalidSchemaError('"unique" option must be boolean', col);
  52. }
  53. }
  54.  
  55. if (schema[col].normal !== undefined) {
  56. if (typeof schema[col].normal !== 'boolean') {
  57. throw new InvalidSchemaError('"normal" option must be boolean', col);
  58. }
  59. }
  60.  
  61. if (schema[col].primary && schema[col].unique) {
  62. throw new InvalidSchemaError('can not enable both of "primary" option and "unique" option to same column', col);
  63. }
  64. if (schema[col].primary && schema[col].normal) {
  65. throw new InvalidSchemaError('can not enable both of "primary" option and "normal" option to same column', col);
  66. }
  67. if (schema[col].unique && schema[col].normal) {
  68. throw new InvalidSchemaError('can not enable both of "unique" option and "normal" option to same column', col);
  69. }
  70.  
  71. if (schema[col].ngram !== undefined && schema[col].fulltext !== undefined) {
  72. throw new InvalidSchemaError('can not set both of "ngram" option and "fulltext" option to same column', col);
  73. }
  74. const fts = schema[col].ngram === undefined ? schema[col].fulltext : schema[col].ngram;
  75. const ftsFrom = schema[col].ngram === undefined ? 'fulltext' : 'ngram';
  76. if (fts !== undefined && typeof fts !== 'boolean') {
  77. throw new InvalidSchemaError(`"${ftsFrom}" option must be boolean`, col);
  78. }
  79.  
  80. if (schema[col].word !== undefined && typeof schema[col].word !== 'boolean') {
  81. throw new InvalidSchemaError('"word" option must be boolean', col);
  82. }
  83. }
  84. }
  85.  
  86.  
  87. export {normalize, schemaCheck};
  88.  
  89.  
  90. /**
  91. * The database schema of IndexedFTS.
  92. */
  93. export default class IFTSSchema {
  94. /**
  95. * Create IFTSSchema.
  96. *
  97. * @param {object} schema - please see same name param of {@link IndexedFTS#constructor}.
  98. *
  99. * @throws {InvalidSchemaError}
  100. */
  101. constructor(schema) {
  102. /** @ignore */
  103. this._schema = normalize(schema);
  104.  
  105. /** @ignore */
  106. this._storeOption = {autoIncrement: true};
  107.  
  108. /**
  109. * Primary key of this schema.
  110. *
  111. * This value will be null if not set primary key.
  112. *
  113. * @type {string|null}
  114. */
  115. this.primaryKey = null;
  116.  
  117. /**
  118. * Column names that indexed with ngram for full-text search.
  119. *
  120. * @type {Set<string>}
  121. */
  122. this.ngramIndexes = new Set();
  123.  
  124. /**
  125. * Column names that indexed with word for full-text search.
  126. *
  127. * @type {Set<string>}
  128. */
  129. this.wordIndexes = new Set();
  130.  
  131. /**
  132. * Column names that unique indexed.
  133. *
  134. * @type {Set<string>}
  135. */
  136. this.uniqueIndexes = new Set();
  137.  
  138. /**
  139. * Column names that normal indexed.
  140. *
  141. * @type {Set<string>}
  142. */
  143. this.normalIndexes = new Set();
  144.  
  145. for (let x in schema) {
  146. schemaCheck(this._schema);
  147.  
  148. if (this._schema[x].primary === true) {
  149. this.primaryKey = x;
  150. this._storeOption = {keyPath: x};
  151. } else if (this._schema[x].unique === true) {
  152. this.uniqueIndexes.add(x);
  153. } else if (this._schema[x].normal !== false) {
  154. this.normalIndexes.add(x);
  155. }
  156.  
  157. if (this._schema[x].ngram || this._schema[x].fulltext) {
  158. this.ngramIndexes.add(x);
  159. }
  160.  
  161. if (this._schema[x].word) {
  162. this.wordIndexes.add(x);
  163. }
  164. }
  165. }
  166.  
  167. /**
  168. * All column names that indexed in some way.
  169. *
  170. * @type {Set<string>}
  171. */
  172. get indexes() {
  173. if (this.primaryKey) {
  174. return new Set([this.primaryKey, ...this.uniqueIndexes, ...this.normalIndexes]);
  175. } else {
  176. return new Set([...this.uniqueIndexes, ...this.normalIndexes]);
  177. }
  178. }
  179. }