UNPKG

@mysql/xdevapi

Version:

MySQL Connector/Node.js - A Node.js driver for MySQL using the X Protocol and X DevAPI.

325 lines (294 loc) 13.7 kB
/* * Copyright (c) 2015, 2022, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is also distributed with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, * as designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an * additional permission to link the program and your derivative works * with the separately licensed software that they have included with * MySQL. * * Without limiting anything contained in the foregoing, this file, * which is part of MySQL Connector/Node.js, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * http://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ 'use strict'; const DatabaseObject = require('./DatabaseObject'); const ProjectedSearchExprStr = require('./ProjectedSearchExprStr'); const SqlExecute = require('./SqlExecute'); const TableDelete = require('./TableDelete'); const TableInsert = require('./TableInsert'); const TableSelect = require('./TableSelect'); const TableUpdate = require('./TableUpdate'); const errors = require('../constants/errors'); const escapeQuotes = require('./Util/escapeQuotes'); const logger = require('../logger'); const warnings = require('../constants/warnings'); /** * Table factory. * @module Table * @mixes DatabaseObject */ const log = logger('api:table'); /** * @private * @alias module:Table * @param {Connection} connection - The instance of the current * database connection. * @param {module:Schema} schema - The instance of the schema where statements * will be executed. * @param {string} tableName - The name of the underlying database table. * @returns {module:Table} */ function Table ({ connection, schema, tableName } = {}) { return { ...DatabaseObject(connection), /** * Retrieves the total number of documents in the table. * This method executes a statement in the database. * @function * @name module:Table#count * @returns {Promise<number>} A <code>Promise</code> that resolves to * the number of documents in the table. */ count: function () { const escapedSchemaName = Table.escapeIdentifier(schema.getName()); const escapedTableName = Table.escapeIdentifier(tableName); let count = 0; const callback = row => { count = row[0]; }; return SqlExecute(connection, `SELECT COUNT(*) FROM ${escapedSchemaName}.${escapedTableName}`) .execute(callback) .then(() => count); }, /** * Creates a statement that updates all rows that match a criteria. * The criteria is required and should be defined using the * {@link module:TableFiltering#where|TableFiltering.where()} method. * This method does not cause the statement to be executed. * @function * @name module:Table#delete * @example * // delete all rows from a table * table.delete('true') * * // delete rows that match a given criteria * table.delete('`name` == "foobar"') * @returns {module:TableDelete} A new instance of a statement * containing the filtering criteria which will be used for * determining which rows will be deleted. * @see {@link https://dev.mysql.com/doc/x-devapi-userguide/en/crud-ebnf-table-crud-functions.html#crud-ebnf-tabledeletefunction|TableDeleteFunction} */ delete (searchConditionStr) { // The criteria should be defined using the TableFiltering.where() // method. Although it still works, defining the criteria in the // call to Table.delete() is deprecated. if (typeof searchConditionStr !== 'undefined') { log.warning('delete', warnings.MESSAGES.WARN_DEPRECATED_TABLE_DELETE_EXPR_ARGUMENT, { type: warnings.TYPES.DEPRECATION, code: warnings.CODES.DEPRECATION }); } return TableDelete({ connection, schema, tableName }).where(searchConditionStr); }, /** * Checks if this table exists in the database. * This method executes a statement in the database. * @function * @name module:Table#existsInDatabase * @returns {Promise<boolean>} A <code>Promise</code> that resolves to a * boolean value which indicates whether the table exists or not. */ existsInDatabase () { const args = [{ schema: schema.getName(), pattern: tableName }]; return SqlExecute(connection, 'list_objects', args, SqlExecute.Namespace.X_PLUGIN) .execute() .then(res => { return res.fetchAll().some(record => record[1] === 'TABLE'); }); }, /** * Retrieves the table name. * This method works with the local table instance and does not * execute any statement in the database. * @function * @name module:Table#getName * @returns {string} The Name of the table. */ getName () { return tableName; }, /** * Retrieves the instance of the schema where the table lives under. * This method works with the local table instance and does not * execute any statement in the database. * @function * @name module:Table#getSchema * @returns {module:Schema} The instance of the schema where * statements will be executed. */ getSchema () { return schema; }, /** * Retrieves the table metadata. * This method works with the local table instance and does not * execute any statement in the database. * @function * @name module:Table#inspect * @returns {Object} An Object containing metadata about the table. */ inspect () { return { schema: schema ? schema.getName() : schema, table: tableName }; }, /** * Creates a statement that inserts one or more rows in the * table. * This method does not cause the statement to be executed. * @function * @name module:Table#insert * @param {...TableFields} tableFields - The Names of the columns * where the values will be inserted for each row. * @example * // arguments as column names * table.insert('foo', 'bar') * * // array of column names * table.insert(['foo', 'bar']) * * // object with column name and value * table.insert({ foo: 'baz', bar: 'qux' }) * @throws Column name is not a valid string or X DevAPI expression instance. * @returns {module:TableInsert} A new instance of a statement * containing the column names of the rows to be inserted. * @see {@link https://dev.mysql.com/doc/x-devapi-userguide/en/crud-ebnf-table-crud-functions.html#crud-ebnf-tableinsertfunction|TableInsertFunction} */ insert (...tableFields) { const columns = tableFields.flat() .filter(tableField => typeof tableField !== 'undefined'); const isNonEmptyObject = // is an object typeof columns[0] === 'object' && // is not an array !Array.isArray(columns[0]) && // is not null Object(columns[0]) === columns[0] && // is not empty Object.keys(columns[0]).length > 0; // It is possible to support an object containing the mapping // between the column names and values, but this behavior is // deprecated. if (isNonEmptyObject) { log.warning('insert', warnings.MESSAGES.WARN_DEPRECATED_TABLE_INSERT_OBJECT_ARGUMENT, { type: warnings.TYPES.DEPRECATION, code: warnings.CODES.DEPRECATION }); return TableInsert({ connection, schema, tableName, columns: Object.keys(columns[0]) }) .values(Object.values(columns[0])); } if (!columns.length || columns.some(field => typeof field !== 'string')) { throw new Error(errors.MESSAGES.ER_DEVAPI_BAD_TABLE_INSERT_ARGUMENT); } return TableInsert({ connection, schema, tableName, columns }); }, /** * Checks if the table is a view (virtual) or an actual table. * This method executes a statement in the database. * @function * @name module:Table#isView * @returns {Promise.<boolean>} A <code>Promise</code> that resolves to * a boolean that indicates whether the table is a view or not. */ isView () { const sqlStatement = 'SELECT COUNT(*) cnt FROM information_schema.VIEWS WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; const args = ['def', schema.getName(), tableName]; let status = false; const callback = found => { status = !!found.length; }; return SqlExecute(connection, sqlStatement, args) .execute(callback) .then(() => status); }, /** * Creates a statement that retrieves the values for the given * column names on each table row that matches a criteria. * The criteria is optional and should be defined using the * {@link module:TableFiltering#where|TableFiltering.where()} method. * This method does not cause the statement to be executed. * @function * @name module:Table#select * @param {...ProjectedSearchExprStrList} [projectedSearchExprStrs] - * One or more names of columns to include in the result set. * @example * // all columns should be projected * const selection = table.select() * * // arguments as columns to be projected * const selection = table.select('foo', 'bar') * * // array of columns to be projected * const selection = table.select(['foo', 'bar']) * @returns {module:TableSelect} A new instance of a statement * containing the names of the columns to include in the result set * for all the rows that match the criteria. * @see {@link https://dev.mysql.com/doc/x-devapi-userguide/en/crud-ebnf-table-crud-functions.html#crud-ebnf-tableselectfunction|TableSelectFunction} */ select (...projectedSearchExprStrList) { const projectionList = projectedSearchExprStrList.flat() .map(projectedSearchExprStr => ProjectedSearchExprStr(projectedSearchExprStr).getValue()); return TableSelect({ connection, projectionList, schema, tableName }); }, /** * Creates a statement that updates all rows that match a criteria, * The criteria is required and should be defined using the * {@link module:TableFiltering#where|TableFiltering.where()} method. * This method does not cause the statement to be executed. * @function * @name module:Table#update * @example * // update all rows in a table * table.update('true').set('name', 'foo') * table.update().where('true').set('name', 'foo') * * // update rows that match a given criteria * table.update().where('`name` == "foo"').set('name', 'bar') * @returns {module:TableUpdate} A new instance of a statement * containing the filtering criteria which will be used for * determining which rows will be updated. * @see {@link https://dev.mysql.com/doc/x-devapi-userguide/en/crud-ebnf-table-crud-functions.html#crud-ebnf-tableupdatefunction|TableUpdateFunction} */ update (searchConditionStr) { // The criteria should be defined using the TableFiltering.where() // method. Although it still works, defining the criteria in the // call to Table.update() is deprecated. if (typeof searchConditionStr !== 'undefined') { log.warning('update', warnings.MESSAGES.WARN_DEPRECATED_TABLE_UPDATE_EXPR_ARGUMENT, { type: warnings.TYPES.DEPRECATION, code: warnings.CODES.DEPRECATION }); } return TableUpdate({ connection, schema, tableName }).where(searchConditionStr); } }; } /** * Internal utility function. */ // TODO(Rui): refactor somehow. Table.escapeIdentifier = function (ident) { return '`' + escapeQuotes(ident) + '`'; }; module.exports = Table;