不要怂,就是干,撸起袖子干!

Commit c6d73338 by diego dupin Committed by Sushant

feat(dialect): mariadb (#10192)

1 parent e4218a23
Showing with 1732 additions and 312 deletions
......@@ -27,14 +27,10 @@ env:
- COVERAGE=true
before_script:
# mount ramdisk
- "if [ $POSTGRES_VER ]; then sudo mkdir /mnt/sequelize-postgres-ramdisk; fi"
- "if [ $POSTGRES_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-postgres-ramdisk; fi"
- "if [ $MYSQL_VER ]; then sudo mkdir /mnt/sequelize-mysql-ramdisk; fi"
- "if [ $MYSQL_VER ]; then sudo mount -t ramfs tmpfs /mnt/sequelize-mysql-ramdisk; fi"
# setup docker
- "if [ $POSTGRES_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MYSQL_VER}; fi"
- "if [ $MARIADB_VER ]; then export MARIADB_ENTRYPOINT=$TRAVIS_BUILD_DIR/test/config/mariadb; fi"
- "if [ $POSTGRES_VER ] || [ $MARIADB_VER ] || [ $MYSQL_VER ]; then docker-compose up -d ${POSTGRES_VER} ${MARIADB_VER} ${MYSQL_VER}; fi"
- "if [ $MARIADB_VER ]; then docker run --link ${MARIADB_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi"
- "if [ $MYSQL_VER ]; then docker run --link ${MYSQL_VER}:db -e CHECK_PORT=3306 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi"
- "if [ $POSTGRES_VER ]; then docker run --link ${POSTGRES_VER}:db -e CHECK_PORT=5432 -e CHECK_HOST=db --net sequelize_default giorgos/takis; fi"
......@@ -51,6 +47,10 @@ jobs:
- stage: test
node_js: '6'
sudo: required
env: MARIADB_VER=mariadb-103 SEQ_MARIADB_PORT=8960 DIALECT=mariadb
- stage: test
node_js: '6'
sudo: required
env: MYSQL_VER=mysql-57 SEQ_MYSQL_PORT=8980 DIALECT=mysql
- stage: test
node_js: '6'
......
......@@ -12,7 +12,7 @@
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
[![Greenkeeper badge](https://badges.greenkeeper.io/sequelize/sequelize.svg)](https://greenkeeper.io/)
Sequelize is a promise-based Node.js ORM for Postgres, MySQL, SQLite and Microsoft SQL Server. It features solid transaction support, relations, read replication and more.
Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, read replication and more.
## v5 Beta Release
......@@ -38,6 +38,7 @@ $ npm install --save sequelize
# And one of the following:
$ npm install --save pg pg-hstore
$ npm install --save mysql2
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious # MSSQL
```
......
......@@ -34,11 +34,26 @@ services:
POSTGRES_PASSWORD: sequelize_test
POSTGRES_DB: sequelize_test
volumes:
- /mnt/sequelize-postgres-10-ramdisk:/var/lib/postgresql/data
- /mnt/sequelize-postgres-10-ramdisk:/var/lib/postgresql/data
ports:
- "8991:5432"
- "8991:5432"
container_name: postgres-10
# MariaDB
mariadb-103:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: lollerskates
MYSQL_DATABASE: sequelize_test
MYSQL_USER: sequelize_test
MYSQL_PASSWORD: sequelize_test
volumes:
- /mnt/sequelize-mariadb-103-ramdisk:/var/lib/mysql
- $MARIADB_ENTRYPOINT:/docker-entrypoint-initdb.d
ports:
- "8960:3306"
container_name: mariadb-103
# MySQL
mysql-57:
image: mysql:5.7
......
......@@ -11,6 +11,7 @@ $ npm install --save sequelize
# And one of the following:
$ npm install --save pg pg-hstore
$ npm install --save mysql2
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious // MSSQL
......@@ -20,6 +21,7 @@ $ yarn add sequelize
# And one of the following:
$ yarn add pg pg-hstore
$ yarn add mysql2
$ yarn add mariadb
$ yarn add sqlite3
$ yarn add tedious // MSSQL
```
......@@ -32,7 +34,7 @@ Sequelize will setup a connection pool on initialization so you should ideally o
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'|'sqlite'|'postgres'|'mssql',
dialect: 'mysql'|'mariadb'|'sqlite'|'postgres'|'mssql',
pool: {
max: 5,
......
......@@ -10,7 +10,7 @@
[![npm](https://img.shields.io/npm/v/sequelize.svg?style=flat-square)](https://github.com/sequelize/sequelize/releases)
[![Slack Status](http://sequelize-slack.herokuapp.com/badge.svg)](http://sequelize-slack.herokuapp.com/)
Sequelize is a promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and more.
Sequelize is a promise-based ORM for Node.js v4 and up. It supports the dialects PostgreSQL, MariaDB, MySQL, SQLite and MSSQL and features solid transaction support, relations, read replication and more.
## Example usage
......@@ -18,7 +18,7 @@ Sequelize is a promise-based ORM for Node.js v4 and up. It supports the dialects
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'mysql'|'sqlite'|'postgres'|'mssql',
dialect: 'mysql'|'mariadb'|'sqlite'|'postgres'|'mssql',
pool: {
max: 5,
......
......@@ -314,7 +314,7 @@ const connection = new Sequelize(db, user, pass, { operatorsAliases });
### JSON
The JSON data type is supported by the PostgreSQL, SQLite and MySQL dialects only.
The JSON data type is supported by the PostgreSQL, SQLite, MySQL and MariaDB dialects only.
#### PostgreSQL
......
......@@ -170,7 +170,22 @@ const sequelize = new Sequelize('database', 'username', 'password', {
**Note:** You can pass options directly to dialect library by setting the
`dialectOptions` parameter. See [Options][0]
for examples (currently only mysql is supported).
### MariaDB
Library for MariaDB is `mariadb`.
```js
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mariadb',
dialectOptions: {connectTimeout: 1000} // mariadb connector option
})
```
or using connection String:
```js
const sequelize = new Sequelize('mariadb://user:password@example.com:9821/database')
```
### SQLite
......
......@@ -954,8 +954,7 @@ ARRAY.is = function is(obj, type) {
/**
* A column storing Geometry information.
* It is only available in PostgreSQL (with PostGIS) or MySQL.
* In MySQL, allowable Geometry types are `POINT`, `LINESTRING`, `POLYGON`.
* It is only available in PostgreSQL (with PostGIS), MariaDB or MySQL.
*
* GeoJSON is accepted as input and returned as output.
*
......@@ -1254,6 +1253,7 @@ _.each(DataTypes, dataType => {
DataTypes.postgres = require('./dialects/postgres/data-types')(DataTypes);
DataTypes.mysql = require('./dialects/mysql/data-types')(DataTypes);
DataTypes.mariadb = require('./dialects/mariadb/data-types')(DataTypes);
DataTypes.sqlite = require('./dialects/sqlite/data-types')(DataTypes);
DataTypes.mssql = require('./dialects/mssql/data-types')(DataTypes);
......
......@@ -252,12 +252,18 @@ class ConnectionManager {
_options.logging = () => {};
_options.logging.__testLoggingFn = true;
return this.sequelize.databaseVersion(_options).then(version => {
this.sequelize.options.databaseVersion = semver.valid(version) ? version : this.defaultVersion;
this.versionPromise = null;
//connection might have set databaseVersion value at initialization,
//avoiding a useless round trip
if (this.sequelize.options.databaseVersion === 0) {
return this.sequelize.databaseVersion(_options).then(version => {
this.sequelize.options.databaseVersion = semver.valid(version) ? version : this.defaultVersion;
this.versionPromise = null;
return this._disconnect(connection);
});
}
return this._disconnect(connection);
});
this.versionPromise = null;
return this._disconnect(connection);
}).catch(err => {
this.versionPromise = null;
throw err;
......
......@@ -1041,7 +1041,9 @@ class QueryGenerator {
jsonPathExtractionQuery(column, path) {
let paths = _.toPath(path);
let pathStr;
let quotedColumn;
const quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
switch (this.dialect) {
case 'mysql':
......@@ -1051,29 +1053,21 @@ class QueryGenerator {
*/
paths = paths.map(subPath => Utils.addTicks(subPath, '"'));
pathStr = ['$'].concat(paths).join('.');
quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
return `(${quotedColumn}->>'${pathStr}')`;
case 'mariadb':
pathStr = ['$'].concat(paths).join('.');
return `json_unquote(json_extract(${quotedColumn},'${pathStr}'))`;
case 'sqlite':
pathStr = this.escape(['$']
.concat(paths)
.join('.')
.replace(/\.(\d+)(?:(?=\.)|$)/g, (_, digit) => `[${digit}]`));
quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
return `json_extract(${quotedColumn}, ${pathStr})`;
case 'postgres':
pathStr = this.escape(`{${paths.join(',')}}`);
quotedColumn = this.isIdentifierQuoted(column)
? column
: this.quoteIdentifier(column);
return `(${quotedColumn}#>>${pathStr})`;
default:
......@@ -1466,8 +1460,8 @@ class QueryGenerator {
prefix = attr;
} else if (/#>>|->>/.test(attr)) {
prefix = `(${this.quoteIdentifier(includeAs.internalAs)}.${attr.replace(/\(|\)/g, '')})`;
} else if (/json_extract/.test(attr)) {
prefix = `json_extract(${this.quoteIdentifier(includeAs.internalAs)}.${attr.replace(/\(|\)|json_extract/g, '')})`;
} else if (/json_extract\(/.test(attr)) {
prefix = attr.replace(/json_extract\(/i, `json_extract(${this.quoteIdentifier(includeAs.internalAs)}.`);
} else {
prefix = `${this.quoteIdentifier(includeAs.internalAs)}.${this.quoteIdentifier(attr)}`;
}
......
......@@ -42,8 +42,7 @@ function quoteIdentifier(dialect, identifier, options) {
switch (dialect) {
case 'sqlite':
return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`');
case 'mariadb':
case 'mysql':
return Utils.addTicks(Utils.removeTicks(identifier, '`'), '`');
......
'use strict';
const AbstractConnectionManager = require('../abstract/connection-manager');
const SequelizeErrors = require('../../errors');
const Promise = require('../../promise');
const logger = require('../../utils/logger');
const DataTypes = require('../../data-types').mariadb;
const momentTz = require('moment-timezone');
const debug = logger.getLogger().debugContext('connection:mariadb');
const parserStore = require('../parserStore')('mariadb');
/**
* MariaDB Connection Manager
*
* Get connections, validate and disconnect them.
* AbstractConnectionManager pooling use it to handle MariaDB specific connections
* Use https://github.com/MariaDB/mariadb-connector-nodejs to connect with MariaDB server
*
* @extends AbstractConnectionManager
* @returns Class<ConnectionManager>
* @private
*/
class ConnectionManager extends AbstractConnectionManager {
constructor(dialect, sequelize) {
super(dialect, sequelize);
this.sequelize.config.port = this.sequelize.config.port || 3306;
this.lib = this._loadDialectModule('mariadb');
this.refreshTypeParser(DataTypes);
}
static _typecast(field, next) {
if (parserStore.get(field.type)) {
return parserStore.get(field.type)(field, this.sequelize.options, next);
}
return next();
}
_refreshTypeParser(dataType) {
parserStore.refresh(dataType);
}
_clearTypeParser() {
parserStore.clear();
}
/**
* Connect with MariaDB database based on config, Handle any errors in connection
* Set the pool handlers on connection.error
* Also set proper timezone once connection is connected
*
* @returns Promise<Connection>
* @private
*/
connect(config) {
const connectionConfig = {
host: config.host,
port: config.port,
user: config.username,
password: config.password,
database: config.database,
timezone: this.sequelize.options.timezone,
typeCast: ConnectionManager._typecast.bind(this),
bigNumberStrings: false,
supportBigNumbers: true,
foundRows: false
};
if (config.dialectOptions) {
Object.assign(connectionConfig, config.dialectOptions);
}
if (!this.sequelize.config.keepDefaultTimezone) {
// set timezone for this connection
// but named timezone are not directly supported in mariadb, so get its offset first
let tzOffset = this.sequelize.options.timezone;
tzOffset = /\//.test(tzOffset) ? momentTz.tz(tzOffset).format('Z')
: tzOffset;
if (connectionConfig.initSql) {
if (!Array.isArray(
connectionConfig.initSql)) {
connectionConfig.initSql = [connectionConfig.initSql];
}
connectionConfig.initSql.push(`SET time_zone = '${tzOffset}'`);
} else {
connectionConfig.initSql = `SET time_zone = '${tzOffset}'`;
}
}
return this.lib.createConnection(connectionConfig)
.then(connection => {
this.sequelize.options.databaseVersion = connection.serverVersion();
debug('connection acquired');
connection.on('error', error => {
switch (error.code) {
case 'ESOCKET':
case 'ECONNRESET':
case 'EPIPE':
case 'PROTOCOL_CONNECTION_LOST':
this.pool.destroy(connection);
}
});
return connection;
})
.catch(err => {
switch (err.code) {
case 'ECONNREFUSED':
throw new SequelizeErrors.ConnectionRefusedError(err);
case 'ER_ACCESS_DENIED_ERROR':
case 'ER_ACCESS_DENIED_NO_PASSWORD_ERROR':
throw new SequelizeErrors.AccessDeniedError(err);
case 'ENOTFOUND':
throw new SequelizeErrors.HostNotFoundError(err);
case 'EHOSTUNREACH':
case 'ENETUNREACH':
case 'EADDRNOTAVAIL':
throw new SequelizeErrors.HostNotReachableError(err);
case 'EINVAL':
throw new SequelizeErrors.InvalidConnectionError(err);
default:
throw new SequelizeErrors.ConnectionError(err);
}
});
}
disconnect(connection) {
// Don't disconnect connections with CLOSED state
if (!connection.isValid()) {
debug('connection tried to disconnect but was already at CLOSED state');
return Promise.resolve();
}
//wrap native Promise into bluebird
return Promise.resolve(connection.end());
}
validate(connection) {
return connection && connection.isValid();
}
}
module.exports = ConnectionManager;
module.exports.ConnectionManager = ConnectionManager;
module.exports.default = ConnectionManager;
'use strict';
const _ = require('lodash');
const moment = require('moment-timezone');
const inherits = require('../../utils/inherits');
module.exports = BaseTypes => {
BaseTypes.ABSTRACT.prototype.dialectTypes = 'https://mariadb.com/kb/en/library/resultset/#field-types';
/**
* types: [buffer_type, ...]
* @see documentation : https://mariadb.com/kb/en/library/resultset/#field-types
* @see connector implementation : https://github.com/MariaDB/mariadb-connector-nodejs/blob/master/lib/const/field-type.js
*/
BaseTypes.DATE.types.mariadb = ['DATETIME'];
BaseTypes.STRING.types.mariadb = ['VAR_STRING'];
BaseTypes.CHAR.types.mariadb = ['STRING'];
BaseTypes.TEXT.types.mariadb = ['BLOB'];
BaseTypes.TINYINT.types.mariadb = ['TINY'];
BaseTypes.SMALLINT.types.mariadb = ['SHORT'];
BaseTypes.MEDIUMINT.types.mariadb = ['INT24'];
BaseTypes.INTEGER.types.mariadb = ['LONG'];
BaseTypes.BIGINT.types.mariadb = ['LONGLONG'];
BaseTypes.FLOAT.types.mariadb = ['FLOAT'];
BaseTypes.TIME.types.mariadb = ['TIME'];
BaseTypes.DATEONLY.types.mariadb = ['DATE'];
BaseTypes.BOOLEAN.types.mariadb = ['TINY'];
BaseTypes.BLOB.types.mariadb = ['TINYBLOB', 'BLOB', 'LONGBLOB'];
BaseTypes.DECIMAL.types.mariadb = ['NEWDECIMAL'];
BaseTypes.UUID.types.mariadb = false;
BaseTypes.ENUM.types.mariadb = false;
BaseTypes.REAL.types.mariadb = ['DOUBLE'];
BaseTypes.DOUBLE.types.mariadb = ['DOUBLE'];
BaseTypes.GEOMETRY.types.mariadb = ['GEOMETRY'];
BaseTypes.JSON.types.mariadb = ['JSON'];
function DECIMAL(precision, scale) {
if (!(this instanceof DECIMAL)) {
return new DECIMAL(precision, scale);
}
BaseTypes.DECIMAL.apply(this, arguments);
}
inherits(DECIMAL, BaseTypes.DECIMAL);
DECIMAL.prototype.toSql = function toSql() {
let definition = BaseTypes.DECIMAL.prototype.toSql.apply(this);
if (this._unsigned) {
definition += ' UNSIGNED';
}
if (this._zerofill) {
definition += ' ZEROFILL';
}
return definition;
};
function DATE(length) {
if (!(this instanceof DATE)) {
return new DATE(length);
}
BaseTypes.DATE.apply(this, arguments);
}
inherits(DATE, BaseTypes.DATE);
DATE.prototype.toSql = function toSql() {
return `DATETIME${this._length ? `(${this._length})` : ''}`;
};
DATE.prototype._stringify = function _stringify(date, options) {
date = BaseTypes.DATE.prototype._applyTimezone(date, options);
return date.format('YYYY-MM-DD HH:mm:ss.SSS');
};
DATE.parse = function parse(value, options) {
value = value.string();
if (value === null) {
return value;
}
if (moment.tz.zone(options.timezone)) {
value = moment.tz(value, options.timezone).toDate();
} else {
value = new Date(`${value} ${options.timezone}`);
}
return value;
};
function DATEONLY() {
if (!(this instanceof DATEONLY)) {
return new DATEONLY();
}
BaseTypes.DATEONLY.apply(this, arguments);
}
inherits(DATEONLY, BaseTypes.DATEONLY);
DATEONLY.parse = function parse(value) {
return value.string();
};
function UUID() {
if (!(this instanceof UUID)) {
return new UUID();
}
BaseTypes.UUID.apply(this, arguments);
}
inherits(UUID, BaseTypes.UUID);
UUID.prototype.toSql = function toSql() {
return 'CHAR(36) BINARY';
};
function GEOMETRY(type, srid) {
if (!(this instanceof GEOMETRY)) {
return new GEOMETRY(type, srid);
}
BaseTypes.GEOMETRY.apply(this, arguments);
if (_.isEmpty(this.type)) {
this.sqlType = this.key;
} else {
this.sqlType = this.type;
}
}
inherits(GEOMETRY, BaseTypes.GEOMETRY);
GEOMETRY.prototype.toSql = function toSql() {
return this.sqlType;
};
function ENUM() {
if (!(this instanceof ENUM)) {
const obj = Object.create(ENUM.prototype);
ENUM.apply(obj, arguments);
return obj;
}
BaseTypes.ENUM.apply(this, arguments);
}
inherits(ENUM, BaseTypes.ENUM);
ENUM.prototype.toSql = function toSql(options) {
return `ENUM(${this.values.map(value => options.escape(value)).join(', ')})`;
};
function JSONTYPE() {
if (!(this instanceof JSONTYPE)) {
return new JSONTYPE();
}
BaseTypes.JSON.apply(this, arguments);
}
inherits(JSONTYPE, BaseTypes.JSON);
JSONTYPE.prototype._stringify = function _stringify(value, options) {
return options.operation === 'where' && typeof value === 'string' ? value
: JSON.stringify(value);
};
const exports = {
ENUM,
DATE,
DATEONLY,
UUID,
GEOMETRY,
DECIMAL,
JSON: JSONTYPE
};
_.forIn(exports, (DataType, key) => {
if (!DataType.key) {
DataType.key = key;
}
if (!DataType.extend) {
DataType.extend = function extend(oldType) {
return new DataType(oldType.options);
};
}
});
return exports;
};
'use strict';
const _ = require('lodash');
const AbstractDialect = require('../abstract');
const ConnectionManager = require('./connection-manager');
const Query = require('./query');
const QueryGenerator = require('./query-generator');
const DataTypes = require('../../data-types').mariadb;
class MariadbDialect extends AbstractDialect {
constructor(sequelize) {
super();
this.sequelize = sequelize;
this.connectionManager = new ConnectionManager(this, sequelize);
this.QueryGenerator = new QueryGenerator({
_dialect: this,
sequelize
});
}
}
MariadbDialect.prototype.supports = _.merge(
_.cloneDeep(AbstractDialect.prototype.supports), {
'VALUES ()': true,
'LIMIT ON UPDATE': true,
lock: true,
forShare: 'LOCK IN SHARE MODE',
schemas: true,
inserts: {
ignoreDuplicates: ' IGNORE',
updateOnDuplicate: true
},
index: {
collate: false,
length: true,
parser: true,
type: true,
using: 1
},
constraints: {
dropConstraint: false,
check: false
},
indexViaAlter: true,
NUMERIC: true,
GEOMETRY: true,
JSON: true,
REGEXP: true
});
ConnectionManager.prototype.defaultVersion = '5.5.3';
MariadbDialect.prototype.Query = Query;
MariadbDialect.prototype.QueryGenerator = QueryGenerator;
MariadbDialect.prototype.DataTypes = DataTypes;
MariadbDialect.prototype.name = 'mariadb';
MariadbDialect.prototype.TICK_CHAR = '`';
MariadbDialect.prototype.TICK_CHAR_LEFT = MariadbDialect.prototype.TICK_CHAR;
MariadbDialect.prototype.TICK_CHAR_RIGHT = MariadbDialect.prototype.TICK_CHAR;
module.exports = MariadbDialect;
'use strict';
const _ = require('lodash');
const Utils = require('../../utils');
const MySQLQueryGenerator = require('../mysql/query-generator');
const util = require('util');
class MariaDBQueryGenerator extends MySQLQueryGenerator {
createSchema(schema, options) {
options = Object.assign({
charset: null,
collate: null
}, options || {});
const charset = options.charset ? ` DEFAULT CHARACTER SET ${this.escape(options.charset)}` : '';
const collate = options.collate ? ` DEFAULT COLLATE ${this.escape(options.collate)}` : '';
return `CREATE SCHEMA IF NOT EXISTS ${this.quoteIdentifier(schema)}${charset}${collate};`;
}
dropSchema(schema) {
return `DROP SCHEMA IF EXISTS ${this.quoteIdentifier(schema)};`;
}
showSchemasQuery(options) {
const skip = options.skip && Array.isArray(options.skip) && options.skip.length > 0 ? options.skip : null;
return `SELECT SCHEMA_NAME as schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('MYSQL', 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA'${skip ? skip.reduce( (sql, schemaName) => sql += `,${this.escape(schemaName)}`, '') : ''});`;
}
showTablesQuery() {
return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA NOT IN (\'MYSQL\', \'INFORMATION_SCHEMA\', \'PERFORMANCE_SCHEMA\') AND TABLE_TYPE = \'BASE TABLE\'';
}
handleSequelizeMethod(smth, tableName, factory, options, prepend) {
if (smth instanceof Utils.Json) {
// Parse nested object
if (smth.conditions) {
const conditions = this.parseConditionObject(smth.conditions).map(
condition =>
`json_unquote(json_extract(${this.quoteIdentifier(
condition.path[0])},'\$.${_.tail(condition.path).join(
'.')}')) = '${condition.value}'`
);
return conditions.join(' and ');
}
if (smth.path) {
let str;
// Allow specifying conditions using the sqlite json functions
if (this._checkValidJsonStatement(smth.path)) {
str = smth.path;
} else {
// Also support json dot notation
let path = smth.path;
let startWithDot = true;
// Convert .number. to [number].
path = path.replace(/\.(\d+)\./g, '[$1].');
// Convert .number$ to [number]
path = path.replace(/\.(\d+)$/, '[$1]');
path = path.split('.');
let columnName = path.shift();
const match = columnName.match(/\[\d+\]$/);
// If columnName ends with [\d+]
if (match !== null) {
path.unshift(columnName.substr(match.index));
columnName = columnName.substr(0, match.index);
startWithDot = false;
}
str = `json_unquote(json_extract(${this.quoteIdentifier(
columnName)},'\$${startWithDot ? '.' : ''}${path.join('.')}'))`;
}
if (smth.value) {
str += util.format(' = %s', this.escape(smth.value));
}
return str;
}
} else if (smth instanceof Utils.Cast) {
const lowType = smth.type.toLowerCase();
if (lowType.includes('timestamp')) {
smth.type = 'datetime';
} else if (smth.json && lowType.includes('boolean')) {
// true or false cannot be casted as booleans within a JSON structure
smth.type = 'char';
} else if (lowType.includes('double precision') || lowType.includes('boolean') || lowType.includes('integer')) {
smth.type = 'decimal';
} else if (lowType.includes('text')) {
smth.type = 'char';
}
}
return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
}
}
module.exports = MariaDBQueryGenerator;
......@@ -6,6 +6,25 @@ const AbstractQueryGenerator = require('../abstract/query-generator');
const util = require('util');
const Op = require('../../operators');
const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
const foreignKeyFields = 'CONSTRAINT_NAME as constraint_name,'
+ 'CONSTRAINT_NAME as constraintName,'
+ 'CONSTRAINT_SCHEMA as constraintSchema,'
+ 'CONSTRAINT_SCHEMA as constraintCatalog,'
+ 'TABLE_NAME as tableName,'
+ 'TABLE_SCHEMA as tableSchema,'
+ 'TABLE_SCHEMA as tableCatalog,'
+ 'COLUMN_NAME as columnName,'
+ 'REFERENCED_TABLE_SCHEMA as referencedTableSchema,'
+ 'REFERENCED_TABLE_SCHEMA as referencedTableCatalog,'
+ 'REFERENCED_TABLE_NAME as referencedTableName,'
+ 'REFERENCED_COLUMN_NAME as referencedColumnName';
const typeWithoutDefault = new Set(['BLOB', 'TEXT', 'GEOMETRY', 'JSON']);
class MySQLQueryGenerator extends AbstractQueryGenerator {
constructor(options) {
super(options);
......@@ -57,29 +76,28 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
const attrStr = [];
for (const attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
const dataType = attributes[attr];
let match;
if (dataType.includes('PRIMARY KEY')) {
primaryKeys.push(attr);
if (dataType.includes('REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`);
foreignKeys[attr] = match[2];
} else {
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`);
}
} else if (dataType.includes('REFERENCES')) {
if (!attributes.hasOwnProperty(attr)) continue;
const dataType = attributes[attr];
let match;
if (dataType.includes('PRIMARY KEY')) {
primaryKeys.push(attr);
if (dataType.includes('REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace('PRIMARY KEY', '')}`);
foreignKeys[attr] = match[2];
} else {
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`);
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace('PRIMARY KEY', '')}`);
}
} else if (dataType.includes('REFERENCES')) {
// MySQL doesn't support inline REFERENCES declarations: move to the end
match = dataType.match(/^(.+) (REFERENCES.*)$/);
attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
foreignKeys[attr] = match[2];
} else {
attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`);
}
}
......@@ -339,7 +357,7 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
};
}
const attributeString = attribute.type.toString({ escape: this.escape.bind(this) });
const attributeString = attribute.type.toString({escape: this.escape.bind(this)});
let template = attributeString;
if (attribute.allowNull === false) {
......@@ -351,7 +369,9 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
}
// BLOB/TEXT/GEOMETRY/JSON cannot have a default value
if (!['BLOB', 'TEXT', 'GEOMETRY', 'JSON'].includes(attributeString) && attribute.type._binary !== true && Utils.defaultValueSchemable(attribute.defaultValue)) {
if (!typeWithoutDefault.has(attributeString)
&& attribute.type._binary !== true
&& Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ` DEFAULT ${this.escape(attribute.defaultValue)}`;
}
......@@ -427,10 +447,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
return false;
}
const jsonFunctionRegex = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
const jsonOperatorRegex = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
let currentIndex = 0;
let openingBrackets = 0;
let closingBrackets = 0;
......@@ -472,8 +488,7 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
}
// Check invalid json statement
hasInvalidToken |= openingBrackets !== closingBrackets;
if (hasJsonFunction && hasInvalidToken) {
if (hasJsonFunction && (hasInvalidToken || openingBrackets !== closingBrackets)) {
throw new Error(`Invalid json statement: ${stmt}`);
}
......@@ -482,28 +497,6 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
}
/**
* Generates fields for getForeignKeysQuery
* @returns {string} fields
* @private
*/
_getForeignKeysQueryFields() {
return [
'CONSTRAINT_NAME as constraint_name',
'CONSTRAINT_NAME as constraintName',
'CONSTRAINT_SCHEMA as constraintSchema',
'CONSTRAINT_SCHEMA as constraintCatalog',
'TABLE_NAME as tableName',
'TABLE_SCHEMA as tableSchema',
'TABLE_SCHEMA as tableCatalog',
'COLUMN_NAME as columnName',
'REFERENCED_TABLE_SCHEMA as referencedTableSchema',
'REFERENCED_TABLE_SCHEMA as referencedTableCatalog',
'REFERENCED_TABLE_NAME as referencedTableName',
'REFERENCED_COLUMN_NAME as referencedColumnName'
].join(',');
}
/**
* Generates an SQL query that returns all foreign keys of a table.
*
* @param {string} tableName The name of the table.
......@@ -511,9 +504,9 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
* @returns {string} The generated sql query.
* @private
*/
getForeignKeysQuery(tableName, schemaName) {
return `SELECT ${this._getForeignKeysQueryFields()} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName /* jshint ignore: line */
}' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}' AND REFERENCED_TABLE_NAME IS NOT NULL;`; /* jshint ignore: line */
getForeignKeysQuery(table, schemaName) {
const tableName = table.tableName || table;
return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}' AND REFERENCED_TABLE_NAME IS NOT NULL;`;
}
/**
......@@ -525,18 +518,16 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
* @private
*/
getForeignKeyQuery(table, columnName) {
const tableName = table.tableName || table;
const schemaName = table.schema;
return `SELECT ${this._getForeignKeysQueryFields()
} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE`
+ ` WHERE (REFERENCED_TABLE_NAME = ${wrapSingleQuote(tableName)
}${schemaName ? ` AND REFERENCED_TABLE_SCHEMA = ${wrapSingleQuote(schemaName)}`: ''
} AND REFERENCED_COLUMN_NAME = ${wrapSingleQuote(columnName)
}) OR (TABLE_NAME = ${wrapSingleQuote(tableName)
}${schemaName ? ` AND TABLE_SCHEMA = ${wrapSingleQuote(schemaName)}`: ''
} AND COLUMN_NAME = ${wrapSingleQuote(columnName)
} AND REFERENCED_TABLE_NAME IS NOT NULL)`;
const quotedSchemaName = table.schema ? wrapSingleQuote(table.schema) : '';
const quotedTableName = wrapSingleQuote(table.tableName || table);
const quotedColumnName = wrapSingleQuote(columnName);
return `SELECT ${foreignKeyFields} FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE`
+ ` WHERE (REFERENCED_TABLE_NAME = ${quotedTableName}${table.schema
? ` AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}`
: ''} AND REFERENCED_COLUMN_NAME = ${quotedColumnName})`
+ ` OR (TABLE_NAME = ${quotedTableName}${table.schema ?
` AND TABLE_SCHEMA = ${quotedSchemaName}` : ''} AND COLUMN_NAME = ${quotedColumnName} AND REFERENCED_TABLE_NAME IS NOT NULL)`;
}
/**
......@@ -548,9 +539,10 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
* @private
*/
dropForeignKeyQuery(tableName, foreignKey) {
return `ALTER TABLE ${this.quoteTable(tableName)} DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`;
return `ALTER TABLE ${this.quoteTable(tableName)}
DROP FOREIGN KEY ${this.quoteIdentifier(foreignKey)};`;
}
};
}
// private methods
function wrapSingleQuote(identifier) {
......
......@@ -12,13 +12,13 @@ const Promise = require('../../promise');
const sequelizeErrors = require('../../errors');
/**
A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint.
A wrapper that fixes MySQL's inability to cleanly remove columns from existing tables if they have a foreign key constraint.
@param {string} tableName The name of the table.
@param {string} columnName The name of the attribute that we want to remove.
@param {Object} options
@param {string} tableName The name of the table.
@param {string} columnName The name of the attribute that we want to remove.
@param {Object} options
@private
@private
*/
function removeColumn(tableName, columnName, options) {
options = options || {};
......@@ -28,7 +28,7 @@ function removeColumn(tableName, columnName, options) {
tableName,
schema: this.sequelize.config.database
}, columnName),
Object.assign({ raw: true }, options)
Object.assign({raw: true}, options)
)
.then(([results]) => {
//Exclude primary key constraint
......@@ -38,38 +38,40 @@ function removeColumn(tableName, columnName, options) {
}
return Promise.map(results, constraint => this.sequelize.query(
this.QueryGenerator.dropForeignKeyQuery(tableName, constraint.constraint_name),
Object.assign({ raw: true }, options)
Object.assign({raw: true}, options)
));
})
.then(() => this.sequelize.query(
this.QueryGenerator.removeColumnQuery(tableName, columnName),
Object.assign({ raw: true }, options)
Object.assign({raw: true}, options)
));
}
function removeConstraint(tableName, constraintName, options) {
const sql = this.QueryGenerator.showConstraintsQuery(tableName.tableName ? tableName : {
tableName,
schema: this.sequelize.config.database
}, constraintName);
const sql = this.QueryGenerator.showConstraintsQuery(
tableName.tableName ? tableName : {
tableName,
schema: this.sequelize.config.database
}, constraintName);
return this.sequelize.query(sql, Object.assign({}, options, { type: this.sequelize.QueryTypes.SHOWCONSTRAINTS }))
return this.sequelize.query(sql, Object.assign({}, options,
{type: this.sequelize.QueryTypes.SHOWCONSTRAINTS}))
.then(constraints => {
const constraint = constraints[0];
let query;
if (constraint && constraint.constraintType) {
if (constraint.constraintType === 'FOREIGN KEY') {
query = this.QueryGenerator.dropForeignKeyQuery(tableName, constraintName);
} else {
query = this.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName);
}
if (!constraint || !constraint.constraintType) {
throw new sequelizeErrors.UnknownConstraintError(
{
message: `Constraint ${constraintName} on table ${tableName} does not exist`,
constraint: constraintName,
table: tableName
});
}
if (constraint.constraintType === 'FOREIGN KEY') {
query = this.QueryGenerator.dropForeignKeyQuery(tableName, constraintName);
} else {
throw new sequelizeErrors.UnknownConstraintError({
message: `Constraint ${constraintName} on table ${tableName} does not exist`,
constraint: constraintName,
table: tableName
});
query = this.QueryGenerator.removeIndexQuery(constraint.tableName, constraint.constraintName);
}
return this.sequelize.query(query, options);
......
......@@ -2495,7 +2495,7 @@ class Model {
if (options.ignoreDuplicates && ['mssql'].includes(dialect)) {
return Promise.reject(new Error(`${dialect} does not support the ignoreDuplicates option.`));
}
if (options.updateOnDuplicate && dialect !== 'mysql') {
if (options.updateOnDuplicate && (dialect !== 'mysql' && dialect !== 'mariadb')) {
return Promise.reject(new Error(`${dialect} does not support the updateOnDuplicate option.`));
}
......
......@@ -114,7 +114,7 @@ class QueryInterface {
type: this.sequelize.QueryTypes.SELECT
});
const showSchemasSql = this.QueryGenerator.showSchemasQuery();
const showSchemasSql = this.QueryGenerator.showSchemasQuery(options);
return this.sequelize.query(showSchemasSql, options).then(schemaNames => _.flatten(
schemaNames.map(value => value.schema_name ? value.schema_name : value)
......@@ -499,7 +499,7 @@ class QueryInterface {
*/
addColumn(table, key, attribute, options) {
if (!table || !key || !attribute) {
throw new Error('addColumn takes atleast 3 arguments (table, attribute name, attribute definition)');
throw new Error('addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
}
options = options || {};
......@@ -526,7 +526,8 @@ class QueryInterface {
// mssql needs special treatment as it cannot drop a column with a default or foreign key constraint
return MSSSQLQueryInterface.removeColumn.call(this, tableName, attributeName, options);
case 'mysql':
// mysql needs special treatment as it cannot drop a column with a foreign key constraint
case 'mariadb':
// mysql/mariadb need special treatment as it cannot drop a column with a foreign key constraint
return MySQLQueryInterface.removeColumn.call(this, tableName, attributeName, options);
default:
return this.sequelize.query(this.QueryGenerator.removeColumnQuery(tableName, attributeName), options);
......@@ -721,8 +722,8 @@ class QueryInterface {
}
case 'mssql':
case 'mysql':
default:
{
case 'mariadb':
default: {
const query = this.QueryGenerator.getForeignKeysQuery(tableName, catalogName);
return this.sequelize.query(query, queryOptions);
}
......@@ -850,7 +851,8 @@ class QueryInterface {
switch (this.sequelize.options.dialect) {
case 'mysql':
//Mysql does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used
case 'mariadb':
//does not support DROP CONSTRAINT. Instead DROP PRIMARY, FOREIGN KEY, INDEX should be used
return MySQLQueryInterface.removeConstraint.call(this, tableName, constraintName, options);
case 'sqlite':
return SQLiteQueryInterface.removeConstraint.call(this, tableName, constraintName, options);
......@@ -946,6 +948,7 @@ class QueryInterface {
// MySQL returns 1 for inserted, 2 for updated
// http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html.
case 'mysql':
case 'mariadb':
return [result === 1, undefined];
default:
......@@ -1131,13 +1134,6 @@ class QueryInterface {
}
rawSelect(tableName, options, attributeSelector, Model) {
if (options.schema) {
tableName = this.QueryGenerator.addSchema({
tableName,
_schema: options.schema
});
}
options = Utils.cloneDeep(options);
options = _.defaults(options, {
raw: true,
......
......@@ -216,6 +216,9 @@ class Sequelize {
// Requiring the dialect in a switch-case to keep the
// require calls static. (Browserify fix)
switch (this.getDialect()) {
case 'mariadb':
Dialect = require('./dialects/mariadb');
break;
case 'mssql':
Dialect = require('./dialects/mssql');
break;
......@@ -229,7 +232,7 @@ class Sequelize {
Dialect = require('./dialects/sqlite');
break;
default:
throw new Error(`The dialect ${this.getDialect()} is not supported. Supported dialects: mssql, mysql, postgres, and sqlite.`);
throw new Error(`The dialect ${this.getDialect()} is not supported. Supported dialects: mssql, mariadb, mysql, postgres, and sqlite.`);
}
this.dialect = new Dialect(this);
......
......@@ -333,9 +333,12 @@ function sliceArgs(args, begin) {
}
exports.sliceArgs = sliceArgs;
const dialects = new Set(['mariadb', 'mysql', 'postgres', 'sqlite', 'mssql']);
function now(dialect) {
const d = new Date();
if (!['mysql', 'postgres', 'sqlite', 'mssql'].includes(dialect)) {
if (!dialects.has(dialect)) {
d.setMilliseconds(0);
}
return d;
......
......@@ -64,6 +64,7 @@
"istanbul": "^0.x",
"lcov-result-merger": "^3.0.0",
"lint-staged": "^7.3.0",
"mariadb": "^2.0.1-beta",
"mocha": "^5.x",
"mysql2": "^1.x",
"pg": "^7.5.0",
......@@ -77,6 +78,7 @@
},
"keywords": [
"mysql",
"mariadb",
"sqlite",
"postgresql",
"postgres",
......@@ -111,19 +113,22 @@
"docs": "esdoc && cp docs/ROUTER esdoc/ROUTER",
"teaser": "node scripts/teaser",
"test-unit": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --ui tdd --exit --check-leaks --colors -t 30000 --reporter spec \"test/unit/**/*.js\"",
"test-unit-mariadb": "cross-env DIALECT=mariadb npm run test-unit",
"test-unit-mysql": "cross-env DIALECT=mysql npm run test-unit",
"test-unit-postgres": "cross-env DIALECT=postgres npm run test-unit",
"test-unit-postgres-native": "cross-env DIALECT=postgres-native npm run test-unit",
"test-unit-sqlite": "cross-env DIALECT=sqlite npm run test-unit",
"test-unit-mssql": "cross-env DIALECT=mssql npm run test-unit",
"test-unit-all": "npm run test-unit-mysql && npm run test-unit-postgres && npm run test-unit-postgres-native && npm run test-unit-mssql && npm run test-unit-sqlite",
"test-unit-all": "npm run test-unit-mariadb && npm run test-unit-mysql && npm run test-unit-postgres && npm run test-unit-postgres-native && npm run test-unit-mssql && npm run test-unit-sqlite",
"test-integration": "mocha --require scripts/mocha-bootload --globals setImmediate,clearImmediate --ui tdd --exit --check-leaks --colors -t 30000 --reporter spec \"test/integration/**/*.test.js\"",
"test-integration-mariadb": "cross-env DIALECT=mariadb npm run test-integration",
"test-integration-mysql": "cross-env DIALECT=mysql npm run test-integration",
"test-integration-postgres": "cross-env DIALECT=postgres npm run test-integration",
"test-integration-postgres-native": "cross-env DIALECT=postgres-native npm run test-integration",
"test-integration-sqlite": "cross-env DIALECT=sqlite npm run test-integration",
"test-integration-mssql": "cross-env DIALECT=mssql npm run test-integration",
"test-integration-all": "npm run test-integration-mysql && npm run test-integration-postgres && npm run test-integration-postgres-native && npm run test-integration-mssql && npm run test-integration-sqlite",
"test-integration-all": "npm run test-integration-mariadb && npm run test-integration-mysql && npm run test-integration-postgres && npm run test-integration-postgres-native && npm run test-integration-mssql && npm run test-integration-sqlite",
"test-mariadb": "cross-env DIALECT=mariadb npm test",
"test-mysql": "cross-env DIALECT=mysql npm test",
"test-sqlite": "cross-env DIALECT=sqlite npm test",
"test-postgres": "cross-env DIALECT=postgres npm test",
......@@ -131,12 +136,13 @@
"test-postgres-native": "cross-env DIALECT=postgres-native npm test",
"test-postgresn": "npm run test-postgres-native",
"test-mssql": "cross-env DIALECT=mssql npm test",
"test-all": "npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql",
"test-all": "npm run test-mariadb && npm run test-mysql && npm run test-sqlite && npm run test-postgres && npm run test-postgres-native && npm run test-mssql",
"cover": "rimraf coverage && npm run teaser && npm run cover-integration && npm run cover-unit && npm run merge-coverage",
"cover-integration": "cross-env COVERAGE=true ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --require scripts/mocha-bootload --report lcovonly -- -t 30000 --exit --ui tdd \"test/integration/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/integration.info')\"",
"cover-unit": "cross-env COVERAGE=true ./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --require scripts/mocha-bootload --report lcovonly -- -t 30000 --exit --ui tdd \"test/unit/**/*.test.js\" && node -e \"require('fs').renameSync('coverage/lcov.info', 'coverage/unit.info')\"",
"merge-coverage": "lcov-result-merger \"coverage/*.info\" \"coverage/lcov.info\"",
"sscce": "env-cmd $npm_package_options_env_cmd node sscce.js",
"sscce-mariadb": "cross-env DIALECT=mariadb npm run sscce",
"sscce-mysql": "cross-env DIALECT=mysql npm run sscce",
"sscce-postgres": "cross-env DIALECT=postgres npm run sscce",
"sscce-sqlite": "cross-env DIALECT=sqlite npm run sscce",
......
# Special ports needed for docker to prevent port conflicts
SEQ_MARIADB_PORT=8960
SEQ_MARIADB_USER=sequelize_test
SEQ_MARIADB_PW=sequelize_test
SEQ_MYSQL_PORT=8980
SEQ_MYSQL_USER=sequelize_test
SEQ_MYSQL_PW=sequelize_test
......
......@@ -7,15 +7,16 @@ try {
} catch (e) {
// ignore
}
const env = process.env;
module.exports = {
username: process.env.SEQ_USER || 'root',
password: process.env.SEQ_PW || null,
database: process.env.SEQ_DB || 'sequelize_test',
host: process.env.SEQ_HOST || '127.0.0.1',
username: env.SEQ_USER || 'root',
password: env.SEQ_PW || null,
database: env.SEQ_DB || 'sequelize_test',
host: env.SEQ_HOST || '127.0.0.1',
pool: {
max: process.env.SEQ_POOL_MAX || 5,
idle: process.env.SEQ_POOL_IDLE || 30000
max: env.SEQ_POOL_MAX || 5,
idle: env.SEQ_POOL_IDLE || 30000
},
rand() {
......@@ -23,46 +24,57 @@ module.exports = {
},
mssql: mssqlConfig || {
database: process.env.SEQ_MSSQL_DB || process.env.SEQ_DB || 'sequelize_test',
username: process.env.SEQ_MSSQL_USER || process.env.SEQ_USER || 'sequelize',
password: process.env.SEQ_MSSQL_PW || process.env.SEQ_PW || 'nEGkLma26gXVHFUAHJxcmsrK',
host: process.env.SEQ_MSSQL_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.SEQ_MSSQL_PORT || process.env.SEQ_PORT || 1433,
database: env.SEQ_MSSQL_DB || env.SEQ_DB || 'sequelize_test',
username: env.SEQ_MSSQL_USER || env.SEQ_USER || 'sequelize',
password: env.SEQ_MSSQL_PW || env.SEQ_PW || 'nEGkLma26gXVHFUAHJxcmsrK',
host: env.SEQ_MSSQL_HOST || env.SEQ_HOST || '127.0.0.1',
port: env.SEQ_MSSQL_PORT || env.SEQ_PORT || 1433,
dialectOptions: {
// big insert queries need a while
requestTimeout: 60000
},
pool: {
max: process.env.SEQ_MSSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
idle: process.env.SEQ_MSSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
max: env.SEQ_MSSQL_POOL_MAX || env.SEQ_POOL_MAX || 5,
idle: env.SEQ_MSSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000
}
},
//make idle time small so that tests exit promptly
mysql: {
database: process.env.SEQ_MYSQL_DB || process.env.SEQ_DB || 'sequelize_test',
username: process.env.SEQ_MYSQL_USER || process.env.SEQ_USER || 'root',
password: process.env.SEQ_MYSQL_PW || process.env.SEQ_PW || null,
host: process.env.MYSQL_PORT_3306_TCP_ADDR || process.env.SEQ_MYSQL_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.MYSQL_PORT_3306_TCP_PORT || process.env.SEQ_MYSQL_PORT || process.env.SEQ_PORT || 3306,
database: env.SEQ_MYSQL_DB || env.SEQ_DB || 'sequelize_test',
username: env.SEQ_MYSQL_USER || env.SEQ_USER || 'root',
password: env.SEQ_MYSQL_PW || env.SEQ_PW || null,
host: env.MYSQL_PORT_3306_TCP_ADDR || env.SEQ_MYSQL_HOST || env.SEQ_HOST || '127.0.0.1',
port: env.MYSQL_PORT_3306_TCP_PORT || env.SEQ_MYSQL_PORT || env.SEQ_PORT || 3306,
pool: {
max: process.env.SEQ_MYSQL_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
idle: process.env.SEQ_MYSQL_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
max: env.SEQ_MYSQL_POOL_MAX || env.SEQ_POOL_MAX || 5,
idle: env.SEQ_MYSQL_POOL_IDLE || env.SEQ_POOL_IDLE || 3000
}
},
sqlite: {
mariadb: {
database: env.SEQ_MARIADB_DB || env.SEQ_DB || 'sequelize_test',
username: env.SEQ_MARIADB_USER || env.SEQ_USER || 'root',
password: env.SEQ_MARIADB_PW || env.SEQ_PW || null,
host: env.MARIADB_PORT_3306_TCP_ADDR || env.SEQ_MARIADB_HOST || env.SEQ_HOST || '127.0.0.1',
port: env.MARIADB_PORT_3306_TCP_PORT || env.SEQ_MARIADB_PORT || env.SEQ_PORT || 3306,
pool: {
max: env.SEQ_MARIADB_POOL_MAX || env.SEQ_POOL_MAX || 5,
idle: env.SEQ_MARIADB_POOL_IDLE || env.SEQ_POOL_IDLE || 3000
}
},
sqlite: {},
postgres: {
database: process.env.SEQ_PG_DB || process.env.SEQ_DB || 'sequelize_test',
username: process.env.SEQ_PG_USER || process.env.SEQ_USER || 'postgres',
password: process.env.SEQ_PG_PW || process.env.SEQ_PW || 'postgres',
host: process.env.POSTGRES_PORT_5432_TCP_ADDR || process.env.SEQ_PG_HOST || process.env.SEQ_HOST || '127.0.0.1',
port: process.env.POSTGRES_PORT_5432_TCP_PORT || process.env.SEQ_PG_PORT || process.env.SEQ_PORT || 5432,
database: env.SEQ_PG_DB || env.SEQ_DB || 'sequelize_test',
username: env.SEQ_PG_USER || env.SEQ_USER || 'postgres',
password: env.SEQ_PG_PW || env.SEQ_PW || 'postgres',
host: env.POSTGRES_PORT_5432_TCP_ADDR || env.SEQ_PG_HOST || env.SEQ_HOST || '127.0.0.1',
port: env.POSTGRES_PORT_5432_TCP_PORT || env.SEQ_PG_PORT || env.SEQ_PORT || 5432,
pool: {
max: process.env.SEQ_PG_POOL_MAX || process.env.SEQ_POOL_MAX || 5,
idle: process.env.SEQ_PG_POOL_IDLE || process.env.SEQ_POOL_IDLE || 3000
max: env.SEQ_PG_POOL_MAX || env.SEQ_POOL_MAX || 5,
idle: env.SEQ_PG_POOL_IDLE || env.SEQ_POOL_IDLE || 3000
}
}
};
GRANT ALL ON *.* TO 'sequelize_test'@'%' with grant option;
FLUSH PRIVILEGES;
......@@ -190,7 +190,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => {
AcmeProject.belongsToMany(AcmeUser, {through: AcmeProjectUsers});
const ctx = {};
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('acme');
}).then(() => {
return Promise.all([
......@@ -216,8 +216,8 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => {
expect(project.ProjectUsers.status).to.equal('active');
return this.sequelize.dropSchema('acme').then(() => {
return this.sequelize.showAllSchemas().then(schemas => {
if (dialect === 'postgres' || dialect === 'mssql') {
expect(schemas).to.be.empty;
if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') {
expect(schemas).to.not.have.property('acme');
}
});
});
......@@ -2026,7 +2026,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => {
return this.sequelize.sync({force: true}).then(() => {
return this.sequelize.getQueryInterface().showAllTables();
}).then(result => {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
if (dialect === 'mssql' || dialect === 'mariadb') {
result = result.map(v => v.tableName);
}
......@@ -2045,7 +2045,7 @@ describe(Support.getTestDialectTeaser('BelongsToMany'), () => {
return this.sequelize.sync({force: true}).then(() => {
return this.sequelize.getQueryInterface().showAllTables();
}).then(result => {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
if (dialect === 'mssql' || dialect === 'mariadb') {
result = result.map(v => v.tableName);
}
......
......@@ -127,7 +127,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
Task.belongsTo(User);
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('archive');
}).then(() => {
return User.sync({force: true });
......@@ -146,8 +146,8 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
expect(user).to.be.ok;
return this.sequelize.dropSchema('archive').then(() => {
return this.sequelize.showAllSchemas().then(schemas => {
if (dialect === 'postgres' || dialect === 'mssql') {
expect(schemas).to.be.empty;
if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') {
expect(schemas).to.not.have.property('archive');
}
});
});
......@@ -172,7 +172,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
Task.belongsTo(User, { foreignKey: 'user_id'});
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('archive');
}).then(() => {
return User.sync({force: true });
......@@ -188,6 +188,7 @@ describe(Support.getTestDialectTeaser('BelongsTo'), () => {
});
}).then(user => {
expect(user).to.be.ok;
return this.sequelize.dropSchema('archive');
});
});
});
......
......@@ -331,7 +331,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
User.Tasks = User.hasMany(Task, {as: 'tasks'});
Task.SubTasks = Task.hasMany(SubTask, {as: 'subtasks'});
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('work');
}).then(() => {
return User.sync({force: true});
......@@ -430,7 +430,7 @@ describe(Support.getTestDialectTeaser('HasMany'), () => {
expect(users[1].tasks[1].subtasks[1].title).to.equal('a');
return this.sequelize.dropSchema('work').then(() => {
return this.sequelize.showAllSchemas().then(schemas => {
if (dialect === 'postgres' || dialect === 'mssql') {
if (dialect === 'postgres' || dialect === 'mssql' || schemas === 'mariadb') {
expect(schemas).to.be.empty;
}
});
......
......@@ -126,7 +126,7 @@ describe(Support.getTestDialectTeaser('HasOne'), () => {
Group.hasOne(User);
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('admin');
}).then(() => {
return Group.sync({force: true });
......@@ -151,8 +151,8 @@ describe(Support.getTestDialectTeaser('HasOne'), () => {
}).then(() => {
return this.sequelize.dropSchema('admin').then(() => {
return this.sequelize.showAllSchemas().then(schemas => {
if (dialect === 'postgres' || dialect === 'mssql') {
expect(schemas).to.be.empty;
if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') {
expect(schemas).to.not.have.property('admin');
};
});
});
......
......@@ -250,7 +250,8 @@ describe(Support.getTestDialectTeaser('associations'), () => {
it('should make the same query if called multiple time (#4470)', function() {
const logs = [];
const logging = function(log) {
logs.push(log);
//removing 'executing(<uuid> || 'default'}) :' from logs
logs.push(log.substring(log.indexOf(':') + 1));
};
return this.sequelize.sync({force: true}).then(() => {
......
......@@ -43,7 +43,7 @@ describe(Support.getTestDialectTeaser('Configuration'), () => {
it('when we don\'t have a valid dialect.', () => {
expect(() => {
new Sequelize(config[dialect].database, config[dialect].username, config[dialect].password, {host: '0.0.0.1', port: config[dialect].port, dialect: 'some-fancy-dialect'});
}).to.throw(Error, 'The dialect some-fancy-dialect is not supported. Supported dialects: mssql, mysql, postgres, and sqlite.');
}).to.throw(Error, 'The dialect some-fancy-dialect is not supported. Supported dialects: mssql, mariadb, mysql, postgres, and sqlite.');
});
});
......
......@@ -356,8 +356,8 @@ describe(Support.getTestDialectTeaser('DataTypes'), () => {
//This case throw unhandled exception
return User.findAll();
}).then(users =>{
if (dialect === 'mysql') {
// MySQL will return NULL, becuase they lack EMPTY geometry data support.
if (dialect === 'mysql' || dialect === 'mariadb') {
// MySQL will return NULL, because they lack EMPTY geometry data support.
expect(users[0].field).to.be.eql(null);
} else if (dialect === 'postgres' || dialect === 'postgres-native') {
//Empty Geometry data [0,0] as per https://trac.osgeo.org/postgis/ticket/1996
......
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require('../../support'),
dialect = Support.getTestDialect(),
DataTypes = require('../../../../lib/data-types');
if (dialect !== 'mariadb') return;
describe('[MariaDB Specific] Associations', () => {
describe('many-to-many', () => {
describe('where tables have the same prefix', () => {
it('should create a table wp_table1wp_table2s', function() {
const Table2 = this.sequelize.define('wp_table2', {foo: DataTypes.STRING}),
Table1 = this.sequelize.define('wp_table1',
{foo: DataTypes.STRING}),
self = this;
Table1.belongsToMany(Table2, {through: 'wp_table1swp_table2s'});
Table2.belongsToMany(Table1, {through: 'wp_table1swp_table2s'});
return Table1.sync({force: true}).then(() => {
return Table2.sync({force: true}).then(() => {
expect(self.sequelize.modelManager.getModel(
'wp_table1swp_table2s')).to.exist;
});
});
});
});
describe('when join table name is specified', () => {
beforeEach(function() {
const Table2 = this.sequelize.define('ms_table1', {foo: DataTypes.STRING}),
Table1 = this.sequelize.define('ms_table2',
{foo: DataTypes.STRING});
Table1.belongsToMany(Table2, {through: 'table1_to_table2'});
Table2.belongsToMany(Table1, {through: 'table1_to_table2'});
return Table1.sync({force: true}).then(() => {
return Table2.sync({force: true});
});
});
it('should not use only a specified name', function() {
expect(this.sequelize.modelManager.getModel(
'ms_table1sms_table2s')).not.to.exist;
expect(
this.sequelize.modelManager.getModel('table1_to_table2')).to.exist;
});
});
});
describe('HasMany', () => {
beforeEach(function() {
//prevent periods from occurring in the table name since they are used to delimit (table.column)
this.User = this.sequelize.define(`User${Math.ceil(Math.random() * 10000000)}`, {name: DataTypes.STRING});
this.Task = this.sequelize.define(`Task${Math.ceil(Math.random() * 10000000)}`, {name: DataTypes.STRING});
this.users = null;
this.tasks = null;
this.User.belongsToMany(this.Task, {as: 'Tasks', through: 'UserTasks'});
this.Task.belongsToMany(this.User, {as: 'Users', through: 'UserTasks'});
const self = this,
users = [],
tasks = [];
for (let i = 0; i < 5; ++i) {
users[users.length] = {name: `User${Math.random()}`};
}
for (let x = 0; x < 5; ++x) {
tasks[tasks.length] = {name: `Task${Math.random()}`};
}
return this.sequelize.sync({force: true})
.then(() => self.User.bulkCreate(users))
.then(() => self.Task.bulkCreate(tasks));
});
});
});
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require('../../support'),
dialect = Support.getTestDialect(),
env = process.env,
Sequelize = Support.Sequelize;
if (dialect !== 'mariadb') {
return;
}
describe('[MARIADB Specific] Connection Manager', () => {
it('has existing init SQL', () => {
const sequelize = Support.createSequelizeInstance(
{dialectOptions: {initSql: 'SET @myUserVariable=\'myValue\''}});
return sequelize.query('SELECT @myUserVariable')
.then(res => {
expect(res[0]).to.deep.equal([{'@myUserVariable': 'myValue'}]);
sequelize.close();
});
});
it('has existing init SQL array', () => {
const sequelize = Support.createSequelizeInstance(
{
dialectOptions: {
initSql: ['SET @myUserVariable1=\'myValue\'',
'SET @myUserVariable2=\'myValue\'']
}
});
return sequelize.query('SELECT @myUserVariable1, @myUserVariable2')
.then(res => {
expect(res[0]).to.deep.equal(
[{'@myUserVariable1': 'myValue', '@myUserVariable2': 'myValue'}]);
sequelize.close();
});
});
describe('Errors', () => {
const testHost = env.MARIADB_PORT_3306_TCP_ADDR || env.SEQ_MARIADB_HOST || env.SEQ_HOST || '127.0.0.1';
it('Connection timeout', () => {
const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535, dialectOptions: {connectTimeout: 500}});
return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.SequelizeConnectionError);
});
it('ECONNREFUSED', () => {
const sequelize = Support.createSequelizeInstance({ host: testHost, port: 65535});
return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.ConnectionRefusedError);
});
it('ENOTFOUND', () => {
const sequelize = Support.createSequelizeInstance({ host: 'http://wowow.example.com' });
return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotFoundError);
});
it('EHOSTUNREACH', () => {
const sequelize = Support.createSequelizeInstance({ host: '255.255.255.255' });
return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.HostNotReachableError);
});
it('ER_ACCESS_DENIED_ERROR | ELOGIN', () => {
const sequelize = new Support.Sequelize('localhost', 'was', 'ddsd', Support.sequelize.options);
return expect(sequelize.connectionManager.getConnection()).to.have.been.rejectedWith(Sequelize.AccessDeniedError);
});
});
});
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require('../../support'),
dialect = Support.getTestDialect(),
DataTypes = require('../../../../lib/data-types'),
config = require('../../../config/config');
if (dialect !== 'mariadb') return;
describe('[MariaDB Specific] DAOFactory', () => {
describe('constructor', () => {
it('handles extended attributes (unique)', function() {
const User = this.sequelize.define(`User${config.rand()}`, {
username: {type: DataTypes.STRING, unique: true}
}, {timestamps: false});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.rawAttributes)).to.deep.equal({
username: 'VARCHAR(255) UNIQUE',
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'
});
});
it('handles extended attributes (default)', function() {
const User = this.sequelize.define(`User${config.rand()}`, {
username: {type: DataTypes.STRING, defaultValue: 'foo'}
}, {timestamps: false});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.rawAttributes)).to.deep.equal({
username: 'VARCHAR(255) DEFAULT \'foo\'',
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'
});
});
it('handles extended attributes (null)', function() {
const User = this.sequelize.define(`User${config.rand()}`, {
username: {type: DataTypes.STRING, allowNull: false}
}, {timestamps: false});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.rawAttributes)).to.deep.equal({
username: 'VARCHAR(255) NOT NULL',
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY'
});
});
it('handles extended attributes (primaryKey)', function() {
const User = this.sequelize.define(`User${config.rand()}`, {
username: {type: DataTypes.STRING, primaryKey: true}
}, {timestamps: false});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.rawAttributes)).to.deep.equal(
{username: 'VARCHAR(255) PRIMARY KEY'});
});
it('adds timestamps', function() {
const User1 = this.sequelize.define(`User${config.rand()}`, {});
const User2 = this.sequelize.define(`User${config.rand()}`, {},
{timestamps: true});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User1.rawAttributes)).to.deep.equal({
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY',
updatedAt: 'DATETIME NOT NULL',
createdAt: 'DATETIME NOT NULL'
});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User2.rawAttributes)).to.deep.equal({
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY',
updatedAt: 'DATETIME NOT NULL',
createdAt: 'DATETIME NOT NULL'
});
});
it('adds deletedAt if paranoid', function() {
const User = this.sequelize.define(`User${config.rand()}`, {},
{paranoid: true});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.rawAttributes)).to.deep.equal({
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY',
deletedAt: 'DATETIME',
updatedAt: 'DATETIME NOT NULL',
createdAt: 'DATETIME NOT NULL'
});
});
it('underscores timestamps if underscored', function() {
const User = this.sequelize.define(`User${config.rand()}`, {},
{paranoid: true, underscored: true});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.rawAttributes)).to.deep.equal({
id: 'INTEGER NOT NULL auto_increment PRIMARY KEY',
deleted_at: 'DATETIME',
updated_at: 'DATETIME NOT NULL',
created_at: 'DATETIME NOT NULL'
});
});
it('omits text fields with defaultValues', function() {
const User = this.sequelize.define(`User${config.rand()}`,
{name: {type: DataTypes.TEXT, defaultValue: 'helloworld'}});
expect(User.rawAttributes.name.type.toString()).to.equal('TEXT');
});
it('omits blobs fields with defaultValues', function() {
const User = this.sequelize.define(`User${config.rand()}`,
{name: {type: DataTypes.STRING.BINARY, defaultValue: 'helloworld'}});
expect(User.rawAttributes.name.type.toString()).to.equal(
'VARCHAR(255) BINARY');
});
});
describe('primaryKeys', () => {
it('determines the correct primaryKeys', function() {
const User = this.sequelize.define(`User${config.rand()}`, {
foo: {type: DataTypes.STRING, primaryKey: true},
bar: DataTypes.STRING
});
expect(
this.sequelize.getQueryInterface().QueryGenerator.attributesToSQL(
User.primaryKeys)).to.deep.equal(
{'foo': 'VARCHAR(255) PRIMARY KEY'});
});
});
});
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require('../../support'),
dialect = Support.getTestDialect(),
DataTypes = require('../../../../lib/data-types');
if (dialect !== 'mariadb') return;
describe('[MariaDB Specific] DAO', () => {
beforeEach(function() {
this.sequelize.options.quoteIdentifiers = true;
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
email: DataTypes.STRING,
location: DataTypes.GEOMETRY()
});
return this.User.sync({force: true});
});
afterEach(function() {
this.sequelize.options.quoteIdentifiers = true;
});
describe('integers', () => {
describe('integer', () => {
beforeEach(function() {
this.User = this.sequelize.define('User', {
aNumber: DataTypes.INTEGER
});
return this.User.sync({force: true});
});
it('positive', function() {
const User = this.User;
return User.create({aNumber: 2147483647}).then(user => {
expect(user.aNumber).to.equal(2147483647);
return User.findOne({where: {aNumber: 2147483647}}).then(_user => {
expect(_user.aNumber).to.equal(2147483647);
});
});
});
it('negative', function() {
const User = this.User;
return User.create({aNumber: -2147483647}).then(user => {
expect(user.aNumber).to.equal(-2147483647);
return User.findOne({where: {aNumber: -2147483647}}).then(_user => {
expect(_user.aNumber).to.equal(-2147483647);
});
});
});
});
describe('bigint', () => {
beforeEach(function() {
this.User = this.sequelize.define('User', {
aNumber: DataTypes.BIGINT
});
return this.User.sync({force: true});
});
it('positive', function() {
const User = this.User;
return User.create({aNumber: '9223372036854775807'}).then(user => {
expect(user.aNumber).to.equal('9223372036854775807');
return User.findOne({where: {aNumber: '9223372036854775807'}}).then(
_user => {
return expect(_user.aNumber.toString()).to.equal(
'9223372036854775807');
});
});
});
it('negative', function() {
const User = this.User;
return User.create({aNumber: '-9223372036854775807'}).then(user => {
expect(user.aNumber).to.equal('-9223372036854775807');
return User.findOne(
{where: {aNumber: '-9223372036854775807'}}).then(_user => {
return expect(_user.aNumber.toString()).to.equal(
'-9223372036854775807');
});
});
});
});
});
it('should save geometry correctly', function() {
const point = {type: 'Point', coordinates: [39.807222, -76.984722]};
return this.User.create(
{username: 'user', email: 'foo@bar.com', location: point}).then(
newUser => {
expect(newUser.location).to.deep.eql(point);
});
});
it('should update geometry correctly', function() {
const User = this.User;
const point1 = {type: 'Point', coordinates: [39.807222, -76.984722]};
const point2 = {type: 'Point', coordinates: [39.828333, -77.232222]};
return User.create(
{username: 'user', email: 'foo@bar.com', location: point1})
.then(oldUser => {
return User.update({location: point2},
{where: {username: oldUser.username}})
.then(() => {
return User.findOne({where: {username: oldUser.username}});
})
.then(updatedUser => {
expect(updatedUser.location).to.deep.eql(point2);
});
});
});
it('should read geometry correctly', function() {
const User = this.User;
const point = {type: 'Point', coordinates: [39.807222, -76.984722]};
return User.create(
{username: 'user', email: 'foo@bar.com', location: point}).then(
user => {
return User.findOne({where: {username: user.username}});
}).then(user => {
expect(user.location).to.deep.eql(point);
});
});
});
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require('../../support'),
dialect = Support.getTestDialect(),
DataTypes = require('../../../../lib/data-types');
if (dialect !== 'mariadb') return;
describe('[MariaDB Specific] Errors', () => {
const validateError = (promise, errClass, errValues) => {
const wanted = Object.assign({}, errValues);
return expect(promise).to.have.been.rejectedWith(errClass).then(() =>
promise.catch(err => Object.keys(wanted).forEach(
k => expect(err[k]).to.eql(wanted[k]))));
};
describe('ForeignKeyConstraintError', () => {
beforeEach(function() {
this.Task = this.sequelize.define('task', {title: DataTypes.STRING});
this.User = this.sequelize.define('user', {username: DataTypes.STRING});
this.UserTasks = this.sequelize.define('tasksusers',
{userId: DataTypes.INTEGER, taskId: DataTypes.INTEGER});
this.User.belongsToMany(this.Task,
{onDelete: 'RESTRICT', through: 'tasksusers'});
this.Task.belongsToMany(this.User,
{onDelete: 'RESTRICT', through: 'tasksusers'});
this.Task.belongsTo(this.User,
{foreignKey: 'primaryUserId', as: 'primaryUsers'});
});
it('in context of DELETE restriction', function() {
const self = this,
ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError;
return this.sequelize.sync({force: true}).bind({}).then(() => {
return Promise.all([
self.User.create({id: 67, username: 'foo'}),
self.Task.create({id: 52, title: 'task'})
]);
}).spread(function(user1, task1) {
this.user1 = user1;
this.task1 = task1;
return user1.setTasks([task1]);
}).then(function() {
return Promise.all([
validateError(this.user1.destroy(), ForeignKeyConstraintError, {
fields: ['userId'],
table: 'users',
value: undefined,
index: 'tasksusers_ibfk_1',
reltype: 'parent'
}),
validateError(this.task1.destroy(), ForeignKeyConstraintError, {
fields: ['taskId'],
table: 'tasks',
value: undefined,
index: 'tasksusers_ibfk_2',
reltype: 'parent'
})
]);
});
});
it('in context of missing relation', function() {
const self = this,
ForeignKeyConstraintError = this.sequelize.ForeignKeyConstraintError;
return this.sequelize.sync({force: true}).then(() =>
validateError(self.Task.create({title: 'task', primaryUserId: 5}),
ForeignKeyConstraintError, {
fields: ['primaryUserId'],
table: 'users',
value: 5,
index: 'tasks_ibfk_1',
reltype: 'child'
}));
});
});
});
'use strict';
const chai = require('chai'),
expect = chai.expect,
Support = require('../../support'),
dialect = Support.getTestDialect();
if (dialect.match(/^mariadb/)) {
describe('QueryInterface', () => {
describe('databases', () => {
it('should create and drop database', function() {
return this.sequelize.query('SHOW DATABASES')
.then(res => {
const databaseNumber = res[0].length;
return this.sequelize.getQueryInterface().createDatabase('myDB')
.then(() => {
return this.sequelize.query('SHOW DATABASES');
})
.then(databases => {
expect(databases[0]).to.have.length(databaseNumber + 1);
return this.sequelize.getQueryInterface().dropDatabase('myDB');
});
});
});
});
});
}
......@@ -16,9 +16,13 @@ const sortById = function(a, b) {
describe(Support.getTestDialectTeaser('Includes with schemas'), () => {
describe('findAll', () => {
afterEach(function() {
return this.sequelize.dropSchema('account');
});
beforeEach(function() {
this.fixtureA = function() {
return this.sequelize.dropAllSchemas().then(() => {
return this.sequelize.dropSchema('account').then(() => {
return this.sequelize.createSchema('account').then(() => {
const AccUser = this.sequelize.define('AccUser', {}, {schema: 'account'}),
Company = this.sequelize.define('Company', {
......@@ -190,10 +194,11 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => {
});
});
};
return this.sequelize.createSchema('account');
});
it('should support an include with multiple different association types', function() {
return this.sequelize.dropAllSchemas().then(() => {
return this.sequelize.dropSchema('account').then(() => {
return this.sequelize.createSchema('account').then(() => {
const AccUser = this.sequelize.define('AccUser', {}, {schema: 'account'}),
Product = this.sequelize.define('Product', {
......@@ -1170,7 +1175,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => {
});
});
it('should support including date fields, with the correct timeszone', function() {
it('should support including date fields, with the correct timezone', function() {
const User = this.sequelize.define('user', {
dateField: Sequelize.DATE
}, {timestamps: false, schema: 'account'}),
......@@ -1260,7 +1265,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => {
foreignKey: 'UserId'
});
return this.sequelize.dropAllSchemas().then(() => {
return this.sequelize.dropSchema('hero').then(() => {
return this.sequelize.createSchema('hero');
}).then(() => {
return this.sequelize.sync({force: true}).then(() => {
......@@ -1274,7 +1279,7 @@ describe(Support.getTestDialectTeaser('Includes with schemas'), () => {
}]
});
});
});
}).then(() => this.sequelize.dropSchema('hero'));
});
});
});
......@@ -442,7 +442,7 @@ if (current.dialect.supports.groupedLimit) {
User.Tasks = User.hasMany(Task, {as: 'tasks'});
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('archive').then(() => {
return this.sequelize.sync({force: true}).then(() => {
return Promise.join(
......@@ -484,8 +484,8 @@ if (current.dialect.supports.groupedLimit) {
expect(result[1].tasks[1].title).to.equal('c');
return this.sequelize.dropSchema('archive').then(() => {
return this.sequelize.showAllSchemas().then(schemas => {
if (dialect === 'postgres' || dialect === 'mssql') {
expect(schemas).to.be.empty;
if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') {
expect(schemas).to.not.have.property('archive');
}
});
});
......
......@@ -26,7 +26,10 @@ describe('model', () => {
it('should tell me that a column is json', function() {
return this.sequelize.queryInterface.describeTable('Users')
.then(table => {
expect(table.emergency_contact.type).to.equal('JSON');
// expected for mariadb 10.4 : https://jira.mariadb.org/browse/MDEV-15558
if (dialect !== 'mariadb') {
expect(table.emergency_contact.type).to.equal('JSON');
}
});
});
......@@ -37,7 +40,7 @@ describe('model', () => {
}, {
fields: ['id', 'username', 'document', 'emergency_contact'],
logging: sql => {
if (dialect.match(/^mysql/)) {
if (dialect.match(/^mysql|mariadb/)) {
expect(sql).to.include('?');
} else {
expect(sql).to.include('$1');
......
......@@ -1647,12 +1647,10 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
});
};
return this.sequelize.queryInterface.dropAllSchemas().then(() => {
return this.sequelize.queryInterface.createSchema('prefix').then(() => {
return run.call(this);
});
});
return Support.dropTestSchemas(this.sequelize)
.then(() => this.sequelize.queryInterface.createSchema('prefix'))
.then(() => run.call(this))
.then(() => this.sequelize.queryInterface.dropSchema('prefix'));
});
it('should work if model is paranoid and only operator in where clause is a Symbol', function() {
......@@ -2190,15 +2188,19 @@ describe(Support.getTestDialectTeaser('Model'), () => {
age: Sequelize.INTEGER
});
return this.sequelize.dropAllSchemas().then(() => {
return this.sequelize.createSchema('schema_test').then(() => {
return this.sequelize.createSchema('special').then(() => {
return this.UserSpecial.schema('special').sync({force: true}).then(UserSpecialSync => {
this.UserSpecialSync = UserSpecialSync;
});
});
return Support.dropTestSchemas(this.sequelize)
.then(() => this.sequelize.createSchema('schema_test'))
.then(() => this.sequelize.createSchema('special'))
.then(() => this.UserSpecial.schema('special').sync({force: true}))
.then(UserSpecialSync => {
this.UserSpecialSync = UserSpecialSync;
});
});
});
afterEach(function() {
return this.sequelize.dropSchema('schema_test')
.finally(() => this.sequelize.dropSchema('special'))
.finally(() => this.sequelize.dropSchema('prefix'));
});
it('should be able to drop with schemas', function() {
......@@ -2211,7 +2213,18 @@ describe(Support.getTestDialectTeaser('Model'), () => {
// sqlite & MySQL doesn't actually create schemas unless Model.sync() is called
// Postgres supports schemas natively
expect(schemas).to.have.length(dialect === 'postgres' || dialect === 'mssql' ? 2 : 1);
switch (dialect) {
case 'mssql':
case 'postgres':
expect(schemas).to.have.length(2);
break;
case 'mariadb':
expect(schemas).to.have.length(3);
break;
default :
expect(schemas).to.have.length(1);
break;
}
});
});
......@@ -2254,7 +2267,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return UserPublic.schema('special').sync({ force: true }).then(() => {
return this.sequelize.queryInterface.describeTable('Publics', {
logging(sql) {
if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql') {
if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql' || dialect === 'mariadb') {
expect(sql).to.not.contain('special');
count++;
}
......@@ -2267,7 +2280,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return this.sequelize.queryInterface.describeTable('Publics', {
schema: 'special',
logging(sql) {
if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql') {
if (dialect === 'sqlite' || dialect === 'mysql' || dialect === 'mssql' || dialect === 'mariadb') {
expect(sql).to.contain('special');
count++;
}
......@@ -2305,6 +2318,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
expect(sql).to.match(/REFERENCES\s+"prefix"\."UserPubs" \("id"\)/);
} else if (dialect === 'mssql') {
expect(sql).to.match(/REFERENCES\s+\[prefix\]\.\[UserPubs\] \(\[id\]\)/);
} else if (dialect === 'mariadb') {
expect(sql).to.match(/REFERENCES\s+`prefix`\.`UserPubs` \(`id`\)/);
} else {
expect(sql).to.match(/REFERENCES\s+`prefix\.UserPubs` \(`id`\)/);
}
......@@ -2313,8 +2328,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
});
};
if (dialect === 'postgres' || dialect === 'mssql') {
return this.sequelize.queryInterface.dropAllSchemas().then(() => {
if (dialect === 'postgres' || dialect === 'mssql' || dialect === 'mariadb') {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.queryInterface.createSchema('prefix').then(() => {
return run.call(this);
});
......@@ -2338,6 +2353,9 @@ describe(Support.getTestDialectTeaser('Model'), () => {
} else if (dialect === 'mssql') {
expect(this.UserSpecialSync.getTableName().toString()).to.equal('[special].[UserSpecials]');
expect(UserPublic).to.include('INSERT INTO [UserPublics]');
} else if (dialect === 'mariadb') {
expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special`.`UserSpecials`');
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).to.be.above(-1);
} else {
expect(this.UserSpecialSync.getTableName().toString()).to.equal('`special.UserSpecials`');
expect(UserPublic).to.include('INSERT INTO `UserPublics`');
......@@ -2353,6 +2371,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`');
} else if (dialect === 'mssql') {
expect(UserSpecial).to.include('INSERT INTO [special].[UserSpecials]');
} else if (dialect === 'mariadb') {
expect(UserSpecial).to.include('INSERT INTO `special`.`UserSpecials`');
} else {
expect(UserSpecial).to.include('INSERT INTO `special.UserSpecials`');
}
......@@ -2365,6 +2385,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
expect(user).to.include('UPDATE "special"."UserSpecials"');
} else if (dialect === 'mssql') {
expect(user).to.include('UPDATE [special].[UserSpecials]');
} else if (dialect === 'mariadb') {
expect(user).to.include('UPDATE `special`.`UserSpecials`');
} else {
expect(user).to.include('UPDATE `special.UserSpecials`');
}
......@@ -2404,7 +2426,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return Post.sync({logging: _.once(sql => {
if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/);
} else if (dialect === 'mysql') {
} else if (dialect === 'mysql' || dialect === 'mariadb') {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/);
} else if (dialect === 'mssql') {
expect(sql).to.match(/FOREIGN KEY \(\[authorId\]\) REFERENCES \[authors\] \(\[id\]\)/);
......@@ -2428,7 +2450,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
return Post.sync({logging: _.once(sql => {
if (dialect === 'postgres') {
expect(sql).to.match(/"authorId" INTEGER REFERENCES "authors" \("id"\)/);
} else if (dialect === 'mysql') {
} else if (dialect === 'mysql' || dialect === 'mariadb') {
expect(sql).to.match(/FOREIGN KEY \(`authorId`\) REFERENCES `authors` \(`id`\)/);
} else if (dialect === 'sqlite') {
expect(sql).to.match(/`authorId` INTEGER REFERENCES `authors` \(`id`\)/);
......@@ -2462,7 +2484,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}).catch (err => {
if (dialect === 'mysql') {
// MySQL 5.7 or above doesn't support POINT EMPTY
if (dialect === 'mysql' && semver.gte(current.options.databaseVersion, '5.6.0')) {
if (semver.gte(current.options.databaseVersion, '5.6.0')) {
expect(err.message).to.match(/Cannot add foreign key constraint/);
} else {
expect(err.message).to.match(/Can\'t create table/);
......@@ -2470,6 +2492,8 @@ describe(Support.getTestDialectTeaser('Model'), () => {
} else if (dialect === 'sqlite') {
// the parser should not end up here ... see above
expect(1).to.equal(2);
} else if (dialect === 'mariadb') {
expect(err.message).to.match(/Foreign key constraint is incorrectly formed/);
} else if (dialect === 'postgres') {
expect(err.message).to.match(/relation "4uth0r5" does not exist/);
} else if (dialect === 'mssql') {
......
......@@ -399,7 +399,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
tableName: 'Dummy'
});
return this.sequelize.dropAllSchemas().then(() => {
return Support.dropTestSchemas(this.sequelize).then(() => {
return this.sequelize.createSchema('space1');
}).then(() => {
return Dummy.sync({force: true});
......
......@@ -797,7 +797,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
}
it('is possible to use casting when creating an instance', function() {
const type = dialect === 'mysql' ? 'signed' : 'integer';
const type = dialect === 'mysql' || dialect === 'mariadb' ? 'signed' : 'integer';
let match = false;
return this.User.create({
......@@ -819,7 +819,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
let type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'integer'), 'integer'),
match = false;
if (dialect === 'mysql') {
if (dialect === 'mysql' || dialect === 'mariadb') {
type = this.sequelize.cast(this.sequelize.cast(this.sequelize.literal('1-2'), 'unsigned'), 'signed');
}
......@@ -827,7 +827,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
intVal: type
}, {
logging(sql) {
if (dialect === 'mysql') {
if (dialect === 'mysql' || dialect === 'mariadb') {
expect(sql).to.contain('CAST(CAST(1-2 AS UNSIGNED) AS SIGNED)');
} else {
expect(sql).to.contain('CAST(CAST(1-2 AS INTEGER) AS INTEGER)');
......
......@@ -104,6 +104,31 @@ describe(Support.getTestDialectTeaser('Model'), () => {
attributes: []
})).to.eventually.equal(1);
});
if (Support.sequelize.dialect.supports.schemas) {
it('aggregate with schema', function() {
this.Hero = this.sequelize.define('Hero', {
codename: Sequelize.STRING
}, {schema: 'heroschema'});
return this.sequelize.createSchema('heroschema')
.then(() => {
return this.sequelize.sync({force: true});
})
.then(() => {
const records = [
{codename: 'hulk'},
{codename: 'rantanplan'}
];
return this.Hero.bulkCreate(records);
})
.then(() => {
return expect(
this.Hero.unscoped().aggregate('*', 'count',
{schema: 'heroschema'})).to.eventually.equal(
2);
});
});
}
});
});
});
......@@ -362,7 +362,7 @@ describe(Support.getTestDialectTeaser('Model'), () => {
if (dialect === 'sqlite') {
expect(created).to.be.undefined;
} else {
// After set node-mysql flags = '-FOUND_ROWS' in connection of mysql,
// After set node-mysql flags = '-FOUND_ROWS' / foundRows=false
// result from upsert should be false when upsert a row to its current value
// https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html
expect(created).to.equal(false);
......
......@@ -13,6 +13,7 @@ function assertSameConnection(newConnection, oldConnection) {
expect(oldConnection.processID).to.be.equal(newConnection.processID).and.to.be.ok;
break;
case 'mariadb':
case 'mysql':
expect(oldConnection.threadId).to.be.equal(newConnection.threadId).and.to.be.ok;
break;
......@@ -32,6 +33,7 @@ function assertNewConnection(newConnection, oldConnection) {
expect(oldConnection.processID).to.not.be.equal(newConnection.processID);
break;
case 'mariadb':
case 'mysql':
expect(oldConnection.threadId).to.not.be.equal(newConnection.threadId);
break;
......
......@@ -16,23 +16,45 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
afterEach(function() {
return this.sequelize.dropAllSchemas();
return Support.dropTestSchemas(this.sequelize);
});
describe('dropAllSchema', () => {
it('should drop all schema', function() {
return this.queryInterface.dropAllSchemas(
{skip: [this.sequelize.config.database]})
.then(() => {
return this.queryInterface.showAllSchemas();
})
.then(schemaNames => {
return this.queryInterface.createSchema('newSchema')
.then(() => {
return this.queryInterface.showAllSchemas();
})
.then(newSchemaNames => {
if (!current.dialect.supports.schemas) return;
expect(newSchemaNames).to.have.length(schemaNames.length + 1);
return this.queryInterface.dropSchema('newSchema');
});
});
});
});
describe('renameTable', () => {
it('should rename table', function() {
return this.queryInterface
.createTable('myTestTable', {
.createTable('my_test_table', {
name: DataTypes.STRING
})
.then(() => this.queryInterface.renameTable('myTestTable', 'myTestTableNew'))
.then(() => this.queryInterface.renameTable('my_test_table', 'my_test_table_new'))
.then(() => this.queryInterface.showAllTables())
.then(tableNames => {
if (dialect === 'mssql') {
if (dialect === 'mssql' || dialect === 'mariadb') {
tableNames = tableNames.map(v => v.tableName);
}
expect(tableNames).to.contain('myTestTableNew');
expect(tableNames).to.not.contain('myTestTable');
expect(tableNames).to.contain('my_test_table_new');
expect(tableNames).to.not.contain('my_test_table');
});
});
});
......@@ -40,41 +62,46 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
describe('dropAllTables', () => {
it('should drop all tables', function() {
const filterMSSQLDefault = tableNames => tableNames.filter(t => t.tableName !== 'spt_values');
return this.queryInterface.dropAllTables().then(() => {
return this.queryInterface.showAllTables().then(tableNames => {
return this.queryInterface.dropAllTables()
.then(() => {
return this.queryInterface.showAllTables();
})
.then(tableNames => {
// MSSQL include spt_values table which is system defined, hence cant be dropped
tableNames = filterMSSQLDefault(tableNames);
expect(tableNames).to.be.empty;
return this.queryInterface.createTable('table', {name: DataTypes.STRING});
})
.then(() => {
return this.queryInterface.showAllTables();
})
.then(tableNames => {
tableNames = filterMSSQLDefault(tableNames);
expect(tableNames).to.have.length(1);
return this.queryInterface.dropAllTables();
})
.then(() => {
return this.queryInterface.showAllTables();
})
.then(tableNames => {
// MSSQL include spt_values table which is system defined, hence cant be dropped
tableNames = filterMSSQLDefault(tableNames);
expect(tableNames).to.be.empty;
return this.queryInterface.createTable('table', { name: DataTypes.STRING }).then(() => {
return this.queryInterface.showAllTables().then(tableNames => {
tableNames = filterMSSQLDefault(tableNames);
expect(tableNames).to.have.length(1);
return this.queryInterface.dropAllTables().then(() => {
return this.queryInterface.showAllTables().then(tableNames => {
// MSSQL include spt_values table which is system defined, hence cant be dropped
tableNames = filterMSSQLDefault(tableNames);
expect(tableNames).to.be.empty;
});
});
});
});
});
});
});
it('should be able to skip given tables', function() {
return this.queryInterface.createTable('skipme', {
name: DataTypes.STRING
}).then(() => {
return this.queryInterface.dropAllTables({skip: ['skipme']}).then(() => {
return this.queryInterface.showAllTables().then(tableNames => {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
tableNames = tableNames.map(v => v.tableName);
}
expect(tableNames).to.contain('skipme');
});
})
.then(() => this.queryInterface.dropAllTables({skip: ['skipme']}))
.then(() => this.queryInterface.showAllTables())
.then(tableNames => {
if (dialect === 'mssql' || dialect === 'mariadb') {
tableNames = tableNames.map(v => v.tableName);
}
expect(tableNames).to.contain('skipme');
});
});
});
});
......@@ -286,6 +313,20 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
});
it('addColumn expected error', function() {
return this.queryInterface.createTable('level2', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
}
}).then(() => {
expect(this.queryInterface.addColumn.bind(this, 'users', 'level_id')).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
expect(this.queryInterface.addColumn.bind(this, null, 'level_id')).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
expect(this.queryInterface.addColumn.bind(this, 'users', null, {})).to.throw(Error, 'addColumn takes at least 3 arguments (table, attribute name, attribute definition)');
});
});
it('should work with schemas', function() {
return this.queryInterface.createTable({
tableName: 'users',
......@@ -473,6 +514,13 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
expect(constraints).to.not.include('check_user_roles');
});
});
it('addconstraint missing type', function() {
expect(this.queryInterface.addConstraint.bind(this, 'users', ['roles'], {
where: {roles: ['user', 'admin', 'guest', 'moderator']},
name: 'check_user_roles'
})).to.throw(Error, 'Constraint type must be specified through options.type');
});
});
}
......@@ -517,7 +565,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
//The name of primaryKey constraint is always PRIMARY in case of mysql
if (dialect === 'mysql') {
if (dialect === 'mysql' || dialect === 'mariadb') {
expect(constraints).to.include('PRIMARY');
return this.queryInterface.removeConstraint('users', 'PRIMARY');
}
......@@ -527,7 +575,11 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
.then(() => this.queryInterface.showConstraint('users'))
.then(constraints => {
constraints = constraints.map(constraint => constraint.constraintName);
expect(constraints).to.not.include('users_username_pk');
if (dialect === 'mysql' || dialect === 'mariadb') {
expect(constraints).to.not.include('PRIMARY');
} else {
expect(constraints).to.not.include('users_username_pk');
}
});
});
});
......
......@@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
afterEach(function() {
return this.sequelize.dropAllSchemas();
return Support.dropTestSchemas(this.sequelize);
});
describe('changeColumn', () => {
......@@ -223,4 +223,4 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
}
});
});
\ No newline at end of file
});
......@@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
afterEach(function() {
return this.sequelize.dropAllSchemas();
return Support.dropTestSchemas(this.sequelize);
});
// FIXME: These tests should make assertions against the created table using describeTable
......@@ -74,6 +74,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
expect(indexes[1].fields[0].attribute).to.equal('name');
break;
case 'mariadb':
case 'mysql':
// name + email
expect(indexes[1].unique).to.be.true;
......
......@@ -13,10 +13,44 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
afterEach(function() {
return this.sequelize.dropAllSchemas();
return Support.dropTestSchemas(this.sequelize);
});
describe('describeTable', () => {
if (Support.sequelize.dialect.supports.schemas) {
it('reads the metadata of the table with schema', function() {
const MyTable1 = this.sequelize.define('my_table', {
username1: DataTypes.STRING
});
const MyTable2 = this.sequelize.define('my_table', {
username2: DataTypes.STRING
}, {schema: 'test_meta'});
return this.sequelize.createSchema('test_meta')
.then(() => {
return MyTable1.sync({force: true});
})
.then(() => {
return MyTable2.sync({force: true});
})
.then(() => {
return this.queryInterface.describeTable('my_tables', 'test_meta');
})
.then(metadata => {
expect(metadata.username2).not.to.be.undefined;
})
.then(() => {
return this.queryInterface.describeTable('my_tables');
})
.then(metadata => {
expect(metadata.username1).not.to.be.undefined;
return this.sequelize.dropSchema('test_meta');
});
});
}
it('reads the metadata of the table', function() {
const Users = this.sequelize.define('_Users', {
username: DataTypes.STRING,
......@@ -27,9 +61,9 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
},
isAdmin: DataTypes.BOOLEAN,
enumVals: DataTypes.ENUM('hello', 'world')
}, { freezeTableName: true });
}, {freezeTableName: true});
return Users.sync({ force: true }).then(() => {
return Users.sync({force: true}).then(() => {
return this.queryInterface.describeTable('_Users').then(metadata => {
const id = metadata.id;
const username = metadata.username;
......@@ -105,37 +139,41 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
it('should correctly determine the primary key columns', function() {
const Country = this.sequelize.define('_Country', {
code: {type: DataTypes.STRING, primaryKey: true },
code: {type: DataTypes.STRING, primaryKey: true},
name: {type: DataTypes.STRING, allowNull: false}
}, { freezeTableName: true });
}, {freezeTableName: true});
const Alumni = this.sequelize.define('_Alumni', {
year: {type: DataTypes.INTEGER, primaryKey: true },
num: {type: DataTypes.INTEGER, primaryKey: true },
username: {type: DataTypes.STRING, allowNull: false, unique: true },
dob: {type: DataTypes.DATEONLY, allowNull: false },
dod: {type: DataTypes.DATEONLY, allowNull: true },
year: {type: DataTypes.INTEGER, primaryKey: true},
num: {type: DataTypes.INTEGER, primaryKey: true},
username: {type: DataTypes.STRING, allowNull: false, unique: true},
dob: {type: DataTypes.DATEONLY, allowNull: false},
dod: {type: DataTypes.DATEONLY, allowNull: true},
city: {type: DataTypes.STRING, allowNull: false},
ctrycod: {type: DataTypes.STRING, allowNull: false,
references: { model: Country, key: 'code'}}
}, { freezeTableName: true });
return Country.sync({ force: true }).then(() => {
return this.queryInterface.describeTable('_Country').then(metacountry => {
expect(metacountry.code.primaryKey).to.eql(true);
expect(metacountry.name.primaryKey).to.eql(false);
return Alumni.sync({ force: true }).then(() => {
return this.queryInterface.describeTable('_Alumni').then(metalumni => {
expect(metalumni.year.primaryKey).to.eql(true);
expect(metalumni.num.primaryKey).to.eql(true);
expect(metalumni.username.primaryKey).to.eql(false);
expect(metalumni.dob.primaryKey).to.eql(false);
expect(metalumni.dod.primaryKey).to.eql(false);
expect(metalumni.ctrycod.primaryKey).to.eql(false);
expect(metalumni.city.primaryKey).to.eql(false);
ctrycod: {
type: DataTypes.STRING, allowNull: false,
references: {model: Country, key: 'code'}
}
}, {freezeTableName: true});
return Country.sync({force: true}).then(() => {
return this.queryInterface.describeTable('_Country').then(
metacountry => {
expect(metacountry.code.primaryKey).to.eql(true);
expect(metacountry.name.primaryKey).to.eql(false);
return Alumni.sync({force: true}).then(() => {
return this.queryInterface.describeTable('_Alumni').then(
metalumni => {
expect(metalumni.year.primaryKey).to.eql(true);
expect(metalumni.num.primaryKey).to.eql(true);
expect(metalumni.username.primaryKey).to.eql(false);
expect(metalumni.dob.primaryKey).to.eql(false);
expect(metalumni.dod.primaryKey).to.eql(false);
expect(metalumni.ctrycod.primaryKey).to.eql(false);
expect(metalumni.city.primaryKey).to.eql(false);
});
});
});
});
});
});
});
......
......@@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
afterEach(function() {
return this.sequelize.dropAllSchemas();
return Support.dropTestSchemas(this.sequelize);
});
describe('dropEnum', () => {
......
......@@ -13,7 +13,7 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
});
afterEach(function() {
return this.sequelize.dropAllSchemas();
return Support.dropTestSchemas(this.sequelize);
});
describe('removeColumn', () => {
......@@ -190,4 +190,4 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
}
});
});
});
\ No newline at end of file
});
......@@ -19,7 +19,7 @@ const qq = str => {
if (dialect === 'postgres' || dialect === 'mssql') {
return `"${str}"`;
}
if (dialect === 'mysql' || dialect === 'sqlite') {
if (dialect === 'mysql' || dialect === 'mariadb' || dialect === 'sqlite') {
return `\`${str}\``;
}
return str;
......@@ -280,7 +280,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
return sequelize.query('select 1;').then(() => {
expect(logger.calledOnce).to.be.true;
expect(logger.args[0][0]).to.be.match(/Executed \(default\): select 1; Elapsed time: \d+ms/);
expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1; Elapsed time: \d+ms/);
});
});
......@@ -293,7 +293,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
return sequelize.query('select 1;').then(() => {
expect(logger.calledOnce).to.be.true;
expect(logger.args[0][0]).to.be.equal('Executed (default): select 1;');
expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1/);
expect(typeof logger.args[0][1] === 'number').to.be.true;
});
});
......@@ -305,7 +305,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
benchmark: true
}).then(() => {
expect(logger.calledOnce).to.be.true;
expect(logger.args[0][0]).to.be.match(/Executed \(default\): select 1; Elapsed time: \d+ms/);
expect(logger.args[0][0]).to.be.match(/Executed \((\d*|default)\): select 1; Elapsed time: \d+ms/);
});
});
......@@ -317,7 +317,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
benchmark: true
}).then(() => {
expect(logger.calledOnce).to.be.true;
expect(logger.args[0][0]).to.be.equal('Executed (default): select 1;');
expect(logger.args[0][0]).to.be.match(/Executed \(\d*|default\): select 1;/);
expect(typeof logger.args[0][1] === 'number').to.be.true;
});
});
......@@ -892,7 +892,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
const Photo = this.sequelize.define('Foto', { name: DataTypes.STRING }, { tableName: 'photos' });
return Photo.sync({ force: true }).then(() => {
return this.sequelize.getQueryInterface().showAllTables().then(tableNames => {
if (dialect === 'mssql' /* current.dialect.supports.schemas */) {
if (dialect === 'mssql' || dialect === 'mariadb') {
tableNames = tableNames.map(v => v.tableName);
}
expect(tableNames).to.include('photos');
......
......@@ -137,9 +137,32 @@ const Support = {
return sequelize
.getQueryInterface()
.dropAllEnums();
})
.then(() => {
return this.dropTestSchemas(sequelize);
});
},
dropTestSchemas(sequelize) {
const queryInterface = sequelize.getQueryInterface();
if (!queryInterface.QueryGenerator._dialect.supports.schemas) {
return this.sequelize.drop({});
}
return sequelize.showAllSchemas().then(schemas => {
const schemasPromise = [];
schemas.forEach(schema => {
const schemaName = schema.name ? schema.name : schema;
if (schemaName !== sequelize.config.database) {
schemasPromise.push(sequelize.dropSchema(schemaName));
}
});
return Promise.all(schemasPromise.map(p => p.catch(e => e)))
.then(() => {}, () => {});
});
},
getSupportedDialects() {
return fs.readdirSync(`${__dirname}/../lib/dialects`)
.filter(file => !file.includes('.js') && !file.includes('abstract'));
......
......@@ -28,6 +28,7 @@ describe(Support.getTestDialectTeaser('Sequelize'), () => {
switch (dialect) {
case 'postgres': dialectPath = path.join(dialectPath, 'pg'); break;
case 'mysql': dialectPath = path.join(dialectPath, 'mysql2'); break;
case 'mariadb': dialectPath = path.join(dialectPath, 'mariadb'); break;
case 'mssql': dialectPath = path.join(dialectPath, 'tedious'); break;
case 'sqlite': dialectPath = path.join(dialectPath, 'sqlite3'); break;
default: throw Error('Unsupported dialect');
......
'use strict';
const chai = require('chai'),
expect = chai.expect,
QuoteHelper = require('../../../../lib/dialects/abstract/query-generator/helpers/quote');
describe('QuoteIdentifier', () => {
it('unknown dialect', () => {
expect(
QuoteHelper.quoteIdentifier.bind(this, 'unknown', 'id', {})).to.throw(
Error);
});
});
'use strict';
const chai = require('chai');
const expect = chai.expect;
const Support = require('../../support');
const Sequelize = Support.Sequelize;
const dialect = Support.getTestDialect();
const queryProto = Support.sequelize.dialect.Query.prototype;
if (dialect === 'mariadb') {
describe('[MARIADB Specific] ForeignKeyConstraintError - error message parsing', () => {
it('FK Errors with ` quotation char are parsed correctly', () => {
const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails (`table`.`brothers`, CONSTRAINT `brothers_ibfk_1` FOREIGN KEY (`personId`) REFERENCES `people` (`id`) ON UPDATE CASCADE).');
fakeErr.errno = 1451;
const parsedErr = queryProto.formatError(fakeErr);
expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError);
expect(parsedErr.parent).to.equal(fakeErr);
expect(parsedErr.reltype).to.equal('parent');
expect(parsedErr.table).to.equal('people');
expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']);
expect(parsedErr.value).to.be.undefined;
expect(parsedErr.index).to.equal('brothers_ibfk_1');
});
it('FK Errors with " quotation char are parsed correctly', () => {
const fakeErr = new Error('Cannot delete or update a parent row: a foreign key constraint fails ("table"."brothers", CONSTRAINT "brothers_ibfk_1" FOREIGN KEY ("personId") REFERENCES "people" ("id") ON UPDATE CASCADE).');
fakeErr.errno = 1451;
const parsedErr = queryProto.formatError(fakeErr);
expect(parsedErr).to.be.instanceOf(Sequelize.ForeignKeyConstraintError);
expect(parsedErr.parent).to.equal(fakeErr);
expect(parsedErr.reltype).to.equal('parent');
expect(parsedErr.table).to.equal('people');
expect(parsedErr.fields).to.be.an('array').to.deep.equal(['personId']);
expect(parsedErr.value).to.be.undefined;
expect(parsedErr.index).to.equal('brothers_ibfk_1');
});
});
}
......@@ -9,7 +9,7 @@ const sinon = require('sinon');
const current = Support.sequelize;
const expect = chai.expect;
describe('[MYSQL Specific] Query', () => {
describe('[MYSQL/MARIADB Specific] Query', () => {
describe('logWarnings', () => {
beforeEach(() => {
sinon.spy(console, 'log');
......
......@@ -7,7 +7,7 @@ const Support = require('../support'),
sql = current.dialect.QueryGenerator;
if (current.dialect.name === 'mysql') {
if (['mysql', 'mariadb'].includes(current.dialect.name)) {
describe(Support.getTestDialectTeaser('SQL'), () => {
describe('addColumn', () => {
......@@ -24,6 +24,7 @@ if (current.dialect.name === 'mysql') {
type: DataTypes.FLOAT,
allowNull: false
})), {
mariadb: 'ALTER TABLE `users` ADD `level_id` FLOAT NOT NULL;',
mysql: 'ALTER TABLE `users` ADD `level_id` FLOAT NOT NULL;'
});
});
......@@ -38,6 +39,7 @@ if (current.dialect.name === 'mysql') {
onUpdate: 'cascade',
onDelete: 'cascade'
})), {
mariadb: 'ALTER TABLE `users` ADD `level_id` INTEGER, ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;',
mysql: 'ALTER TABLE `users` ADD `level_id` INTEGER, ADD CONSTRAINT `users_level_id_foreign_idx` FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;'
});
});
......@@ -47,6 +49,7 @@ if (current.dialect.name === 'mysql') {
type: DataTypes.STRING,
first: true
})), {
mariadb: 'ALTER TABLE `users` ADD `test_added_col_first` VARCHAR(255) FIRST;',
mysql: 'ALTER TABLE `users` ADD `test_added_col_first` VARCHAR(255) FIRST;'
});
});
......@@ -56,6 +59,7 @@ if (current.dialect.name === 'mysql') {
type: DataTypes.STRING,
comment: 'This is a comment'
})), {
mariadb: 'ALTER TABLE `users` ADD `column_with_comment` VARCHAR(255) COMMENT \'This is a comment\';',
mysql: 'ALTER TABLE `users` ADD `column_with_comment` VARCHAR(255) COMMENT \'This is a comment\';'
});
});
......
......@@ -45,6 +45,7 @@ if (current.dialect.name !== 'sqlite') {
}).then(sql => {
expectsql(sql, {
mssql: 'ALTER TABLE [users] ALTER COLUMN [level_id] FLOAT NOT NULL;',
mariadb: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;',
mysql: 'ALTER TABLE `users` CHANGE `level_id` `level_id` FLOAT NOT NULL;',
postgres: 'ALTER TABLE "users" ALTER COLUMN "level_id" SET NOT NULL;ALTER TABLE "users" ALTER COLUMN "level_id" DROP DEFAULT;ALTER TABLE "users" ALTER COLUMN "level_id" TYPE FLOAT;'
});
......@@ -63,6 +64,7 @@ if (current.dialect.name !== 'sqlite') {
}).then(sql => {
expectsql(sql, {
mssql: 'ALTER TABLE [users] ADD FOREIGN KEY ([level_id]) REFERENCES [level] ([id]) ON DELETE CASCADE;',
mariadb: 'ALTER TABLE `users` ADD FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;',
mysql: 'ALTER TABLE `users` ADD FOREIGN KEY (`level_id`) REFERENCES `level` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;',
postgres: 'ALTER TABLE "users" ADD FOREIGN KEY ("level_id") REFERENCES "level" ("id") ON DELETE CASCADE ON UPDATE CASCADE;'
});
......
......@@ -21,6 +21,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.createTableQuery(FooUser.getTableName(), sql.attributesToSQL(FooUser.rawAttributes), { }), {
sqlite: 'CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `mood` TEXT);',
postgres: 'CREATE TABLE IF NOT EXISTS "foo"."users" ("id" SERIAL , "mood" "foo"."enum_users_mood", PRIMARY KEY ("id"));',
mariadb: "CREATE TABLE IF NOT EXISTS `foo`.`users` (`id` INTEGER NOT NULL auto_increment , `mood` ENUM('happy', 'sad'), PRIMARY KEY (`id`)) ENGINE=InnoDB;",
mysql: "CREATE TABLE IF NOT EXISTS `foo.users` (`id` INTEGER NOT NULL auto_increment , `mood` ENUM('happy', 'sad'), PRIMARY KEY (`id`)) ENGINE=InnoDB;",
mssql: "IF OBJECT_ID('[foo].[users]', 'U') IS NULL CREATE TABLE [foo].[users] ([id] INTEGER NOT NULL IDENTITY(1,1) , [mood] VARCHAR(255) CHECK ([mood] IN(N'happy', N'sad')), PRIMARY KEY ([id]));"
});
......@@ -49,6 +50,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.createTableQuery(BarProject.getTableName(), sql.attributesToSQL(BarProject.rawAttributes), { }), {
sqlite: 'CREATE TABLE IF NOT EXISTS `bar.projects` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `user_id` INTEGER REFERENCES `bar.users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE);',
postgres: 'CREATE TABLE IF NOT EXISTS "bar"."projects" ("id" SERIAL , "user_id" INTEGER REFERENCES "bar"."users" ("id") ON DELETE NO ACTION ON UPDATE CASCADE, PRIMARY KEY ("id"));',
mariadb: 'CREATE TABLE IF NOT EXISTS `bar`.`projects` (`id` INTEGER NOT NULL auto_increment , `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `bar`.`users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;',
mysql: 'CREATE TABLE IF NOT EXISTS `bar.projects` (`id` INTEGER NOT NULL auto_increment , `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `bar.users` (`id`) ON DELETE NO ACTION ON UPDATE CASCADE) ENGINE=InnoDB;',
mssql: 'IF OBJECT_ID(\'[bar].[projects]\', \'U\') IS NULL CREATE TABLE [bar].[projects] ([id] INTEGER NOT NULL IDENTITY(1,1) , [user_id] INTEGER NULL, PRIMARY KEY ([id]), FOREIGN KEY ([user_id]) REFERENCES [bar].[users] ([id]) ON DELETE NO ACTION);'
});
......@@ -75,6 +77,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.createTableQuery(Image.getTableName(), sql.attributesToSQL(Image.rawAttributes), { }), {
sqlite: 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER PRIMARY KEY AUTOINCREMENT REFERENCES `files` (`id`));',
postgres: 'CREATE TABLE IF NOT EXISTS "images" ("id" SERIAL REFERENCES "files" ("id"), PRIMARY KEY ("id"));',
mariadb: 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER auto_increment , PRIMARY KEY (`id`), FOREIGN KEY (`id`) REFERENCES `files` (`id`)) ENGINE=InnoDB;',
mysql: 'CREATE TABLE IF NOT EXISTS `images` (`id` INTEGER auto_increment , PRIMARY KEY (`id`), FOREIGN KEY (`id`) REFERENCES `files` (`id`)) ENGINE=InnoDB;',
mssql: 'IF OBJECT_ID(\'[images]\', \'U\') IS NULL CREATE TABLE [images] ([id] INTEGER IDENTITY(1,1) , PRIMARY KEY ([id]), FOREIGN KEY ([id]) REFERENCES [files] ([id]));'
});
......
......@@ -72,24 +72,28 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('TEXT("tiny")', DataTypes.TEXT('tiny'), {
default: 'TEXT',
mssql: 'NVARCHAR(256)',
mariadb: 'TINYTEXT',
mysql: 'TINYTEXT'
});
testsql('TEXT({ length: "tiny" })', DataTypes.TEXT({ length: 'tiny' }), {
default: 'TEXT',
mssql: 'NVARCHAR(256)',
mariadb: 'TINYTEXT',
mysql: 'TINYTEXT'
});
testsql('TEXT("medium")', DataTypes.TEXT('medium'), {
default: 'TEXT',
mssql: 'NVARCHAR(MAX)',
mariadb: 'MEDIUMTEXT',
mysql: 'MEDIUMTEXT'
});
testsql('TEXT("long")', DataTypes.TEXT('long'), {
default: 'TEXT',
mssql: 'NVARCHAR(MAX)',
mariadb: 'LONGTEXT',
mysql: 'LONGTEXT'
});
......@@ -140,6 +144,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('BOOLEAN', DataTypes.BOOLEAN, {
postgres: 'BOOLEAN',
mssql: 'BIT',
mariadb: 'TINYINT(1)',
mysql: 'TINYINT(1)',
sqlite: 'TINYINT(1)'
});
......@@ -170,6 +175,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('DATE', DataTypes.DATE, {
postgres: 'TIMESTAMP WITH TIME ZONE',
mssql: 'DATETIMEOFFSET',
mariadb: 'DATETIME',
mysql: 'DATETIME',
sqlite: 'DATETIME'
});
......@@ -177,6 +183,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('DATE(6)', DataTypes.DATE(6), {
postgres: 'TIMESTAMP WITH TIME ZONE',
mssql: 'DATETIMEOFFSET',
mariadb: 'DATETIME(6)',
mysql: 'DATETIME(6)',
sqlite: 'DATETIME'
});
......@@ -222,6 +229,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('UUID', DataTypes.UUID, {
postgres: 'UUID',
mssql: 'CHAR(36)',
mariadb: 'CHAR(36) BINARY',
mysql: 'CHAR(36) BINARY',
sqlite: 'UUID'
});
......@@ -1187,16 +1195,19 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
});
testsql('DECIMAL.UNSIGNED', DataTypes.DECIMAL.UNSIGNED, {
mariadb: 'DECIMAL UNSIGNED',
mysql: 'DECIMAL UNSIGNED',
default: 'DECIMAL'
});
testsql('DECIMAL.UNSIGNED.ZEROFILL', DataTypes.DECIMAL.UNSIGNED.ZEROFILL, {
mariadb: 'DECIMAL UNSIGNED ZEROFILL',
mysql: 'DECIMAL UNSIGNED ZEROFILL',
default: 'DECIMAL'
});
testsql('DECIMAL({ precision: 10, scale: 2 }).UNSIGNED', DataTypes.DECIMAL({ precision: 10, scale: 2 }).UNSIGNED, {
mariadb: 'DECIMAL(10,2) UNSIGNED',
mysql: 'DECIMAL(10,2) UNSIGNED',
default: 'DECIMAL(10,2)'
});
......@@ -1430,21 +1441,25 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('GEOMETRY(\'POINT\')', DataTypes.GEOMETRY('POINT'), {
postgres: 'GEOMETRY(POINT)',
mariadb: 'POINT',
mysql: 'POINT'
});
testsql('GEOMETRY(\'LINESTRING\')', DataTypes.GEOMETRY('LINESTRING'), {
postgres: 'GEOMETRY(LINESTRING)',
mariadb: 'LINESTRING',
mysql: 'LINESTRING'
});
testsql('GEOMETRY(\'POLYGON\')', DataTypes.GEOMETRY('POLYGON'), {
postgres: 'GEOMETRY(POLYGON)',
mariadb: 'POLYGON',
mysql: 'POLYGON'
});
testsql('GEOMETRY(\'POINT\',4326)', DataTypes.GEOMETRY('POINT', 4326), {
postgres: 'GEOMETRY(POINT,4326)',
mariadb: 'POINT',
mysql: 'POINT'
});
});
......
......@@ -36,6 +36,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
), {
postgres: 'TRUNCATE "public"."test_users" CASCADE',
mssql: 'TRUNCATE TABLE [public].[test_users]',
mariadb: 'TRUNCATE `public`.`test_users`',
mysql: 'TRUNCATE `public.test_users`',
sqlite: 'DELETE FROM `public.test_users`'
}
......@@ -62,6 +63,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
), {
postgres: 'TRUNCATE "public"."test_users" RESTART IDENTITY CASCADE',
mssql: 'TRUNCATE TABLE [public].[test_users]',
mariadb: 'TRUNCATE `public`.`test_users`',
mysql: 'TRUNCATE `public.test_users`',
sqlite: 'DELETE FROM `public.test_users`'
}
......@@ -87,6 +89,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
), {
default: "DELETE FROM [public.test_users] WHERE `name` = 'foo'",
postgres: 'DELETE FROM "public"."test_users" WHERE "name" = \'foo\'',
mariadb: 'DELETE FROM `public`.`test_users` WHERE `name` = \'foo\'',
sqlite: "DELETE FROM `public.test_users` WHERE `name` = 'foo'",
mssql: "DELETE FROM [public].[test_users] WHERE [name] = N'foo'; SELECT @@ROWCOUNT AS AFFECTEDROWS;"
}
......@@ -111,6 +114,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
User
), {
postgres: 'DELETE FROM "public"."test_users" WHERE "id" IN (SELECT "id" FROM "public"."test_users" WHERE "name" = \'foo\'\';DROP TABLE mySchema.myTable;\' LIMIT 10)',
mariadb: "DELETE FROM `public`.`test_users` WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10",
sqlite: "DELETE FROM `public.test_users` WHERE rowid IN (SELECT rowid FROM `public.test_users` WHERE `name` = \'foo\'\';DROP TABLE mySchema.myTable;\' LIMIT 10)",
mssql: "DELETE TOP(10) FROM [public].[test_users] WHERE [name] = N'foo'';DROP TABLE mySchema.myTable;'; SELECT @@ROWCOUNT AS AFFECTEDROWS;",
default: "DELETE FROM [public.test_users] WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10"
......@@ -143,6 +147,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
return expectsql(
query, {
postgres: new Error('Cannot LIMIT delete without a model.'),
mariadb: "DELETE FROM `public`.`test_users` WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10",
sqlite: "DELETE FROM `public.test_users` WHERE rowid IN (SELECT rowid FROM `public.test_users` WHERE `name` = 'foo'';DROP TABLE mySchema.myTable;' LIMIT 10)",
mssql: "DELETE TOP(10) FROM [public].[test_users] WHERE [name] = N'foo'';DROP TABLE mySchema.myTable;'; SELECT @@ROWCOUNT AS AFFECTEDROWS;",
default: "DELETE FROM [public.test_users] WHERE `name` = 'foo\\';DROP TABLE mySchema.myTable;' LIMIT 10"
......
......@@ -13,26 +13,30 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
it('naming', () => {
expectsql(sql.addIndexQuery('table', ['column1', 'column2'], {}, 'table'), {
default: 'CREATE INDEX [table_column1_column2] ON [table] ([column1], [column2])',
mariadb: 'ALTER TABLE `table` ADD INDEX `table_column1_column2` (`column1`, `column2`)',
mysql: 'ALTER TABLE `table` ADD INDEX `table_column1_column2` (`column1`, `column2`)'
});
if (current.dialect.supports.schemas) {
expectsql(sql.addIndexQuery('schema.table', ['column1', 'column2'], {}), {
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])'
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])',
mariadb: 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)'
});
expectsql(sql.addIndexQuery({
schema: 'schema',
tableName: 'table'
}, ['column1', 'column2'], {}, 'schema_table'), {
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])'
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])',
mariadb: 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)'
});
expectsql(sql.addIndexQuery(sql.quoteTable(sql.addSchema({
_schema: 'schema',
tableName: 'table'
})), ['column1', 'column2'], {}), {
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])'
default: 'CREATE INDEX [schema_table_column1_column2] ON [schema].[table] ([column1], [column2])',
mariadb: 'ALTER TABLE `schema`.`table` ADD INDEX `schema_table_column1_column2` (`column1`, `column2`)'
});
}
});
......@@ -45,6 +49,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
sqlite: 'CREATE INDEX `user_field_c` ON `User` (`fieldC`)',
mssql: 'CREATE FULLTEXT INDEX [user_field_c] ON [User] ([fieldC])',
postgres: 'CREATE INDEX CONCURRENTLY "user_field_c" ON "User" ("fieldC")',
mariadb: 'ALTER TABLE `User` ADD FULLTEXT INDEX `user_field_c` (`fieldC`)',
mysql: 'ALTER TABLE `User` ADD FULLTEXT INDEX `user_field_c` (`fieldC`)'
});
......@@ -57,6 +62,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
sqlite: 'CREATE UNIQUE INDEX `a_b_uniq` ON `User` (`fieldB`, `fieldA` COLLATE `en_US` DESC)',
mssql: 'CREATE UNIQUE INDEX [a_b_uniq] ON [User] ([fieldB], [fieldA] DESC)',
postgres: 'CREATE UNIQUE INDEX "a_b_uniq" ON "User" USING BTREE ("fieldB", "fieldA" COLLATE "en_US" DESC)',
mariadb: 'ALTER TABLE `User` ADD UNIQUE INDEX `a_b_uniq` USING BTREE (`fieldB`, `fieldA`(5) DESC) WITH PARSER foo',
mysql: 'ALTER TABLE `User` ADD UNIQUE INDEX `a_b_uniq` USING BTREE (`fieldB`, `fieldA`(5) DESC) WITH PARSER foo'
});
});
......@@ -65,6 +71,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.addIndexQuery('table', [{ attribute: 'column', collate: 'BINARY', length: 5, order: 'DESC'}], {}, 'table'), {
default: 'CREATE INDEX [table_column] ON [table] ([column] COLLATE [BINARY] DESC)',
mssql: 'CREATE INDEX [table_column] ON [table] ([column] DESC)',
mariadb: 'ALTER TABLE `table` ADD INDEX `table_column` (`column`(5) DESC)',
mysql: 'ALTER TABLE `table` ADD INDEX `table_column` (`column`(5) DESC)'
});
});
......@@ -72,6 +79,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
it('function', () => {
expectsql(sql.addIndexQuery('table', [current.fn('UPPER', current.col('test'))], { name: 'myindex'}), {
default: 'CREATE INDEX [myindex] ON [table] (UPPER([test]))',
mariadb: 'ALTER TABLE `table` ADD INDEX `myindex` (UPPER(`test`))',
mysql: 'ALTER TABLE `table` ADD INDEX `myindex` (UPPER(`test`))'
});
});
......@@ -168,6 +176,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
describe('removeIndex', () => {
it('naming', () => {
expectsql(sql.removeIndexQuery('table', ['column1', 'column2'], {}, 'table'), {
mariadb: 'DROP INDEX `table_column1_column2` ON `table`',
mysql: 'DROP INDEX `table_column1_column2` ON `table`',
mssql: 'DROP INDEX [table_column1_column2] ON [table]',
default: 'DROP INDEX IF EXISTS [table_column1_column2]'
......
......@@ -62,6 +62,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
bind: {
sqlite: ['2015-01-20 00:00:00.000 +00:00'],
mysql: ['2015-01-20 01:00:00'],
mariadb: ['2015-01-20 01:00:00.000'],
default: ['2015-01-20 01:00:00.000 +01:00']
}
});
......@@ -89,6 +90,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
},
bind: {
sqlite: ['2015-01-20 01:02:03.089 +00:00'],
mariadb: ['2015-01-20 02:02:03.089'],
mysql: ['2015-01-20 02:02:03.089'],
default: ['2015-01-20 02:02:03.089 +01:00']
}
......@@ -124,6 +126,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
default: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\');',
postgres: 'INSERT INTO "users" ("user_name","pass_word") VALUES (\'testuser\',\'12345\');',
mssql: 'INSERT INTO [users] ([user_name],[pass_word]) VALUES (N\'testuser\',N\'12345\');',
mariadb: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\') ON DUPLICATE KEY UPDATE `user_name`=VALUES(`user_name`),`pass_word`=VALUES(`pass_word`),`updated_at`=VALUES(`updated_at`);',
mysql: 'INSERT INTO `users` (`user_name`,`pass_word`) VALUES (\'testuser\',\'12345\') ON DUPLICATE KEY UPDATE `user_name`=VALUES(`user_name`),`pass_word`=VALUES(`pass_word`),`updated_at`=VALUES(`updated_at`);'
});
});
......
......@@ -16,6 +16,7 @@ if (current.dialect.supports.JSON) {
it('plain string', () => {
expectsql(sql.escape('string', { type: new DataTypes.JSON() }), {
default: '\'"string"\'',
mariadb: '\'\\"string\\"\'',
mysql: '\'\\"string\\"\''
});
});
......@@ -47,6 +48,7 @@ if (current.dialect.supports.JSON) {
it('nested object', () => {
expectsql(sql.escape({ some: 'nested', more: { nested: true }, answer: 42 }, { type: new DataTypes.JSON() }), {
default: '\'{"some":"nested","more":{"nested":true},"answer":42}\'',
mariadb: '\'{\\"some\\":\\"nested\\",\\"more\\":{\\"nested\\":true},\\"answer\\":42}\'',
mysql: '\'{\\"some\\":\\"nested\\",\\"more\\":{\\"nested\\":true},\\"answer\\":42}\''
});
});
......@@ -81,6 +83,7 @@ if (current.dialect.supports.JSON) {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ id: 1 })), {
postgres: '("id"#>>\'{}\') = \'1\'',
sqlite: "json_extract(`id`, '$') = '1'",
mariadb: "json_unquote(json_extract(`id`,'$.')) = '1'",
mysql: "`id`->>'$.' = '1'"
});
});
......@@ -89,6 +92,7 @@ if (current.dialect.supports.JSON) {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ profile: { id: 1 } })), {
postgres: '("profile"#>>\'{id}\') = \'1\'',
sqlite: "json_extract(`profile`, '$.id') = '1'",
mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '1'",
mysql: "`profile`->>'$.id' = '1'"
});
});
......@@ -97,22 +101,43 @@ if (current.dialect.supports.JSON) {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: { value: 1 }, another: { value: 'string' } })), {
postgres: '("property"#>>\'{value}\') = \'1\' AND ("another"#>>\'{value}\') = \'string\'',
sqlite: "json_extract(`property`, '$.value') = '1' AND json_extract(`another`, '$.value') = 'string'",
mariadb: "json_unquote(json_extract(`property`,'$.value')) = '1' and json_unquote(json_extract(`another`,'$.value')) = 'string'",
mysql: "`property`->>'$.value' = '1' and `another`->>'$.value' = 'string'"
});
});
it('property array object', () => {
expectsql(sql.whereItemQuery(undefined, Sequelize.json({ property: [[4, 6], [8]]})), {
postgres: '("property"#>>\'{0,0}\') = \'4\' AND ("property"#>>\'{0,1}\') = \'6\' AND ("property"#>>\'{1,0}\') = \'8\'',
sqlite: "json_extract(`property`, '$[0][0]') = '4' AND json_extract(`property`, '$[0][1]') = '6' AND json_extract(`property`, '$[1][0]') = '8'",
mariadb: "json_unquote(json_extract(`property`,'$.0.0')) = '4' and json_unquote(json_extract(`property`,'$.0.1')) = '6' and json_unquote(json_extract(`property`,'$.1.0')) = '8'",
mysql: "`property`->>'$.0.0' = '4' and `property`->>'$.0.1' = '6' and `property`->>'$.1.0' = '8'"
});
});
it('dot notation', () => {
expectsql(sql.whereItemQuery(Sequelize.json('profile.id'), '1'), {
postgres: '("profile"#>>\'{id}\') = \'1\'',
sqlite: "json_extract(`profile`, '$.id') = '1'",
mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '1'",
mysql: "`profile`->>'$.id' = '1'"
});
});
it('item dot notation array', () => {
expectsql(sql.whereItemQuery(Sequelize.json('profile.id.0.1'), '1'), {
postgres: '("profile"#>>\'{id,0,1}\') = \'1\'',
sqlite: "json_extract(`profile`, '$.id[0][1]') = '1'",
mariadb: "json_unquote(json_extract(`profile`,'$.id[0][1]')) = '1'",
mysql: "`profile`->>'$.id[0][1]' = '1'"
});
});
it('column named "json"', () => {
expectsql(sql.whereItemQuery(Sequelize.json('json'), '{}'), {
postgres: '("json"#>>\'{}\') = \'{}\'',
sqlite: "json_extract(`json`, '$') = '{}'",
mariadb: "json_unquote(json_extract(`json`,'$.')) = '{}'",
mysql: "`json`->>'$.' = '{}'"
});
});
......
......@@ -61,6 +61,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
]
}, {
default: " LIMIT ''';DELETE FROM user'",
mariadb: " LIMIT '\\';DELETE FROM user'",
mysql: " LIMIT '\\';DELETE FROM user'",
mssql: " OFFSET 0 ROWS FETCH NEXT N''';DELETE FROM user' ROWS ONLY"
});
......@@ -74,6 +75,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}, {
sqlite: " LIMIT ''';DELETE FROM user', 10",
postgres: " LIMIT 10 OFFSET ''';DELETE FROM user'",
mariadb: " LIMIT '\\';DELETE FROM user', 10",
mysql: " LIMIT '\\';DELETE FROM user', 10",
mssql: " OFFSET N''';DELETE FROM user' ROWS FETCH NEXT 10 ROWS ONLY"
});
......
......@@ -352,6 +352,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
]
}, {
mssql: 'SELECT [id], [name] FROM [subtask] AS [Subtask] ORDER BY RAND();',
mariadb: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();',
mysql: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RAND();',
postgres: 'SELECT "id", "name" FROM "subtask" AS "Subtask" ORDER BY RANDOM();',
sqlite: 'SELECT `id`, `name` FROM `subtask` AS `Subtask` ORDER BY RANDOM();'
......
......@@ -16,6 +16,7 @@ if (current.dialect.name !== 'sqlite') {
tableName: 'user'
}, 'email'), {
mssql: 'ALTER TABLE [archive].[user] DROP COLUMN [email];',
mariadb: 'ALTER TABLE `archive`.`user` DROP `email`;',
mysql: 'ALTER TABLE `archive.user` DROP `email`;',
postgres: 'ALTER TABLE "archive"."user" DROP COLUMN "email";'
});
......
......@@ -433,6 +433,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}
}, User), {
postgres: 'SELECT "name", "age", "data" FROM "User" AS "User" WHERE "User"."data" IN (E\'\\\\x313233\');',
mariadb: 'SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X\'313233\');',
mysql: 'SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X\'313233\');',
sqlite: 'SELECT `name`, `age`, `data` FROM `User` AS `User` WHERE `User`.`data` IN (X\'313233\');',
mssql: 'SELECT [name], [age], [data] FROM [User] AS [User] WHERE [User].[data] IN (0x313233);'
......
......@@ -11,6 +11,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.showConstraintsQuery('myTable'), {
mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';",
postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';',
mariadb: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable';",
mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable';",
default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable';"
});
......@@ -20,6 +21,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.showConstraintsQuery('myTable', 'myConstraintName'), {
mssql: "EXEC sp_helpconstraint @objname = N'[myTable]';",
postgres: 'SELECT constraint_catalog AS "constraintCatalog", constraint_schema AS "constraintSchema", constraint_name AS "constraintName", table_catalog AS "tableCatalog", table_schema AS "tableSchema", table_name AS "tableName", constraint_type AS "constraintType", is_deferrable AS "isDeferrable", initially_deferred AS "initiallyDeferred" from INFORMATION_SCHEMA.table_constraints WHERE table_name=\'myTable\';',
mariadb: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable' AND constraint_name = 'myConstraintName';",
mysql: "SELECT CONSTRAINT_CATALOG AS constraintCatalog, CONSTRAINT_NAME AS constraintName, CONSTRAINT_SCHEMA AS constraintSchema, CONSTRAINT_TYPE AS constraintType, TABLE_NAME AS tableName, TABLE_SCHEMA AS tableSchema from INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='myTable' AND constraint_name = 'myConstraintName';",
default: "SELECT sql FROM sqlite_master WHERE tbl_name='myTable' AND sql LIKE '%myConstraintName%';"
});
......
......@@ -54,6 +54,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.updateQuery(User.tableName, { username: 'new.username' }, { username: 'username' }, { limit: 1 }), {
query: {
mssql: 'UPDATE TOP(1) [Users] SET [username]=$1 OUTPUT INSERTED.* WHERE [username] = $2',
mariadb: 'UPDATE `Users` SET `username`=$1 WHERE `username` = $2 LIMIT 1',
mysql: 'UPDATE `Users` SET `username`=$1 WHERE `username` = $2 LIMIT 1',
sqlite: 'UPDATE `Users` SET `username`=$1 WHERE rowid IN (SELECT rowid FROM `Users` WHERE `username` = $2 LIMIT 1)',
default: 'UPDATE [Users] SET [username]=$1 WHERE [username] = $2'
......
......@@ -58,6 +58,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.whereQuery({id: 1}, {prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {schema: 'yolo', tableName: 'User'}))}), {
default: 'WHERE [yolo.User].[id] = 1',
postgres: 'WHERE "yolo"."User"."id" = 1',
mariadb: 'WHERE `yolo`.`User`.`id` = 1',
mssql: 'WHERE [yolo].[User].[id] = 1'
});
});
......@@ -149,6 +150,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('field', Buffer.from('Sequelize'), {
postgres: '"field" = E\'\\\\x53657175656c697a65\'',
sqlite: "`field` = X'53657175656c697a65'",
mariadb: "`field` = X'53657175656c697a65'",
mysql: "`field` = X'53657175656c697a65'",
mssql: '[field] = 0x53657175656c697a65'
});
......@@ -800,6 +802,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.whereItemQuery(undefined, this.sequelize.json('profile.id', this.sequelize.cast('12346-78912', 'text'))), {
postgres: "(\"profile\"#>>'{id}') = CAST('12346-78912' AS TEXT)",
sqlite: "json_extract(`profile`, '$.id') = CAST('12346-78912' AS TEXT)",
mariadb: "json_unquote(json_extract(`profile`,'$.id')) = CAST('12346-78912' AS CHAR)",
mysql: "`profile`->>'$.id' = CAST('12346-78912' AS CHAR)"
});
});
......@@ -808,6 +811,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
expectsql(sql.whereItemQuery(undefined, this.sequelize.json({profile: {id: '12346-78912', name: 'test'}})), {
postgres: "(\"profile\"#>>'{id}') = '12346-78912' AND (\"profile\"#>>'{name}') = 'test'",
sqlite: "json_extract(`profile`, '$.id') = '12346-78912' AND json_extract(`profile`, '$.name') = 'test'",
mariadb: "json_unquote(json_extract(`profile`,'$.id')) = '12346-78912' and json_unquote(json_extract(`profile`,'$.name')) = 'test'",
mysql: "`profile`->>'$.id' = '12346-78912' and `profile`->>'$.name' = 'test'"
});
});
......@@ -822,6 +826,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
},
prefix: 'User'
}, {
mariadb: "json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value'",
mysql: "(`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value'",
postgres: "(\"User\".\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`User`.`data`, '$.nested.attribute') = 'value'"
......@@ -836,6 +841,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) IN (1, 2)",
mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) IN (1, 2)",
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) IN (1, 2)",
sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) IN (1, 2)"
......@@ -850,6 +856,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested')) AS DECIMAL) BETWEEN 1 AND 2",
mysql: "CAST((`data`->>'$.\"nested\"') AS DECIMAL) BETWEEN 1 AND 2",
postgres: "CAST((\"data\"#>>'{nested}') AS DOUBLE PRECISION) BETWEEN 1 AND 2",
sqlite: "CAST(json_extract(`data`, '$.nested') AS DOUBLE PRECISION) BETWEEN 1 AND 2"
......@@ -868,6 +875,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
},
prefix: current.literal(sql.quoteTable.call(current.dialect.QueryGenerator, {tableName: 'User'}))
}, {
mariadb: "(json_unquote(json_extract(`User`.`data`,'$.nested.attribute')) = 'value' AND json_unquote(json_extract(`User`.`data`,'$.nested.prop')) != 'None')",
mysql: "((`User`.`data`->>'$.\"nested\".\"attribute\"') = 'value' AND (`User`.`data`->>'$.\"nested\".\"prop\"') != 'None')",
postgres: "((\"User\".\"data\"#>>'{nested,attribute}') = 'value' AND (\"User\".\"data\"#>>'{nested,prop}') != 'None')",
sqlite: "(json_extract(`User`.`data`, '$.nested.attribute') = 'value' AND json_extract(`User`.`data`, '$.nested.prop') != 'None')"
......@@ -886,6 +894,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
},
prefix: 'User'
}, {
mariadb: "(json_unquote(json_extract(`User`.`data`,'$.name.last')) = 'Simpson' AND json_unquote(json_extract(`User`.`data`,'$.employment')) != 'None')",
mysql: "((`User`.`data`->>'$.\"name\".\"last\"') = 'Simpson' AND (`User`.`data`->>'$.\"employment\"') != 'None')",
postgres: "((\"User\".\"data\"#>>'{name,last}') = 'Simpson' AND (\"User\".\"data\"#>>'{employment}') != 'None')",
sqlite: "(json_extract(`User`.`data`, '$.name.last') = 'Simpson' AND json_extract(`User`.`data`, '$.employment') != 'None')"
......@@ -899,6 +908,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: "(CAST(json_unquote(json_extract(`data`,'$.price')) AS DECIMAL) = 5 AND json_unquote(json_extract(`data`,'$.name')) = 'Product')",
mysql: "(CAST((`data`->>'$.\"price\"') AS DECIMAL) = 5 AND (`data`->>'$.\"name\"') = 'Product')",
postgres: "(CAST((\"data\"#>>'{price}') AS DOUBLE PRECISION) = 5 AND (\"data\"#>>'{name}') = 'Product')",
sqlite: "(CAST(json_extract(`data`, '$.price') AS DOUBLE PRECISION) = 5 AND json_extract(`data`, '$.name') = 'Product')"
......@@ -913,6 +923,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'value'",
mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'value'",
postgres: "(\"data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`data`, '$.nested.attribute') = 'value'"
......@@ -927,6 +938,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) = 4",
mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) = 4",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) = 4",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) = 4"
......@@ -943,6 +955,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) IN (3, 7)",
mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) IN (3, 7)",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) IN (3, 7)",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) IN (3, 7)"
......@@ -959,6 +972,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2",
mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS DOUBLE PRECISION) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS DOUBLE PRECISION) > 2"
......@@ -975,6 +989,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: "CAST(json_unquote(json_extract(`data`,'$.nested.attribute')) AS DECIMAL) > 2",
mysql: "CAST((`data`->>'$.\"nested\".\"attribute\"') AS DECIMAL) > 2",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS INTEGER) > 2",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS INTEGER) > 2"
......@@ -992,6 +1007,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: `CAST(json_unquote(json_extract(\`data\`,'$.nested.attribute')) AS DATETIME) > ${sql.escape(dt)}`,
mysql: `CAST((\`data\`->>'$."nested"."attribute"') AS DATETIME) > ${sql.escape(dt)}`,
postgres: `CAST(("data"#>>'{nested,attribute}') AS TIMESTAMPTZ) > ${sql.escape(dt)}`,
sqlite: `json_extract(\`data\`, '$.nested.attribute') > ${sql.escape(dt.toISOString())}`
......@@ -1006,6 +1022,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
type: new DataTypes.JSONB()
}
}, {
mariadb: "json_unquote(json_extract(`data`,'$.nested.attribute')) = 'true'",
mysql: "(`data`->>'$.\"nested\".\"attribute\"') = 'true'",
postgres: "CAST((\"data\"#>>'{nested,attribute}') AS BOOLEAN) = true",
sqlite: "CAST(json_extract(`data`, '$.nested.attribute') AS BOOLEAN) = 1"
......@@ -1022,6 +1039,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
}
}
}, {
mariadb: "json_unquote(json_extract(`meta_data`,'$.nested.attribute')) = 'value'",
mysql: "(`meta_data`->>'$.\"nested\".\"attribute\"') = 'value'",
postgres: "(\"meta_data\"#>>'{nested,attribute}') = 'value'",
sqlite: "json_extract(`meta_data`, '$.nested.attribute') = 'value'"
......@@ -1050,6 +1068,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('username', {
[Op.regexp]: '^sw.*r$'
}, {
mariadb: "`username` REGEXP '^sw.*r$'",
mysql: "`username` REGEXP '^sw.*r$'",
postgres: '"username" ~ \'^sw.*r$\''
});
......@@ -1059,6 +1078,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('newline', {
[Op.regexp]: '^new\nline$'
}, {
mariadb: "`newline` REGEXP '^new\\nline$'",
mysql: "`newline` REGEXP '^new\\nline$'",
postgres: '"newline" ~ \'^new\nline$\''
});
......@@ -1068,6 +1088,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('username', {
[Op.notRegexp]: '^sw.*r$'
}, {
mariadb: "`username` NOT REGEXP '^sw.*r$'",
mysql: "`username` NOT REGEXP '^sw.*r$'",
postgres: '"username" !~ \'^sw.*r$\''
});
......@@ -1077,6 +1098,7 @@ describe(Support.getTestDialectTeaser('SQL'), () => {
testsql('newline', {
[Op.notRegexp]: '^new\nline$'
}, {
mariadb: "`newline` NOT REGEXP '^new\\nline$'",
mysql: "`newline` NOT REGEXP '^new\\nline$'",
postgres: '"newline" !~ \'^new\nline$\''
});
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!