jii
Version:
Jii - Full-Stack JavaScript Framework
486 lines (425 loc) • 16.1 kB
JavaScript
/**
* @author <a href="http://www.affka.ru">Vladimir Kozhin</a>
* @license MIT
*/
;
const Jii = require('../BaseJii');
const NotSupportedException = require('../exceptions/NotSupportedException');
const QueryBuilder = require('./QueryBuilder');
const FilterBuilder = require('../data/FilterBuilder');
const ColumnSchemaBuilder = require('./ColumnSchemaBuilder');
const _isUndefined = require('lodash/isUndefined');
const _each = require('lodash/each');
const _has = require('lodash/has');
const BaseObject = require('../base/BaseObject');
const ColumnSchema = require('./ColumnSchema');
class BaseSchema extends BaseObject {
preInit() {
/**
* @type {FilterBuilder}
*/
this._filterBuilder = null;
/**
* @type {QueryBuilder}
*/
this._builder = null;
/**
* @var array list of loaded table metadata (table name: TableSchema)
*/
this._tables = {};
/**
* @var array list of ALL table names in the database
*/
this._tableNames = {};
/**
* @var string the default schema name used for the current session.
*/
this.defaultSchema = null;
/**
* @type {Connection} the database connection
*/
this.db = null;
super.preInit(...arguments);
}
getTableSchema(name) {
name = this.getRawTableName(name);
return this._tables[name] || null;
}
/**
* Returns the metadata for all tables in the database.
* @param {string} [schema] the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* @param {boolean} [refresh] whether to fetch the latest available table schemas. If this is false,
* cached data may be returned if available.
* @return {Promise} the metadata for all tables in the database.
* Each array element is an instance of [[TableSchema]] or its child class.
*/
loadTableSchemas(schema, refresh) {
schema = schema || '';
refresh = refresh || false;
return this.loadTableNames(schema, refresh).then(tableNames => {
var tables = [];
var promises = [];
_each(tableNames, name => {
if (schema !== '') {
name = schema + '.' + name;
}
var promise = this.loadTableSchema(name, refresh).then(table => {
if (table !== null) {
tables.push(table);
}
return Promise.resolve();
});
promises.push(promise);
});
return Promise.all(promises).then(() => {
return tables;
});
});
}
getTableNames() {
return this._tableNames[''];
}
/**
* Returns all table names in the database.
* @param {string} [schema] the schema of the tables. Defaults to empty string, meaning the current or default schema name.
* If not empty, the returned table names will be prefixed with the schema name.
* @param {boolean} [refresh] whether to fetch the latest available table names. If this is false,
* table names fetched previously (if available) will be returned.
* @return {Promise} all table names in the database.
*/
loadTableNames(schema, refresh) {
schema = schema || '';
refresh = refresh || false;
if (_has(this._tableNames, schema) && !refresh) {
return Promise.resolve(this._tableNames[schema]);
}
return this._findTableNames(schema).then(tableNames => {
this._tableNames[schema] = tableNames;
return this._tableNames[schema];
});
}
/**
* Obtains the metadata for the named table.
* @param {string} name table name. The table name may contain schema name if any. Do not quote the table name.
* @param {boolean} [refresh] whether to reload the table schema even if it is found in the cache.
* @return TableSchema table metadata. Null if the named table does not exist.
*/
loadTableSchema(name, refresh) {
refresh = refresh || false;
if (_has(this._tables, name) && !refresh) {
return Promise.resolve(this._tables[name]);
}
var realName = this.getRawTableName(name);
return this._loadTableSchema(realName).then(table => {
this._tables[name] = table;
return this._tables[name];
});
}
/**
* Loads the metadata for the specified table.
* @param {string} name table name
* @return {TableSchema} DBMS-dependent table metadata, null if the table does not exist.
*/
_loadTableSchema(name) {
throw new NotSupportedException('Not implemented');
}
/**
* @return {QueryBuilder} the query builder for this connection.
*/
getQueryBuilder() {
if (this._builder === null) {
this._builder = this.createQueryBuilder();
}
return this._builder;
}
/**
* @return {FilterBuilder} the query builder for this connection.
*/
getFilterBuilder() {
if (this._filterBuilder === null) {
this._filterBuilder = this.createFilterBuilder();
}
return this._filterBuilder;
}
/**
* Refreshes the schema.
* This method cleans up all cached table schemas so that they can be re-created later
* to reflect the database schema change.
*/
refresh() {
/** @var Cache cache */
this._tableNames = {};
this._tables = {};
return this.loadTableSchemas();
}
/**
* Creates a query builder for the database.
* This method may be overridden by child classes to create a DBMS-specific query builder.
* @return {QueryBuilder} query builder instance
*/
createQueryBuilder() {
return new QueryBuilder(this.db);
}
/**
* @return {FilterBuilder}
*/
createFilterBuilder() {
return new FilterBuilder();
}
/**
* Create a column schema builder instance giving the type and value precision.
*
* This method may be overridden by child classes to create a DBMS-specific column schema builder.
*
* @param {string} type type of the column. See [[ColumnSchemaBuilder.type]].
* @param {number|string|[]} length length or precision of the column. See [[ColumnSchemaBuilder.length]].
* @returns {ColumnSchemaBuilder} column schema builder instance
* @since 2.0.6
*/
createColumnSchemaBuilder(type, length) {
length = length || null;
return new ColumnSchemaBuilder(type, length);
}
/**
* @return {ColumnSchema}
*/
_createColumnSchema() {
return Jii.createObject(ColumnSchema);
}
/**
* Returns all table names in the database.
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception.
* @param {string} [schema] the schema of the tables. Defaults to empty string, meaning the current or default schema.
* @return array all table names in the database. The names have NO schema name prefix.
* @throws NotSupportedException if this method is called
*/
_findTableNames(schema) {
throw new NotSupportedException(this.className() + ' does not support fetching all table names.');
}
/**
* Returns all unique indexes for the given table.
* Each array element is of the following structure:
*
* ~~~
* {
* IndexName1: ['col1' [, ...]],
* IndexName2: ['col2' [, ...]],
* }
* ~~~
*
* This method should be overridden by child classes in order to support this feature
* because the default implementation simply throws an exception
* @param {TableSchema} table the table metadata
* @return {[]} all unique indexes for the given table.
* @throws NotSupportedException if this method is called
*/
findUniqueIndexes(table) {
throw new NotSupportedException(this.className() + ' does not support getting unique indexes information.');
}
/**
* @return boolean whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
*/
/*supportsSavepoint: function() {
return this.db.enableSavepoint;
},*/
/**
* Creates a new savepoint.
* @param string name the savepoint name
*/
/*createSavepoint: function(name)
{
this.db.createCommand("SAVEPOINT name").execute();
},*/
/**
* Releases an existing savepoint.
* @param string name the savepoint name
*/
/*releaseSavepoint: function(name)
{
this.db.createCommand("RELEASE SAVEPOINT name").execute();
},*/
/**
* Rolls back to a previously created savepoint.
* @param string name the savepoint name
*/
/*rollBackSavepoint: function(name)
{
this.db.createCommand("ROLLBACK TO SAVEPOINT name").execute();
},*/
/**
* Quotes a string value for use in a query.
* @param {string} str string to be quoted
* @return {string} the properly quoted string
*/
quoteValue(str) {
if (_isUndefined(str) || str === null) {
return 'NULL';
}
switch (typeof str) {
case 'boolean':
return str ? 'true' : 'false';
case 'number':
return str + '';
case 'string':
str = str.replace(/[\n\r\b\t\\'"\x1a]/g, s => {
switch (s) {
case '\0':
return '\\0';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '\b':
return '\\b';
case '\t':
return '\\t';
case '\x1A':
return '\\Z';
default:
return '\\' + s;
}
});
return '\'' + str + '\'';
}
throw new NotSupportedException('BaseSchema.quote() not support `' + typeof str + '` value: ' + str);
}
/**
* Quotes a table name for use in a query.
* If the table name contains schema prefix, the prefix will also be properly quoted.
* If the table name is already quoted or contains '(' or '{{',
* then this method will do nothing.
* @param {string} name table name
* @return {string} the properly quoted table name
*/
quoteTableName(name) {
if (name.indexOf('(') !== -1 || name.indexOf('{{') !== -1) {
return name;
}
if (name.indexOf('.') === -1) {
return this.quoteSimpleTableName(name);
}
var parts = name.split('.');
_each(parts, (part, i) => {
parts[i] = this.quoteSimpleTableName(part);
});
return parts.join('.');
}
/**
* Quotes a column name for use in a query.
* If the column name contains prefix, the prefix will also be properly quoted.
* If the column name is already quoted or contains '(', '[[' or '{{',
* then this method will do nothing.
* @param {string} name column name
* @return {string} the properly quoted column name
* @see quoteSimpleColumnName()
*/
quoteColumnName(name) {
if (name.indexOf('(') !== -1 || name.indexOf('[[') !== -1 || name.indexOf('{{') !== -1) {
return name;
}
var prefix = '';
var pos = name.lastIndexOf('.');
if (pos !== -1) {
prefix = this.quoteTableName(name.substr(0, pos)) + '.';
name = name.substr(pos + 1);
}
return prefix + this.quoteSimpleColumnName(name);
}
/**
* Quotes a simple table name for use in a query.
* A simple table name should contain the table name only without any schema prefix.
* If the table name is already quoted, this method will do nothing.
* @param {string} name table name
* @return {string} the properly quoted table name
*/
quoteSimpleTableName(name) {
return name.indexOf('\'') !== -1 ? name : '\'' + name + '\'';
}
/**
* Quotes a simple column name for use in a query.
* A simple column name should contain the column name only without any prefix.
* If the column name is already quoted or is the asterisk character '*', this method will do nothing.
* @param {string} name column name
* @return {string} the properly quoted column name
*/
quoteSimpleColumnName(name) {
return name.indexOf('\'') !== -1 || name === '*' ? name : '"' + name + '"';
}
/**
* Returns the actual name of a given table name.
* This method will strip off curly brackets from the given table name
* and replace the percentage character '%' with [[Connection::tablePrefix]].
* @param {string} name the table name to be converted
* @return {string} the real name of the given table name
*/
getRawTableName(name) {
if (name.indexOf('{{') !== -1) {
name = name.replace(/\{\{(.*?)\}\}/g, '$1');
name = name.replace(/%/g, this.db.tablePrefix);
}
return name;
}
/**
* Extracts the JS type from abstract DB type.
* @param {ColumnSchema} column the column schema information
* @return {string} JS type name
*/
_getColumnJsType(column) {
switch (column.type) {
case BaseSchema.TYPE_SMALLINT:
case BaseSchema.TYPE_INTEGER:
return column.unsigned ? 'string' : 'number';
case BaseSchema.TYPE_BOOLEAN:
return 'boolean';
case BaseSchema.TYPE_FLOAT:
case BaseSchema.TYPE_DECIMAL:
return 'number';
}
return 'string';
}
/**
* Handles database error
*
* @param \Exception e
* @param string rawSql SQL that produced exception
* @throws Exception
*/
/*handleException: function(\Exception e, rawSql)
{
if (e instanceof Exception) {
throw e;
} else {
exceptionClass = '\yii\db\Exception';
foreach (this.exceptionMap as error: class) {
if (strpos(e.getMessage(), error) !== false) {
exceptionClass = class;
}
}
message = e.getMessage() . "\nThe SQL being executed was: rawSql";
errorInfo = e instanceof \PDOException ? e.errorInfo : null;
throw new exceptionClass(message, errorInfo, (int) e.getCode(), e);
}
}*/
}
BaseSchema.TYPE_MONEY = 'money';
BaseSchema.TYPE_BOOLEAN = 'boolean';
BaseSchema.TYPE_BINARY = 'binary';
BaseSchema.TYPE_DATE = 'date';
BaseSchema.TYPE_TIME = 'time';
BaseSchema.TYPE_TIMESTAMP = 'timestamp';
BaseSchema.TYPE_DATETIME = 'datetime';
BaseSchema.TYPE_DECIMAL = 'decimal';
BaseSchema.TYPE_DOUBLE = 'double';
BaseSchema.TYPE_FLOAT = 'float';
BaseSchema.TYPE_BIGINT = 'bigint';
BaseSchema.TYPE_INTEGER = 'integer';
BaseSchema.TYPE_SMALLINT = 'smallint';
BaseSchema.TYPE_TEXT = 'text';
BaseSchema.TYPE_STRING = 'string';
BaseSchema.TYPE_BIGPK = 'bigpk';
/**
* The followings are the supported abstract column data types.
*/
BaseSchema.TYPE_PK = 'pk';
module.exports = BaseSchema;