Home Reference Source

lib/Schema.js

import {InvalidSchemaError} from './errors';


/** @ignore */
function normalize(schema) {
	const allowedOptions = new Set(['primary', 'unique', 'normal', 'ngram', 'fulltext', 'word']);

	const result = {};
	for (const col in schema) {
		result[col] = {};

		if (typeof schema[col] === 'object') {
			for (const opt in schema[col]) {
				if (!allowedOptions.has(opt)) {
					throw new InvalidSchemaError(opt + ' is unknown option', col);
				}
				result[col][opt] = schema[col][opt];
			}
		} else if (typeof schema[col] === 'string') {
			if (!allowedOptions.has(schema[col])) {
				throw new InvalidSchemaError(schema[col] + ' is unknown option', col);
			}
			result[col][schema[col]] = true;
		} else {
			throw new InvalidSchemaError((typeof schema[col]) + ' is invalid option type', col);
		}
	}
	return result;
}


/** @ignore */
function schemaCheck(schema) {
	let primaryKey = null;

	for (const col in schema) {
		if (schema[col].primary !== undefined) {
			if (typeof schema[col].primary !== 'boolean') {
				throw new InvalidSchemaError('"primary" option must be boolean', col);
			}
			if (schema[col].primary) {
				if (primaryKey !== null) {
					throw new InvalidSchemaError('can not use multiple primary key', [col, primaryKey]);
				}
				primaryKey = col;
			}
		}

		if (schema[col].unique !== undefined) {
			if (typeof schema[col].unique !== 'boolean') {
				throw new InvalidSchemaError('"unique" option must be boolean', col);
			}
		}

		if (schema[col].normal !== undefined) {
			if (typeof schema[col].normal !== 'boolean') {
				throw new InvalidSchemaError('"normal" option must be boolean', col);
			}
		}

		if (schema[col].primary && schema[col].unique) {
			throw new InvalidSchemaError('can not enable both of "primary" option and "unique" option to same column', col);
		}
		if (schema[col].primary && schema[col].normal) {
			throw new InvalidSchemaError('can not enable both of "primary" option and "normal" option to same column', col);
		}
		if (schema[col].unique && schema[col].normal) {
			throw new InvalidSchemaError('can not enable both of "unique" option and "normal" option to same column', col);
		}

		if (schema[col].ngram !== undefined && schema[col].fulltext !== undefined) {
			throw new InvalidSchemaError('can not set both of "ngram" option and "fulltext" option to same column', col);
		}
		const fts = schema[col].ngram === undefined ? schema[col].fulltext : schema[col].ngram;
		const ftsFrom = schema[col].ngram === undefined ? 'fulltext' : 'ngram';
		if (fts !== undefined && typeof fts !== 'boolean') {
			throw new InvalidSchemaError(`"${ftsFrom}" option must be boolean`, col);
		}

		if (schema[col].word !== undefined && typeof schema[col].word !== 'boolean') {
			throw new InvalidSchemaError('"word" option must be boolean', col);
		}
	}
}


export {normalize, schemaCheck};


/**
 * The database schema of IndexedFTS.
 */
export default class IFTSSchema {
	/**
	 * Create IFTSSchema.
	 *
	 * @param {object} schema - please see same name param of {@link IndexedFTS#constructor}.
	 *
	 * @throws {InvalidSchemaError}
	 */
	constructor(schema) {
		/** @ignore */
		this._schema = normalize(schema);

		/** @ignore */
		this._storeOption = {autoIncrement: true};

		/**
		 * Primary key of this schema.
		 *
		 * This value will be null if not set primary key.
		 *
		 * @type {string|null}
		 */
		this.primaryKey = null;

		/**
		 * Column names that indexed with ngram for full-text search.
		 *
		 * @type {Set<string>}
		 */
		this.ngramIndexes = new Set();

		/**
		 * Column names that indexed with word for full-text search.
		 *
		 * @type {Set<string>}
		 */
		this.wordIndexes = new Set();

		/**
		 * Column names that unique indexed.
		 *
		 * @type {Set<string>}
		 */
		this.uniqueIndexes = new Set();

		/**
		 * Column names that normal indexed.
		 *
		 * @type {Set<string>}
		 */
		this.normalIndexes = new Set();

		for (let x in schema) {
			schemaCheck(this._schema);

			if (this._schema[x].primary === true) {
				this.primaryKey = x;
				this._storeOption = {keyPath: x};
			} else if (this._schema[x].unique === true) {
				this.uniqueIndexes.add(x);
			} else if (this._schema[x].normal !== false) {
				this.normalIndexes.add(x);
			}

			if (this._schema[x].ngram || this._schema[x].fulltext) {
				this.ngramIndexes.add(x);
			}

			if (this._schema[x].word) {
				this.wordIndexes.add(x);
			}
		}
	}

	/**
	 * All column names that indexed in some way.
	 *
	 * @type {Set<string>}
	 */
	get indexes() {
		if (this.primaryKey) {
			return new Set([this.primaryKey, ...this.uniqueIndexes, ...this.normalIndexes]);
		} else {
			return new Set([...this.uniqueIndexes, ...this.normalIndexes]);
		}
	}
}