@mysql/xdevapi
Advanced tools
Comparing version 8.0.7 to 8.0.8
@@ -11,2 +11,15 @@ ===================================================== | ||
v8.0.8 | ||
====== | ||
- Add support for `collection.find()` and `table.select()` row locking | ||
- Add document-specific CRUD methods (`replaceOne()`, `addOrReplaceOne()`, `getOne()` and `removeOne()`) | ||
- Add support for automatic or manual selection of authentication mechanisms | ||
- Remove some dead or non-supported feature code | ||
- Fix some flaky test cases and other some small issues in the test suite | ||
- Refactor the database entity and operation architecture | ||
- Add support for the SQL "IN" operator for matching values against argument lists | ||
- Add support for using the "IN" operator to check if values exist in arrays or objects | ||
- Fix a few pressing bugs on the existing expression parser (empty quoted strings, `NULL`, negation, zeros) | ||
v8.0.7 | ||
@@ -13,0 +26,0 @@ ====== |
10
index.js
@@ -74,6 +74,10 @@ /* | ||
const base = parseUri(properties.getUri()); | ||
delete base.endpoints; | ||
const configuration = Object.assign({}, base, overrides, { | ||
endpoints: [{ | ||
host: overrides.host || base.endpoints[0].host, | ||
port: overrides.port || base.endpoints[0].port, | ||
socket: overrides.socket || base.endpoints[0].socket | ||
}] | ||
}); | ||
const configuration = Object.assign({}, base, overrides); | ||
return createSession(configuration); | ||
@@ -80,0 +84,0 @@ } |
@@ -39,3 +39,3 @@ /* | ||
this._user = properties.dbUser; | ||
this._password = properties.dbPassword; | ||
this._password = properties.dbPassword || ''; | ||
} | ||
@@ -42,0 +42,0 @@ |
@@ -29,143 +29,287 @@ /* | ||
const CollectionAdd = require('./CollectionAdd'); | ||
const CollectionFind = require('./CollectionFind'); | ||
const CollectionModify = require('./CollectionModify'); | ||
const CollectionRemove = require('./CollectionRemove'); | ||
const DatabaseObject = require('./DatabaseObject'); | ||
const LinkOperations = require('./LinkOperations'); | ||
const Table = require('./Table'); | ||
const collectionAdd = require('./CollectionAdd'); | ||
const collectionFind = require('./CollectionFind'); | ||
const collectionModify = require('./CollectionModify'); | ||
const collectionRemove = require('./CollectionRemove'); | ||
const databaseObject = require('./DatabaseObject'); | ||
const linking = require('./Linking'); | ||
const parseFlexibleParamList = require('./Util/parseFlexibleParamList'); | ||
const util = require('util'); | ||
const table = require('./Table'); | ||
const escapeQuotes = require('./Util/escapeQuotes'); | ||
/** | ||
* Collection object | ||
* | ||
* Usually you shouldn't create an instance of this but ask a Schema for it | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {String} collection | ||
* @param {String} alias | ||
* @param {object} links | ||
* @constructor | ||
* @extends DatabaseObject | ||
* Collection factory. | ||
* @module Collection | ||
* @mixes DatabaseObject | ||
* @mixes Linking | ||
*/ | ||
function Collection (session, schema, collection, alias, links) { | ||
DatabaseObject.call(this, session, schema); | ||
this._collection = collection; | ||
this._links = links || {}; | ||
this._alias = alias; | ||
} | ||
/** | ||
* @private | ||
* @alias module:Collection | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} name - collection name | ||
* @param {string} alias - collection alias | ||
* @param {Object} links - collection join links | ||
* @returns {Collection} | ||
*/ | ||
function Collection (session, schema, name, alias, links) { | ||
const state = Object.assign({}, { links: {} }, { alias, links, name, schema }); | ||
module.exports = Collection; | ||
return Object.assign({}, databaseObject(session), linking(), { | ||
/** | ||
* Literal object or JSON counterpart. | ||
* @typedef {Object|string} DocumentOrJSON | ||
* @global | ||
* @example | ||
* // literal object | ||
* { foo: 'bar' } | ||
* // JSON string | ||
* '{ "foo": "bar" }' | ||
*/ | ||
util.inherits(Collection, DatabaseObject); | ||
LinkOperations.applyTo(Collection); | ||
/** | ||
* Create an operation to add one or more documents to the collection. | ||
* @function | ||
* @name module:Collection#add | ||
* @param {...DocumentOrJSON|DocumentOrJSON[]} expr - object with document data | ||
* @throws {Error} When the input type is invalid. | ||
* @example | ||
* // arguments as single documents | ||
* collection.add({ foo: 'baz' }, { bar: 'qux' }) | ||
* | ||
* // array of documents | ||
* collection.add([{ foo: 'baz' }, { bar: 'qux' }]) | ||
* @returns {CollectionAdd} The operation instance. | ||
*/ | ||
add () { | ||
const documents = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
/** | ||
* Get the name of this collection | ||
* @returns {string} | ||
*/ | ||
Collection.prototype.getName = function () { | ||
return this._collection; | ||
}; | ||
return collectionAdd(this.getSession(), this.getSchema(), this.getName(), documents); | ||
}, | ||
/** | ||
* Verifies this collection exists | ||
* @returns {Promise<boolean>} | ||
*/ | ||
Collection.prototype.existsInDatabase = function () { | ||
const args = [this._schema.getName(), this._collection]; | ||
let status = false; | ||
/** | ||
* Create or replace a document with the given id. | ||
* @function | ||
* @name module:Collection#addOrReplaceOne | ||
* @param {string} id - document id | ||
* @param {Object} data - document properties | ||
* @example | ||
* collection.addOrReplaceOne('foo', { prop1: 'bar', prop2: 'baz' }) | ||
* @returns {Promise.<Result>} A promise that resolves to the operation result. | ||
*/ | ||
addOrReplaceOne (id, data) { | ||
const doc = Object.assign({}, data, { _id: id }); | ||
return this._session._client | ||
.sqlStmtExecute('list_objects', args, (found) => { status = !!found.length; }, null, 'xplugin') | ||
.then(() => status); | ||
}; | ||
return collectionAdd(this.getSession(), this.getSchema(), this.getName(), [doc], { upsert: true }).execute(); | ||
}, | ||
/** | ||
* Find documents from a collection | ||
* @param {String} expr Expression | ||
* @returns {CollectionFind} | ||
*/ | ||
Collection.prototype.find = function (expr) { | ||
return new CollectionFind(this._session, this._schema, this._collection, expr); | ||
}; | ||
/** | ||
* Set alias for a linking operation using the collection. | ||
* @function | ||
* @name module:Collection#as | ||
* @param {string} alias - new collection alias | ||
* @returns {Collection} The entity instance. | ||
*/ | ||
as (alias) { | ||
return Collection(this.getSession(), this.getSchema(), this.getName(), alias, this.getLinks()); | ||
}, | ||
/** | ||
* Add documents | ||
* @param {...Object|Object[]} document - object with document data | ||
* @throws {Error} When the input type is invalid. | ||
* @example | ||
* // arguments as single documents | ||
* collection.add({ foo: 'baz' }, { bar: 'qux' }) | ||
* | ||
* // array of documents | ||
* collection.add([{ foo: 'baz' }, { bar: 'qux' }]) | ||
* @returns {CollectionAdd} | ||
*/ | ||
Collection.prototype.add = function () { | ||
const documents = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
/** | ||
* Retrieve the total number of documents in the collection. | ||
* @function | ||
* @name module:Table#count | ||
* @returns {Promise.<number>} | ||
*/ | ||
// TODO(Rui): extract method into a proper aspect (to be used on Collection and Table). | ||
count () { | ||
const schema = table.escapeIdentifier(this.getSchema().getName()); | ||
const collection = table.escapeIdentifier(this.getName()); | ||
return new CollectionAdd(this._session, this._schema, this._collection, documents); | ||
}; | ||
let count = 0; | ||
/** | ||
* Run modify operations | ||
* @param {string} expr Expression | ||
* @example | ||
* // update all documents in a collection | ||
* collection.modify('true').set('name', 'bar') | ||
* | ||
* // update documents that match a given condition | ||
* collection.modify('$.name == "foo"').set('name', 'bar') | ||
* @returns {CollectionModify} | ||
*/ | ||
Collection.prototype.modify = function (expr) { | ||
return new CollectionModify(this._session, this._schema, this._collection, expr); | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute(`SELECT COUNT(*) FROM ${schema}.${collection}`, [], row => { count = row[0]; }) | ||
.then(() => count); | ||
}, | ||
/** | ||
* Create an operation to remove documents from a collection. | ||
* @param {string} expr Expression | ||
* @example | ||
* // remove all documents from a collection | ||
* collection.remove('true') | ||
* | ||
* // remove documents that match a given condition | ||
* collection.remove('$.name == "foobar"') | ||
* @returns {CollectionRemove} | ||
*/ | ||
Collection.prototype.remove = function (expr) { | ||
return new CollectionRemove(this._session, this._schema, this._collection, expr); | ||
}; | ||
/** | ||
* Check if this collection exists in the database. | ||
* @function | ||
* @name module:Collection#existsInDatabase | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
// TODO(Rui): extract method into a proper aspect (to be used on Collection, Schema and Table). | ||
existsInDatabase () { | ||
const args = [this.getSchema().getName(), this.getName()]; | ||
let status = false; | ||
/** | ||
* Get number of documents in this Collection | ||
* | ||
* @returns {Promise.<Number>} | ||
*/ | ||
Collection.prototype.count = function () { | ||
const schema = Table.escapeIdentifier(this._schema.getName()); | ||
const collection = Table.escapeIdentifier(this._collection); | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute('list_objects', args, (found) => { status = !!found.length; }, null, 'xplugin') | ||
.then(() => status); | ||
}, | ||
let count = 0; | ||
/** | ||
* Expression that establishes the filtering criteria. | ||
* @typedef {string} SearchConditionStr | ||
* @global | ||
* @see {@link https://dev.mysql.com/doc/x-devapi-userguide/en/crud-ebnf-other-definitions.html|X DevAPI User Guide} | ||
*/ | ||
return this._session._client | ||
.sqlStmtExecute(`SELECT COUNT(*) FROM ${schema}.${collection}`, [], row => { count = row[0]; }) | ||
.then(() => count); | ||
}; | ||
/** | ||
* Create an operation to find documents in the collection. | ||
* @function | ||
* @name module:Collection#find | ||
* @param {SearchConditionStr} expr - filtering criteria | ||
* @returns {CollectionFind} The operation instance. | ||
*/ | ||
find (expr) { | ||
return collectionFind(this.getSession(), this.getSchema(), this.getName(), expr); | ||
}, | ||
/** | ||
* Set alias for link operation | ||
* @param {String} alias | ||
* @returns {Collection} | ||
*/ | ||
Collection.prototype.as = function (alias) { | ||
return new Collection(this._session, this._schema, this._collection, alias, this._links); | ||
}; | ||
/** | ||
* Retrieve the collection name. | ||
* @function | ||
* @name module:Collection#getName | ||
* @returns {string} | ||
*/ | ||
getName () { | ||
return state.name; | ||
}, | ||
Collection.prototype.inspect = function () { | ||
return { schema: this._schema.getName(), collection: this._collection }; | ||
}; | ||
/** | ||
* Retrieve a single document with the given id. | ||
* @function | ||
* @name module:Collection#getOne | ||
* @param {string} id - document id | ||
* @example | ||
* collection.getOne('1') | ||
* @returns {Object} The document instance. | ||
*/ | ||
getOne (id) { | ||
let instance; | ||
return this | ||
.find(`$._id == "${escapeQuotes(id)}"`) | ||
.execute(doc => { | ||
instance = doc || null; | ||
}) | ||
.then(() => instance); | ||
}, | ||
/** | ||
* Retrieve the schema associated to the collection. | ||
* @function | ||
* @name module:Collection#getSchema | ||
* @returns {Schema} | ||
*/ | ||
getSchema () { | ||
return state.schema; | ||
}, | ||
/** | ||
* Retrieve the collection metadata. | ||
* @function | ||
* @name module:Collection#inspect | ||
* @returns {Object} An object containing the relevant metadata. | ||
*/ | ||
inspect () { | ||
return { schema: this.getSchema().getName(), collection: this.getName() }; | ||
}, | ||
/** | ||
* Create an operation to modify documents in the collection. | ||
* @function | ||
* @name module:Collection#modify | ||
* @param {SearchConditionStr} expr - filtering criteria | ||
* @example | ||
* // update all documents in a collection | ||
* collection.modify('true').set('name', 'bar') | ||
* | ||
* // update documents that match a given condition | ||
* collection.modify('$.name == "foo"').set('name', 'bar') | ||
* @returns {CollectionModify} The operation instance. | ||
*/ | ||
modify (expr) { | ||
return collectionModify(this.getSession(), this.getSchema(), this.getName(), expr); | ||
}, | ||
/** | ||
* Create an operation to remove documents from the collection. | ||
* @function | ||
* @name module:Collection#remove | ||
* @param {SearchConditionStr} expr - filtering criteria | ||
* @example | ||
* // remove all documents from a collection | ||
* collection.remove('true') | ||
* | ||
* // remove documents that match a given condition | ||
* collection.remove('$.name == "foobar"') | ||
* @returns {CollectionRemove} The operation instance. | ||
*/ | ||
remove (expr) { | ||
return collectionRemove(this.getSession(), this.getSchema(), this.getName(), expr); | ||
}, | ||
/** | ||
* Remove a single document with the given id. | ||
* @function | ||
* @name module:Collection#removeOne | ||
* @param {string} id - document id | ||
* @example | ||
* collection.removeOne('1') | ||
* @returns {Promise.<Result>} A promise that resolves to the operation result. | ||
*/ | ||
removeOne (id) { | ||
return this.remove(`$._id == "${escapeQuotes(id)}"`).execute(); | ||
}, | ||
/** | ||
* Replace an entire document with a given id. | ||
* @function | ||
* @name module:Collection#replaceOne | ||
* @param {string} id - document id | ||
* @param {Object} data - document properties | ||
* @example | ||
* collection.replaceOne('foo', { prop1: 'bar', prop2: 'baz' }) | ||
* @returns {Promise.<Result>} A promise that resolves to the operation result. | ||
*/ | ||
replaceOne (id, data) { | ||
const docs = []; | ||
// TODO(Rui): all this can be boiled down to the following, as soon as the expression parser is replaced | ||
// return this.modify(`$._id == "${escapeQuotes(id)}"`).set('$', data).execute() | ||
return this | ||
.find(`$._id == "${escapeQuotes(id)}"`) | ||
.execute(doc => doc && docs.push(doc)) | ||
.then(() => { | ||
const query = this.modify(`$._id == "${escapeQuotes(id)}"`); | ||
if (docs.length) { | ||
const toReplace = Object.keys(docs[0]) | ||
.filter(key => key !== `_id`) | ||
.map(key => `$.${key}`); | ||
query.unset(toReplace); | ||
} | ||
Object.keys(data).forEach(key => { | ||
if (key === `_id`) { | ||
return; | ||
} | ||
query.set(`$.${key}`, data[key]); | ||
}); | ||
return query.execute(); | ||
}); | ||
} | ||
}); | ||
} | ||
module.exports = Collection; |
/* | ||
* Copyright (c) 2015, 2016, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* | ||
@@ -34,67 +34,90 @@ * MySQL Connector/Node.js is licensed under the terms of the GPLv2 | ||
/** | ||
* Operation to add documents to a collection. | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Collection} collection | ||
* @param {object} document | ||
* @constructor | ||
* CollectionAdd factory. | ||
* @module CollectionAdd | ||
*/ | ||
function CollectionAdd (session, schema, collection, document) { | ||
this._session = session; | ||
this._schema = schema; | ||
this._collection = collection; | ||
this._document = document || []; | ||
} | ||
module.exports = CollectionAdd; | ||
/** | ||
* Create query to add one or various documents. | ||
* @param {...Object|Object[]} document - object with document data | ||
* @throws {Error} When the input type is invalid. | ||
* @example | ||
* // arguments as single documents | ||
* collection.add({ foo: 'baz' }).add({ bar: 'qux' }, { biz: 'quux' }) | ||
* | ||
* // array of documents | ||
* collection.add([{ foo: 'baz' }]).add([{ bar: 'qux' }, { biz: 'quux' }]) | ||
* @private | ||
* @alias module:CollectionAdd | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} collection - collection name | ||
* @param {Object} documents - documents to add | ||
* @returns {CollectionAdd} | ||
*/ | ||
CollectionAdd.prototype.add = function () { | ||
const documents = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
function CollectionAdd (session, schema, collection, documents, options) { | ||
let state = { session, schema, collection, documents: documents || [], options: Object.assign({}, { upsert: false }, options) }; | ||
this._document = this._document.concat(documents); | ||
return { | ||
/** | ||
* Run the query to save the documents to the collection in the database. | ||
* If a document does not contain an <code>_id</code>, it will be assigned a UUID-like value. | ||
* @function | ||
* @name module:CollectionAdd#execute | ||
* @tutorial Working_with_Documents | ||
* @returns {Promise.<Result>} | ||
*/ | ||
execute () { | ||
if (!state.documents.length) { | ||
return Promise.resolve(); | ||
} | ||
return this; | ||
}; | ||
const docs = state.documents.map(doc => { | ||
if (typeof doc._id !== 'undefined') { | ||
return Object.assign({}, doc); | ||
} | ||
/** | ||
* Run the query to save the documents to the collection in the database. | ||
* If a document does not contain an <code>_id</code>, it will be assigned a UUID-like value. | ||
* @tutorial Working_with_Documents | ||
* @returns {Promise.<Result>} | ||
*/ | ||
CollectionAdd.prototype.execute = function () { | ||
if (!this._document.length) { | ||
return Promise.resolve(); | ||
} | ||
return Object.assign({}, doc, { _id: state.session.idGenerator() }); | ||
}); | ||
const documentIds = []; | ||
return state | ||
.session | ||
._client | ||
.crudInsert(state.schema.getName(), state.collection, Client.dataModel.DOCUMENT, { rows: docs.map(doc => [JSON.stringify(doc)]) }, state.options) | ||
.then(state => new Result(Object.assign({}, state, { doc_ids: docs.map(doc => doc._id) }))); | ||
}, | ||
const documents = this._document.map(doc => { | ||
if (typeof doc._id === 'undefined') { | ||
doc._id = this._session.idGenerator(); | ||
} | ||
/** | ||
* Create query to add one or various documents. | ||
* @function | ||
* @name module:CollectionAdd#add | ||
* @param {...Object|Object[]} input - document or list of documents | ||
* @throws {Error} When the input type is invalid. | ||
* @example | ||
* // arguments as single documents | ||
* collection.add({ foo: 'baz' }).add({ bar: 'qux' }, { biz: 'quux' }) | ||
* | ||
* // array of documents | ||
* collection.add([{ foo: 'baz' }]).add([{ bar: 'qux' }, { biz: 'quux' }]) | ||
* @returns {CollectionAdd} The operation instance. | ||
*/ | ||
add () { | ||
const documents = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
documentIds.push(doc._id); | ||
state.documents = state.documents.concat(documents); | ||
return [ JSON.stringify(doc) ]; | ||
}); | ||
return this; | ||
}, | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:CollectionAdd#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'CollectionAdd'; | ||
}, | ||
return this._session._client | ||
.crudInsert(this._schema.getName(), this._collection, Client.dataModel.DOCUMENT, documents) | ||
.then(state => { | ||
state.doc_ids = documentIds; | ||
return new Result(state); | ||
}); | ||
}; | ||
/** | ||
* Retrieve the documents scheduled to be added. | ||
* @function | ||
* @name module:CollectionAdd#getDocuments | ||
* @returns {Object} The set of documents. | ||
*/ | ||
getDocuments () { | ||
return state.documents; | ||
} | ||
}; | ||
} | ||
module.exports = CollectionAdd; |
/* | ||
* Copyright (c) 2015, 2016, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* | ||
@@ -29,101 +29,97 @@ * MySQL Connector/Node.js is licensed under the terms of the GPLv2 | ||
const BaseQuery = require('./BaseQuery'); | ||
const Client = require('../Protocol/Client'); | ||
const Result = require('./Result'); | ||
const util = require('util'); | ||
const binding = require('./Binding'); | ||
const filtering = require('./Filtering'); | ||
const limiting = require('./Limiting'); | ||
const locking = require('./Locking'); | ||
/** | ||
* A callback for a single document | ||
* | ||
* This will be called for each document received from a Collection | ||
* | ||
* @callback documentCallback The document as stored in the database | ||
* @param {object} object | ||
* CollectionFind factory. | ||
* @module CollectionFind | ||
* @mixes Filtering | ||
* @mixes Binding | ||
* @mixes Limiting | ||
* @mixes Locking | ||
*/ | ||
/** | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Collection} collection | ||
* @param {String} [query] | ||
* @constructor | ||
* @augments BaseQuery | ||
* @private | ||
* @alias module:CollectionFind | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} collection - collection name | ||
* @param {string} [criteria] - filtering criteria expression | ||
* @returns {CollectionFind} | ||
*/ | ||
function CollectionFind (session, schema, collection, query) { | ||
BaseQuery.call(this); | ||
function CollectionFind (session, schema, collection, criteria) { | ||
let state = Object.assign({ projection: [] }, { session, schema, collection }); | ||
this._session = session; | ||
this._schema = schema; | ||
this._collection = collection; | ||
this._query = query; | ||
this._bounds = {}; | ||
this._projection = []; | ||
} | ||
return Object.assign({}, filtering({ criteria }), binding(), limiting(), locking(), { | ||
/** | ||
* Document property projection expressions. | ||
* @typedef {ProjectedSearchExprStrList|Expression} ProjectedDocumentExprStr | ||
*/ | ||
util.inherits(CollectionFind, BaseQuery); | ||
/** | ||
* Pick collection properties for projection. | ||
* @function | ||
* @name module:CollectionFind#fields | ||
* @param {ProjectedDocumentExprStr} projection - expression with the properties to pick | ||
* @returns {CollectionFind} The operation instance. | ||
*/ | ||
fields (projection) { | ||
if (!Array.isArray(projection)) { | ||
throw new Error('Argument to fields() must be an array of field selectors'); | ||
} | ||
module.exports = CollectionFind; | ||
state.projection = projection; | ||
CollectionFind.prototype.fields = function (fields) { | ||
if (!Array.isArray(fields)) { | ||
throw new Error('Argument to fields() must be an array of field selectors'); | ||
} | ||
return this; | ||
}, | ||
this._projection = fields; | ||
/** | ||
* Cursor callback. | ||
* @callback documentCallback | ||
* @global | ||
* @param {object} object - the document in the cursor position | ||
*/ | ||
return this; | ||
}; | ||
/** | ||
* Execute find operation. | ||
* @function | ||
* @name module:CollectionFind#execute | ||
* @param {documentCallback} [rowcb] | ||
* @return {Promise.<Result>} | ||
*/ | ||
execute (rowcb) { | ||
let cb; | ||
/** | ||
* Bind values to query parameters. | ||
* @param {string|Object} parameter - parameter name or mapping object | ||
* @param {string} [value] | ||
* @example | ||
* // parameter name and value as arguments | ||
* const query = collection.find('$.foo == :foo').bind('foo', 'bar') | ||
* | ||
* // parameter name and value as key-value pair in an object | ||
* const query = collection.find('$.foo == :foo').bind({ foo: 'bar' }) | ||
* @returns {TableSelect} | ||
*/ | ||
CollectionFind.prototype.bind = function () { | ||
if (!arguments.length) { | ||
return this; | ||
} | ||
if (rowcb) { | ||
cb = function (row) { | ||
rowcb(row[0]); | ||
}; | ||
} | ||
let bound; | ||
const joins = []; | ||
if (Object(arguments[0]) === arguments[0]) { | ||
bound = arguments[0]; | ||
} else { | ||
bound = { [arguments[0]]: arguments[1] }; | ||
} | ||
return state | ||
.session | ||
._client | ||
.crudFind(state.session, state.schema.getName(), state.collection, Client.dataModel.DOCUMENT, state.projection, this.getCriteria(), null, null, null, this.getLimit(), cb, null, this.getBindings(), joins, this.getLockingMode()) | ||
.then(state => new Result(state)); | ||
}, | ||
this._bounds = Object.assign(this._bounds, bound); | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:CollectionFind#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'CollectionFind'; | ||
} | ||
}); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Execute find operation | ||
* @param {documentCallback} [rowcb] | ||
* @return {Promise.<Result>} | ||
*/ | ||
CollectionFind.prototype.execute = function (rowcb) { | ||
let cb; | ||
if (rowcb) { | ||
cb = function (row) { | ||
rowcb(row[0]); | ||
}; | ||
} | ||
const joins = []; | ||
return this | ||
._session | ||
._client | ||
.crudFind(this._session, this._schema.getName(), this._collection, Client.dataModel.DOCUMENT, this._projection, this._query, null, null, null, this._limit, cb, null, this._bounds, joins) | ||
.then(state => new Result(state)); | ||
}; | ||
module.exports = CollectionFind; |
@@ -29,119 +29,159 @@ /* | ||
const BaseQuery = require('./BaseQuery'); | ||
const Client = require('../Protocol/Client'); | ||
const Expressions = require('../Expressions'); | ||
const Result = require('./Result'); | ||
const util = require('util'); | ||
const filtering = require('./Filtering'); | ||
const limiting = require('./Limiting'); | ||
/** | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Collection} collection | ||
* @param {String} [query] | ||
* @constructor | ||
* CollectionModify factory. | ||
* @module CollectionModify | ||
* @mixes Filtering | ||
* @mixes Limiting | ||
*/ | ||
function CollectionModify (session, schema, collection, query) { | ||
BaseQuery.call(this); | ||
this._session = session; | ||
this._schema = schema; | ||
this._collection = collection; | ||
this._query = query; | ||
this._operations = []; | ||
} | ||
/** | ||
* @private | ||
* @alias module:CollectionModify | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} collection - collection name | ||
* @param {string} [criteria] - filtering criteria expression | ||
* @returns {CollectionModify} | ||
*/ | ||
function CollectionModify (session, schema, collection, criteria) { | ||
let state = Object.assign({ operations: [] }, { session, schema, collection }); | ||
util.inherits(CollectionModify, BaseQuery); | ||
return Object.assign({}, limiting(), filtering({ criteria }), { | ||
/** | ||
* Append element to an array field. | ||
* @function | ||
* @name module:CollectionModify#arrayAppend | ||
* @param {string} field - document array field | ||
* @param {} value - value to append | ||
* @returns {CollectionModify} The operation instance. | ||
*/ | ||
arrayAppend (field, value) { | ||
state.operations.push(getOperation(Client.updateOperations.ARRAY_APPEND, field, value)); | ||
module.exports = CollectionModify; | ||
return this; | ||
}, | ||
function getOperation(op, field, expression) { | ||
var fieldExpression = Expressions.parse(field); | ||
if (!fieldExpression.expr.identifier) { | ||
throw new Error("Field expression has to be identifier while parsing " + field); | ||
} | ||
var operation = { | ||
source: fieldExpression.expr.identifier, | ||
operation: op | ||
}; | ||
/** | ||
* Delete element from an array. | ||
* @function | ||
* @name module:CollectionModify#arrayDelete | ||
* @param {string} field - document array field | ||
* @param {} value - value to delete | ||
* @returns {CollectionModify} The operation instance. | ||
*/ | ||
arrayDelete (field, expression) { | ||
state.operations.push(getOperation(Client.updateOperations.ITEM_REMOVE, field, expression)); | ||
if (expression) { | ||
operation.value = Expressions.literalOrParsedExpression(expression); | ||
} | ||
return this; | ||
}, | ||
return operation; | ||
} | ||
/** | ||
* Insert element into an array field. | ||
* @function | ||
* @name module:CollectionModify#arrayInsert | ||
* @param {string} field - document array field | ||
* @param {} value - value to insert | ||
* @returns {CollectionModify} The operation instance. | ||
*/ | ||
arrayInsert (field, value) { | ||
state.operations.push(getOperation(Client.updateOperations.ARRAY_INSERT, field, value)); | ||
/** | ||
* Set a field in the document | ||
* @param {String} field Field descriptor | ||
* @param {String} expression Replacement expression | ||
* @returns {CollectionModify} | ||
*/ | ||
CollectionModify.prototype.set = function (field, expression) { | ||
this._operations.push(getOperation(Client.updateOperations.ITEM_SET, field, expression)); | ||
return this; | ||
}; | ||
return this; | ||
}, | ||
/** | ||
* Unset fields from document | ||
* | ||
* @param {Array.<String>|String} fields | ||
* @returns {CollectionModify} | ||
*/ | ||
CollectionModify.prototype.unset = function (fields) { | ||
if (typeof fields === "string") { | ||
fields = [ fields ]; | ||
} | ||
fields = fields.map(field => getOperation(Client.updateOperations.ITEM_REMOVE, field)); | ||
this._operations = this._operations.concat(fields); | ||
/** | ||
* Execute modify operation. | ||
* @function | ||
* @name module:CollectionModify#execute | ||
* @return {Promise.<Result>} | ||
*/ | ||
execute () { | ||
if (typeof this.getCriteria() !== 'string' || this.getCriteria().trim().length < 1) { | ||
return Promise.reject(new Error('remove needs a valid condition')); | ||
} | ||
return this; | ||
}; | ||
return state | ||
.session | ||
._client | ||
.crudModify(state.schema.getName(), state.collection, Client.dataModel.DOCUMENT, this.getCriteria(), state.operations, this.getLimit(), {}) | ||
.then(state => new Result(state)); | ||
}, | ||
CollectionModify.prototype.merge = function () { | ||
throw new Error("modify.merge currently not implemented"); | ||
}; | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:CollectionModify#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'CollectionModify'; | ||
}, | ||
/** | ||
* | ||
* @param field | ||
* @param expression | ||
* @returns {CollectionModify} | ||
*/ | ||
CollectionModify.prototype.arrayInsert = function (field, expression) { | ||
this._operations.push(getOperation(Client.updateOperations.ARRAY_INSERT, field, expression)); | ||
return this; | ||
}; | ||
merge () { | ||
throw new Error('modify.merge currently not implemented'); | ||
}, | ||
/** | ||
* Append an element to an array | ||
* @param {String} field | ||
* @param {String} expression | ||
* @returns {CollectionModify} | ||
*/ | ||
CollectionModify.prototype.arrayAppend = function (field, expression) { | ||
this._operations.push(getOperation(Client.updateOperations.ARRAY_APPEND, field, expression)); | ||
return this; | ||
}; | ||
/** | ||
* Set the value of a given document field. | ||
* @function | ||
* @name module:CollectionModify#set | ||
* @param {string} field - document field | ||
* @param {*} value - value to assign | ||
* @returns {CollectionModify} The operation instance. | ||
*/ | ||
set (field, value) { | ||
state.operations.push(getOperation(Client.updateOperations.ITEM_SET, field, value)); | ||
CollectionModify.prototype.arrayDelete = function (field, expression) { | ||
this._operations.push(getOperation(Client.updateOperations.ITEM_REMOVE, field, expression)); | ||
return this; | ||
}; | ||
return this; | ||
}, | ||
/** | ||
* Unset the value of document fields. | ||
* @function | ||
* @name module:CollectionModify#unset | ||
* @param {Array.<String>|String} fields | ||
* @returns {CollectionModify} The operation instance. | ||
*/ | ||
unset (fields) { | ||
if (typeof fields === 'string') { | ||
fields = [ fields ]; | ||
} | ||
fields = fields.map(field => getOperation(Client.updateOperations.ITEM_REMOVE, field)); | ||
state.operations = state.operations.concat(fields); | ||
return this; | ||
} | ||
}); | ||
} | ||
/** | ||
* Execute find operation | ||
* @return {Promise.<Result>} | ||
* @private | ||
*/ | ||
CollectionModify.prototype.execute = function () { | ||
if (typeof this._query !== 'string' || this._query.trim().length < 1) { | ||
return Promise.reject(new Error('remove needs a valid condition')); | ||
function getOperation (op, field, expression) { | ||
const fieldExpression = Expressions.parse(field); | ||
if (!fieldExpression.expr.identifier) { | ||
throw new Error(`Field expression has to be identifier while parsing ${field}`); | ||
} | ||
return this | ||
._session | ||
._client | ||
.crudModify(this._schema.getName(), this._collection, Client.dataModel.DOCUMENT, this._query, this._operations, this._limit, {}) | ||
.then(state => new Result(state)); | ||
}; | ||
const operation = { | ||
source: fieldExpression.expr.identifier, | ||
operation: op | ||
}; | ||
if (expression) { | ||
operation.value = Expressions.literalOrParsedExpression(expression); | ||
} | ||
return operation; | ||
} | ||
module.exports = CollectionModify; |
@@ -29,49 +29,59 @@ /* | ||
// TODO: Lots of duplication with CollectionFind .... | ||
const BaseQuery = require('./BaseQuery'); | ||
const Client = require('../Protocol/Client'); | ||
const Result = require('./Result'); | ||
const util = require('util'); | ||
const binding = require('./Binding'); | ||
const filtering = require('./Filtering'); | ||
const limiting = require('./Limiting'); | ||
/** | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Collection} collection | ||
* @param {String} [query] | ||
* @constructor | ||
* CollectionRemove factory. | ||
* @module CollectionRemove | ||
* @mixes Filtering | ||
* @mixes Binding | ||
* @mixes Limiting | ||
*/ | ||
function CollectionRemove (session, schema, collection, query) { | ||
BaseQuery.call(this); | ||
this._session = session; | ||
this._schema = schema; | ||
this._collection = collection; | ||
this._query = query; | ||
this._bounds = {}; | ||
} | ||
/** | ||
* @private | ||
* @alias module:CollectionRemove | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} collection - collection name | ||
* @param {string} [criteria] - filtering criteria expression | ||
* @returns {CollectionRemove} | ||
*/ | ||
function CollectionRemove (session, schema, collection, criteria) { | ||
let state = Object.assign({}, { session, schema, collection }); | ||
util.inherits(CollectionRemove, BaseQuery); | ||
return Object.assign({}, filtering({ criteria }), binding(), limiting(), { | ||
/** | ||
* Execute remove operation. | ||
* @function | ||
* @name module:CollectionRemove#execute | ||
* @return {Promise.<Result>} | ||
*/ | ||
execute () { | ||
if (typeof this.getCriteria() !== 'string' || this.getCriteria().trim().length < 1) { | ||
return Promise.reject(new Error('remove needs a valid condition')); | ||
} | ||
module.exports = CollectionRemove; | ||
return state | ||
.session | ||
._client | ||
.crudRemove(state.schema.getName(), state.collection, Client.dataModel.DOCUMENT, this.getCriteria(), this.getLimit(), this.getBindings()) | ||
.then(state => new Result(state)); | ||
}, | ||
CollectionRemove.prototype.bind = function (bind) { | ||
this._bounds = Object.assign({}, this._bounds, bind); | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:CollectionRemove#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'CollectionRemove'; | ||
} | ||
}); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Remove documents from a collection. | ||
*/ | ||
CollectionRemove.prototype.execute = function () { | ||
if (typeof this._query !== 'string' || this._query.trim().length < 1) { | ||
return Promise.reject(new Error('remove needs a valid condition')); | ||
} | ||
return this | ||
._session | ||
._client | ||
.crudRemove(this._schema.getName(), this._collection, Client.dataModel.DOCUMENT, this._query, this._limit, this._bounds).then(state => new Result(state)); | ||
}; | ||
module.exports = CollectionRemove; |
/* | ||
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* | ||
@@ -33,6 +33,6 @@ * MySQL Connector/Node.js is licensed under the terms of the GPLv2 | ||
* Column Definition for table creation. This is typically created via {@link Schema#columnDef} | ||
* and injected into {@link TableCreater#addColumn}. | ||
* and injected into {@link TableFactory#addColumn}. | ||
* | ||
* @see {Schema#columnDef} | ||
* @see {TableCreater#addColumn} | ||
* @see {TableFactory#addColumn} | ||
* @param {string} name | ||
@@ -39,0 +39,0 @@ * @param type |
@@ -30,28 +30,24 @@ /* | ||
/** | ||
* Base object for different dataase objects | ||
* @param {Session} session - session instance | ||
* @param {string} schema - schema name | ||
* @constructor | ||
* DatabaseObject mixin. | ||
* @mixin | ||
* @alias DatabaseObject | ||
* @param {Session} session | ||
* @returns {DatabaseObject} | ||
*/ | ||
function DatabaseObject (session, schema) { | ||
this._session = session; | ||
this._schema = schema; | ||
function DatabaseObject (session) { | ||
const state = Object.assign({}, { session }); | ||
/** | ||
* Retrieve the session associated to this entity. | ||
* @function | ||
* @name DatabaseObject#getSession | ||
* @returns {Session} The session the entity is bound to. | ||
*/ | ||
return { | ||
getSession () { | ||
return state.session; | ||
} | ||
}; | ||
} | ||
module.exports = DatabaseObject; | ||
/** | ||
* Get session related to this object | ||
* @returns {Session|*} | ||
*/ | ||
DatabaseObject.prototype.getSession = function () { | ||
return this._session; | ||
}; | ||
/** | ||
* Get the current schema name. | ||
* @returns {string} | ||
*/ | ||
DatabaseObject.prototype.getSchema = function () { | ||
return this._schema; | ||
}; |
@@ -51,12 +51,2 @@ /* | ||
cache: {}, | ||
config: { | ||
system: { | ||
unix: path.resolve('/', 'etc', 'mysql', filename), | ||
windows: path.resolve(os.homedir(), 'PROGRAMDATA', 'MySQL', filename) | ||
}, | ||
user: { | ||
unix: path.resolve(os.homedir(), '.mysql', filename), | ||
windows: path.resolve(os.homedir(), 'APPDATA', 'MySQL', filename) | ||
} | ||
}, | ||
platform: os.platform() !== 'win32' ? 'unix' : 'windows' | ||
@@ -84,3 +74,5 @@ }, state); | ||
function getSystemConfigPath () { | ||
return process.env.MYSQL_SYSTEM_CONFIG || state.config.system[state.platform]; | ||
return process.env.MYSQL_SYSTEM_CONFIG || (state.platform === 'unix' | ||
? path.resolve('/', 'etc', 'mysql', filename) | ||
: path.join(process.env.PROGRAMDATA, 'MySQL', filename)); | ||
} | ||
@@ -108,3 +100,5 @@ | ||
function getUserConfigPath () { | ||
return process.env.MYSQL_USER_CONFIG || state.config.user[state.platform]; | ||
return process.env.MYSQL_USER_CONFIG || (state.platform === 'unix' | ||
? path.resolve(os.homedir(), '.mysql', filename) | ||
: path.join(process.env.APPDATA, 'MySQL', filename)); | ||
} | ||
@@ -111,0 +105,0 @@ |
/* | ||
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* | ||
@@ -40,6 +40,6 @@ * MySQL Connector/Node.js is licensed under the terms of the GPLv2 | ||
* Object describing a Foreign Key Definition. Created usually via {@link Schema#foreignKey} and | ||
* injected to {@link TableCreater#addForeignKey}. | ||
* injected to {@link TableFactory#addForeignKey}. | ||
* | ||
* @see {Schema#foreignKey} | ||
* @see {TableCreater#addForeignKey} | ||
* @see {TableFactory#addForeignKey} | ||
* @param {Schema} schema | ||
@@ -126,2 +126,2 @@ * @constructor | ||
; | ||
}; | ||
}; |
@@ -29,318 +29,366 @@ /* | ||
const Collection = require('./Collection'); | ||
const ColumnDefinition = require('./ColumnDefinition'); | ||
const DatabaseObject = require('./DatabaseObject'); | ||
const ForeignKeyDefinition = require('./ForeignKeyDefinition'); | ||
const Table = require('./Table'); | ||
const TableCreator = require('./TableCreater'); | ||
const ViewCreator = require('./ViewCreate'); | ||
const util = require('util'); | ||
const TableFactory = require('./TableFactory'); | ||
const ViewFactory = require('./ViewFactory'); | ||
const collection = require('./Collection'); | ||
const databaseObject = require('./DatabaseObject'); | ||
const table = require('./Table'); | ||
/** | ||
* Access to Schema-related operations | ||
* | ||
* Usually you shouldn't create this yourself but via session object | ||
* | ||
* @param {Session} session | ||
* @param {string} schema Name of the schema | ||
* @constructor | ||
* @extends DatabaseObject | ||
* Schema factory. | ||
* @module Schema | ||
* @mixes DatabaseObject | ||
*/ | ||
function Schema (session, schema) { | ||
DatabaseObject.call(this, session, schema); | ||
} | ||
module.exports = Schema; | ||
util.inherits(Schema, DatabaseObject); | ||
/** | ||
* Get the name of this schema | ||
* @returns {string} | ||
* @private | ||
* @alias module:Schema | ||
* @param {Session} session - session to bind | ||
* @param {string} name - schema name | ||
* @returns {Schema} | ||
*/ | ||
Schema.prototype.getName = function () { | ||
return this._schema; | ||
}; | ||
function Schema (session, name) { | ||
const state = Object.assign({}, { name, session }); | ||
/** | ||
* Verifies this schema exists | ||
* | ||
* @returns {Promise<boolean>} | ||
*/ | ||
Schema.prototype.existsInDatabase = function () { | ||
let status = false; | ||
return Object.assign({}, databaseObject(session), { | ||
/** | ||
* Type flags for table creation | ||
* @type {{Bit: string, Tinyint: string, Smallint: string, Mediumint: string, Int: string, Bigint: string, Real: string, Double: string, Float: string, Decimal: string, Numeric: string, Date: string, Time: string, Timestamp: string, Datetime: string, Year: string, Char: string, Varchar: string, Binary: string, Varbinary: string, Tinyblob: string, Blob: string, Mediumblob: string, Longblob: string, Tinytext: string, Text: string, Mediumtext: string, Enum: string, Set: string, Json: string}} | ||
*/ | ||
Type: { | ||
Bit: 'BIT', | ||
Tinyint: 'TINYINT', | ||
Smallint: 'SMALLINT', | ||
Mediumint: 'MEDIUMINT', | ||
Int: 'INT', | ||
Bigint: 'BIGINT', | ||
Real: 'REAL', | ||
Double: 'DOUBLE', | ||
Float: 'FLOAT', | ||
Decimal: 'DECIMAL', | ||
Numeric: 'NUMERIC', | ||
Date: 'DATE', | ||
Time: 'TIME', | ||
Timestamp: 'TIMESTAMP', | ||
Datetime: 'DATETIME', | ||
Year: 'YEAR', | ||
Char: 'CHAR', | ||
Varchar: 'VARCHAR', | ||
Binary: 'BINARY', | ||
Varbinary: 'VARBINARY', | ||
Tinyblob: 'TINYBLOB', | ||
Blob: 'BLOB', | ||
Mediumblob: 'MEDIUMBLOB', | ||
Longblob: 'LONGBLOB', | ||
Tinytext: 'TINYTEXT', | ||
Text: 'TEXT', | ||
Mediumtext: 'MEDIUMTEXT', | ||
Enum: 'ENUM', | ||
Set: 'SET', | ||
Json: 'JSON' | ||
}, | ||
return this._session._client | ||
.sqlStmtExecute('SHOW DATABASES LIKE ?', [this._schema], (found) => { status = !!found.length; }) | ||
.then(() => status); | ||
}; | ||
/** | ||
* Create an operation to alter a view. | ||
* @function | ||
* @name module:Schema#alterView | ||
* @param {string} name | ||
* @returns {ViewFactory} | ||
*/ | ||
alterView (name) { | ||
return new ViewFactory(this, name, true); | ||
}, | ||
/** | ||
* List objects of a given type using a factory function. | ||
*/ | ||
function _listObjects (type, factory) { | ||
const result = {}; | ||
const callback = (row) => { | ||
if (row[1] === type || type === 'TABLE' && row[1] === 'VIEW') { | ||
result[row[0]] = factory.call(this, row[0]); | ||
} | ||
}; | ||
/** | ||
* Create a Column Definition object which can be injected into {@link TableCreater#addColumn}. | ||
* @function | ||
* @name module:Schema#columnDef | ||
* @see {TableFactory#addColumn} | ||
* @param {string} name - field name | ||
* @param {string} type - field type, usually a member of {@link Schema#Type} should be used | ||
* @param {number} length | ||
* @returns {ColumnDefinition} | ||
*/ | ||
columnDef (name, type, length) { | ||
return new ColumnDefinition(name, type, length); | ||
}, | ||
return this._session._client | ||
.sqlStmtExecute('list_objects', [this._schema], callback, null, 'xplugin') | ||
.then(() => result); | ||
}; | ||
/** | ||
* Options available for creating a new collection. | ||
* @typedef {object} CreateCollectionOptions | ||
* @property {bool} [ReuseExistingObject] re-use or throw error if a collection with the same name already exists | ||
*/ | ||
/** | ||
* Get collections | ||
* | ||
* @returns {Promise.<CollectionList>} Promise resolving to an object of Collection name <-> Collection object pairs | ||
*/ | ||
Schema.prototype.getCollections = function () { | ||
return _listObjects.call(this, 'COLLECTION', this.getCollection); | ||
}; | ||
/** | ||
* Create a new collection in the schema. | ||
* @function | ||
* @name module:Schema#createCollection | ||
* @param {string} collection - collection name | ||
* @param {CreateCollectionOptions} [options] - setup options | ||
* @returns {Promise.<Collection>} | ||
*/ | ||
createCollection (collection, options) { | ||
options = Object.assign({}, { ReuseExistingObject: false }, options); | ||
/** | ||
* Get a Collection object | ||
* | ||
* This will always succeed | ||
* | ||
* @param {string} collection Name of the collection | ||
* @returns {Collection} | ||
*/ | ||
Schema.prototype.getCollection = function (collection) { | ||
return new Collection(this._session, this, collection); | ||
}; | ||
const args = [this.getName(), collection]; | ||
/** | ||
* Options | ||
* @typedef {object} createOptions | ||
* @property {bool} [ReuseExistingObject] If true this won't error if the collection exists already | ||
*/ | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute('create_collection', args, null, null, 'xplugin') | ||
.then(() => this.getCollection(collection)) | ||
.catch(err => { | ||
if (err.info.code !== 1050 || !options.ReuseExistingObject) { | ||
throw err; | ||
} | ||
/** | ||
* Create a new collection | ||
* | ||
* The returned Promise object will resolve on success or provide an Error | ||
* | ||
* @param {string} collection Name of the collection | ||
* @param {CreateOptions} [options] | ||
* @returns {Promise.<Collection>} | ||
*/ | ||
Schema.prototype.createCollection = function (collection, options) { | ||
options = Object.assign({}, { ReuseExistingObject: false }, options); | ||
return this.getCollection(collection); | ||
}); | ||
}, | ||
const args = [this._schema, collection]; | ||
/** | ||
* Retrieve the factory to create a new table in the schema (examples available in the {@tutorial Table_Creation_API} tutorial). | ||
* @function | ||
* @name module:Schema#createTable | ||
* @param {string} name - table name | ||
* @param {bool} replace - re-use or throw error if a table with the same name already exists | ||
* @return {TableFactory} | ||
*/ | ||
createTable (name, replace) { | ||
return new TableFactory(this, name, replace); | ||
}, | ||
return this._session._client.sqlStmtExecute('create_collection', args, null, null, 'xplugin') | ||
.then(() => this.getCollection(collection)) | ||
.catch(err => { | ||
if (err.info.code !== 1050 || !options.ReuseExistingObject) { | ||
throw err; | ||
} | ||
/** | ||
* Retrieve the factory to create a new view in the schema. | ||
* @function | ||
* @name module:Schema#createView | ||
* @param {string} name | ||
* @param {bool} replace - re-use or throw error if a table with the same name already exists | ||
* @returns {ViewFactory} | ||
*/ | ||
createView (name, replace) { | ||
return new ViewFactory(this, name, replace); | ||
}, | ||
return this.getCollection(collection); | ||
}); | ||
}; | ||
/** | ||
* Drop a collection from the schema (without failing even if the collection does not exist). | ||
* @function | ||
* @name module:Schema#dropCollection | ||
* @param {string} name - collection name | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
dropCollection (name) { | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute('drop_collection', [this.getName(), name], null, null, 'xplugin') | ||
.then(() => true) | ||
.catch(err => { | ||
// Don't fail if the collection does not exist. | ||
if (!err.info || err.info.code !== 1051) { | ||
throw err; | ||
} | ||
/** | ||
* Create a new Table in this schema. | ||
* | ||
* | ||
* An example for using this function can be found in the {@tutorial Table_Creation_API} tutorial. | ||
* | ||
* | ||
* @param {String} name Name of the new table | ||
* @param {bool} reuseExisting Flag whether to reuse an existing table, defaults to false | ||
* @return {TableCreater} | ||
*/ | ||
Schema.prototype.createTable = function (name, reuseExisting) { | ||
return new TableCreator(this, name, reuseExisting); | ||
}; | ||
return true; | ||
}); | ||
}, | ||
/** | ||
* Type flags for table creation | ||
* @type {{Bit: string, Tinyint: string, Smallint: string, Mediumint: string, Int: string, Bigint: string, Real: string, Double: string, Float: string, Decimal: string, Numeric: string, Date: string, Time: string, Timestamp: string, Datetime: string, Year: string, Char: string, Varchar: string, Binary: string, Varbinary: string, Tinyblob: string, Blob: string, Mediumblob: string, Longblob: string, Tinytext: string, Text: string, Mediumtext: string, Enum: string, Set: string, Json: string}} | ||
*/ | ||
Schema.prototype.Type = { | ||
Bit: 'BIT', | ||
Tinyint: 'TINYINT', | ||
Smallint: 'SMALLINT', | ||
Mediumint: 'MEDIUMINT', | ||
Int: 'INT', | ||
Bigint: 'BIGINT', | ||
Real: 'REAL', | ||
Double: 'DOUBLE', | ||
Float: 'FLOAT', | ||
Decimal: 'DECIMAL', | ||
Numeric: 'NUMERIC', | ||
Date: 'DATE', | ||
Time: 'TIME', | ||
Timestamp: 'TIMESTAMP', | ||
Datetime: 'DATETIME', | ||
Year: 'YEAR', | ||
Char: 'CHAR', | ||
Varchar: 'VARCHAR', | ||
Binary: 'BINARY', | ||
Varbinary: 'VARBINARY', | ||
Tinyblob: 'TINYBLOB', | ||
Blob: 'BLOB', | ||
Mediumblob: 'MEDIUMBLOB', | ||
Longblob: 'LONGBLOB', | ||
Tinytext: 'TINYTEXT', | ||
Text: 'TEXT', | ||
Mediumtext: 'MEDIUMTEXT', | ||
Enum: 'ENUM', | ||
Set: 'SET', | ||
Json: 'JSON' | ||
}; | ||
/** | ||
* Drop a table from the schema (without failing even if the table does not exist). | ||
* @function | ||
* @name module:Schema#dropTable | ||
* @param {string} name - table name | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
// TODO(Rui): remove duplication with dropCollection. | ||
dropTable (name) { | ||
const schema = table.escapeIdentifier(this.getName()); | ||
const tableName = table.escapeIdentifier(name); | ||
/** | ||
* Create a Column Definition object which can be injected into | ||
* {@link TableCreater#addColumn} | ||
* | ||
* @see {TableCreater#addColumn} | ||
* @param {string} name - Name for the field | ||
* @param {String} type - The type for the field, usually a member of {@link Schema#Type} should be used | ||
* @param {Number=} length | ||
* @returns {ColumnDefinition] | ||
*/ | ||
Schema.prototype.columnDef = function (name, type, length) { | ||
return new ColumnDefinition(name, type, length); | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute(`DROP TABLE ${schema}.${tableName}`) | ||
.then(() => true) | ||
.catch(err => { | ||
// Don't fail if the table does not exist. | ||
if (!err.info || err.info.code !== 1051) { | ||
throw err; | ||
} | ||
/** | ||
* Foreign Key Definition for Table Creation | ||
* | ||
* @returns {ForeignKeyDefinition} | ||
*/ | ||
Schema.prototype.foreignKey = function () { | ||
return new ForeignKeyDefinition(this); | ||
}; | ||
return true; | ||
}); | ||
}, | ||
/** | ||
* Create a new view | ||
* | ||
* This will return an object which can be used to create a view | ||
* | ||
* @param {string} name | ||
* @param {bool} replace | ||
* @returns {ViewCreate} | ||
*/ | ||
Schema.prototype.createView = function (name, replace) { | ||
return new ViewCreator(this, name, replace); | ||
}; | ||
/** | ||
* Drop a view from the schema (without failing even if the view does not exist). | ||
* @function | ||
* @name module:Schema#dropView | ||
* @param {String} name - view name | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
// TODO(Rui): remove duplication with dropCollection. | ||
dropView (name) { | ||
const schema = table.escapeIdentifier(this.getName()); | ||
const view = table.escapeIdentifier(name); | ||
/** | ||
* Alters a view | ||
* | ||
* This will return an object which can be used to create a new view definition | ||
* | ||
* @param {string} name | ||
* @param {bool} replace | ||
* @returns {ViewCreate} | ||
*/ | ||
Schema.prototype.alterView = function (name) { | ||
return new ViewCreator(this, name, true); | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute(`DROP VIEW ${schema}.${view}`) | ||
.then(() => true) | ||
.catch(err => { | ||
// Don't fail if the view does not exist. | ||
if (!err.info || err.info.code !== 1051) { | ||
throw err; | ||
} | ||
/** | ||
* Drop a view (without failing even if the view does not exist). | ||
* @param {String} name - view name | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
Schema.prototype.dropView = function (name) { | ||
const schema = Table.escapeIdentifier(this._schema); | ||
const view = Table.escapeIdentifier(name); | ||
return true; | ||
}); | ||
}, | ||
return this._session._client | ||
.sqlStmtExecute(`DROP VIEW ${schema}.${view}`) | ||
.then(() => true) | ||
.catch(err => { | ||
// Don't fail if the view does not exist. | ||
if (!err.info || err.info.code !== 1051) { | ||
throw err; | ||
} | ||
/** | ||
* Check if this schema exists in the database. | ||
* @function | ||
* @name module:Schema#existsInDatabase | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
// TODO(Rui): extract method into a proper aspect (to be used on Collection, Schema and Table). | ||
existsInDatabase () { | ||
let status = false; | ||
return true; | ||
}); | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute('SHOW DATABASES LIKE ?', [this.getName()], (found) => { status = !!found.length; }) | ||
.then(() => status); | ||
}, | ||
/** | ||
* Drop a collection (without failing even if the collection does not exist). | ||
* @param {string} name - collection name | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
Schema.prototype.dropCollection = function (name) { | ||
return this._session._client | ||
.sqlStmtExecute('drop_collection', [this._schema, name], null, null, 'xplugin') | ||
.then(() => true) | ||
.catch(err => { | ||
// Don't fail if the collection does not exist. | ||
if (!err.info || err.info.code !== 1051) { | ||
throw err; | ||
} | ||
/** | ||
* Create a Foreign Key Definition for table creation. | ||
* @function | ||
* @name module:Schema:foreignKey | ||
* @returns {ForeignKeyDefinition} | ||
*/ | ||
foreignKey () { | ||
return new ForeignKeyDefinition(this); | ||
}, | ||
return true; | ||
}); | ||
}; | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:Schema:getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'Schema'; | ||
}, | ||
/** | ||
* Get tables | ||
* | ||
* @returns {Promise.<TableList>} Promise resolving to an object of Table name <-> Table object pairs | ||
*/ | ||
Schema.prototype.getTables = function () { | ||
return _listObjects.call(this, 'TABLE', this.getTable); | ||
}; | ||
/** | ||
* Retrieve the instance of a given collection. | ||
* @function | ||
* @name module:Schema:getCollection | ||
* @param {string} name - collection name | ||
* @returns {Collection} | ||
*/ | ||
getCollection (name) { | ||
return collection(this.getSession(), this, name); | ||
}, | ||
/** | ||
* Get a Table object | ||
* | ||
* This will always succeed | ||
* | ||
* @param {string} table Name of the table | ||
* @returns {Table} | ||
*/ | ||
Schema.prototype.getTable = function (table) { | ||
return new Table(this._session, this, table); | ||
}; | ||
/** | ||
* Retrieve the instance of a given table or named collection. | ||
* @function | ||
* @name module:Schema:getCollectionAsTable | ||
* @param {string} name - collection name | ||
* @returns {Table} | ||
*/ | ||
getCollectionAsTable (name) { | ||
return this.getTable(name); | ||
}, | ||
/** | ||
* Get a Table object or a given named Collection | ||
* | ||
* @param {string} collection Name of the collection | ||
* @returns {Table} | ||
*/ | ||
Schema.prototype.getCollectionAsTable = function (collection) { | ||
return new Table(this._session, this, collection); | ||
}; | ||
/** | ||
* List of available collections. | ||
* @typedef {object} CollectionList | ||
* @property {Collection} [<collection name>] The collection instance | ||
*/ | ||
/** | ||
* Drop a collection (without failing even if the table does not exist). | ||
* @param {string} name - table name | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
Schema.prototype.dropTable = function (name) { | ||
const schema = Table.escapeIdentifier(this._schema); | ||
const table = Table.escapeIdentifier(name); | ||
/** | ||
* Retrieve the list of collections that exist in the schema. | ||
* @function | ||
* @name module:Schema:getCollections | ||
* @returns {Promise.<CollectionList>} | ||
*/ | ||
getCollections () { | ||
return _listObjects.call(this, 'COLLECTION', this.getCollection); | ||
}, | ||
return this._session._client | ||
.sqlStmtExecute(`DROP TABLE ${schema}.${table}`) | ||
.then(() => true) | ||
.catch(err => { | ||
// Don't fail if the table does not exist. | ||
if (!err.info || err.info.code !== 1051) { | ||
throw err; | ||
} | ||
/** | ||
* Retrieve the schema name. | ||
* @function | ||
* @name module:Schema:getName | ||
* @returns {string} | ||
*/ | ||
getName () { | ||
return state.name; | ||
}, | ||
return true; | ||
}); | ||
}; | ||
/** | ||
* Retrieve the instance of a given table. | ||
* @function | ||
* @name module:Schema:getTable | ||
* @param {string} name - table name | ||
* @returns {Table} | ||
*/ | ||
getTable (name) { | ||
return table(this.getSession(), this, name); | ||
}, | ||
/** | ||
* List of available tables. | ||
* @typedef {object} TableList | ||
* @property {Table} [<table name>] The table instance | ||
*/ | ||
/** | ||
* Retrieve the list of tables that exist in the schema. | ||
* @function | ||
* @name module:Schema:getTables | ||
* @returns {Promise.<TableList>} | ||
*/ | ||
getTables () { | ||
return _listObjects.call(this, 'TABLE', this.getTable); | ||
}, | ||
/** | ||
* Retrieve the schema metadata. | ||
* @function | ||
* @name module:Schema#inspect | ||
* @returns {Object} An object containing the relevant metadata. | ||
*/ | ||
inspect () { | ||
return { schema: this.getName() }; | ||
} | ||
}); | ||
} | ||
/** | ||
* Inspect schema | ||
* List objects of a given type using a factory function. | ||
* @private | ||
*/ | ||
Schema.prototype.inspect = function () { | ||
return { schema: this._schema }; | ||
function _listObjects (type, factory) { | ||
const result = {}; | ||
const callback = (row) => { | ||
if (row[1] === type || type === 'TABLE' && row[1] === 'VIEW') { | ||
result[row[0]] = factory.call(this, row[0]); | ||
} | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute('list_objects', [this.getName()], callback, null, 'xplugin') | ||
.then(() => result); | ||
}; | ||
module.exports = Schema; |
@@ -35,3 +35,2 @@ /* | ||
const PlainAuth = require('../Authentication/PlainAuth'); | ||
const Schema = require('./Schema.js'); | ||
const SocketFactory = require('../SocketFactory'); | ||
@@ -42,2 +41,3 @@ const Statement = require('./Statement'); | ||
const parseUri = require('./Util/URIParser'); | ||
const schema = require('./Schema.js'); | ||
const uuid = require('./Util/UUID'); | ||
@@ -76,5 +76,5 @@ | ||
* @property {string} dbPassword Password | ||
* @property {string} authMethod Name of an authentication mehod to use (default: MySQL41) | ||
* @property {string} auth Name of an authentication mehod to use (default: PLAIN) | ||
* @property {SocketFactory} socketFactory A factory which can creaes socket, usually not needed outside tests | ||
* @property {bool} ssl Enable SSL, defaults to false | ||
* @property {bool} ssl Enable SSL, defaults to true | ||
* @property {object} sslOption options passed to tls.TLSSocket constructor, see https://nodejs.org/api/tls.html#tls_new_tls_tlssocket_socket_options | ||
@@ -111,3 +111,3 @@ * @property {IdGenerator} idGenerator Generator to produce document ids | ||
socketFactory: new SocketFactory(), | ||
ssl: true | ||
ssl: properties.ssl || true | ||
}, properties, { | ||
@@ -124,2 +124,3 @@ dbUser: properties.user || properties.dbUser, | ||
endpoint.port = endpoint.socket || endpoint.port ? endpoint.port : 33060; | ||
endpoint.auth = properties.ssl || endpoint.socket ? 'PLAIN' : 'MYSQL41'; | ||
}); | ||
@@ -148,3 +149,11 @@ | ||
function _authenticate () { | ||
const AuthMethod = Auth.get(this._properties.authMethod || 'MYSQL41'); | ||
// This is a workaround because on MySQL 5.7.x, `CapabilitiesGet` over Unix Domain Sockets reports | ||
// no support for `PLAIN`, while `PLAIN` is in fact supported. | ||
const authMechanisms = this._serverCapabilities['authentication.mechanisms'].concat('PLAIN'); | ||
if (authMechanisms.indexOf(this._properties.auth) === -1) { | ||
return Promise.reject(new Error('Authentication mechanism is not supported by the server')); | ||
} | ||
const AuthMethod = Auth.get(this._properties.auth); | ||
const auth = new AuthMethod(this._properties); | ||
@@ -248,2 +257,3 @@ | ||
this._properties.socket = this._properties.endpoints[index].socket; | ||
this._properties.auth = this._properties.auth || (this._properties.ssl ? 'PLAIN' : this._properties.endpoints[index].auth); | ||
} | ||
@@ -286,7 +296,7 @@ | ||
* | ||
* @param {string} schema - Name of the schema (database) | ||
* @param {string} name - Name of the schema (database) | ||
* @returns {Schema} | ||
*/ | ||
Session.prototype.getSchema = function (schema) { | ||
return new Schema(this, schema); | ||
Session.prototype.getSchema = function (name) { | ||
return schema(this, name); | ||
}; | ||
@@ -391,3 +401,3 @@ | ||
Session.prototype.inspect = function (depth) { | ||
const available = ['dbUser', 'host', 'port', 'socket', 'ssl']; | ||
const available = ['auth', 'dbUser', 'host', 'port', 'socket', 'ssl']; | ||
@@ -394,0 +404,0 @@ return Object.keys(this._properties).reduce((result, key) => { |
@@ -34,2 +34,3 @@ /* | ||
* Build a raw query wrapper. | ||
* @constructor | ||
*/ | ||
@@ -36,0 +37,0 @@ function Statement (client, query, args) { |
@@ -29,184 +29,233 @@ /* | ||
const DatabaseObject = require('./DatabaseObject'); | ||
const LinkOperations = require('./LinkOperations'); | ||
const TableDelete = require('./TableDelete'); | ||
const TableInsert = require('./TableInsert'); | ||
const TableSelect = require('./TableSelect'); | ||
const TableUpdate = require('./TableUpdate'); | ||
const databaseObject = require('./DatabaseObject'); | ||
const linking = require('./Linking'); | ||
const parseFlexibleParamList = require('./Util/parseFlexibleParamList'); | ||
const util = require('util'); | ||
const tableDelete = require('./TableDelete'); | ||
const tableInsert = require('./TableInsert'); | ||
const tableSelect = require('./TableSelect'); | ||
const tableUpdate = require('./TableUpdate'); | ||
/** | ||
* Table object | ||
* | ||
* Usually you shouldn't create an instance of this but ask a Schema for it | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {String} table | ||
* @param {String} alias | ||
* @param {object} links | ||
* @constructor | ||
* @extends DatabaseObject | ||
* Table factory. | ||
* @module Table | ||
* @mixes DatabaseObject | ||
* @mixes Linking | ||
*/ | ||
function Table (session, schema, table, alias, links) { | ||
DatabaseObject.call(this, session, schema); | ||
this._table = table; | ||
this._links = links || {}; | ||
this._alias = alias; | ||
} | ||
/** | ||
* @private | ||
* @alias module:Table | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} name - table name | ||
* @param {string} alias - table alias | ||
* @param {Object} links - table join links | ||
* @returns {Table} | ||
*/ | ||
function Table (session, schema, name, alias, links) { | ||
const state = Object.assign({}, { links: {} }, { alias, links, name, schema }); | ||
module.exports = Table; | ||
return Object.assign({}, databaseObject(session), linking(), { | ||
/** | ||
* Set an alias for link operation using the table. | ||
* @function | ||
* @name module:Table#as | ||
* @param {string} alias - new table alias | ||
* @returns {Table} The entity instance. | ||
*/ | ||
as (alias) { | ||
return Table(this.getSession(), this.getSchema(), this.getName(), alias, this.getLinks()); | ||
}, | ||
util.inherits(Table, DatabaseObject); | ||
LinkOperations.applyTo(Table); | ||
/** | ||
* Retrieve the total number of rows in the table. | ||
* @function | ||
* @name module:Table#count | ||
* @returns {Promise.<number>} | ||
*/ | ||
// TODO(Rui): extract method into a proper aspect (to be used on Collection and Table). | ||
count () { | ||
const schema = Table.escapeIdentifier(this.getSchema().getName()); | ||
const table = Table.escapeIdentifier(this.getName()); | ||
Table.escapeIdentifier = function (ident) { | ||
return '`' + (ident || '').replace('`', '``') + '`'; | ||
}; | ||
let count = 0; | ||
/** | ||
* Get the name of this table | ||
* @returns {string} | ||
*/ | ||
Table.prototype.getName = function () { | ||
return this._table; | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute(`SELECT COUNT(*) FROM ${schema}.${table}`, [], row => { count = row[0]; }) | ||
.then(() => count); | ||
}, | ||
/** | ||
* Verifies this table exists | ||
* @returns {Promise<boolean>} | ||
*/ | ||
Table.prototype.existsInDatabase = function () { | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.TABLES WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
const args = ['def', this._schema.getName(), this._table]; | ||
let status = false; | ||
/** | ||
* Create operation to delete rows from a table. | ||
* @function | ||
* @name module:Table#delete | ||
* @param {SearchConditionStr} [expr] - filtering criteria | ||
* @example | ||
* // delete all rows from a table | ||
* table.delete('true') | ||
* | ||
* // delete rows that match a given criteria | ||
* table.delete('`name` == "foobar"') | ||
* @returns {TableDelete} The operation instance. | ||
*/ | ||
delete (expr) { | ||
return tableDelete(this.getSession(), this.getSchema(), this.getName(), expr); | ||
}, | ||
return this._session._client | ||
.sqlStmtExecute(query, args, (found) => { status = !!found.length; }) | ||
.then(() => status); | ||
}; | ||
/** | ||
* Check if the table exists in the database. | ||
* @function | ||
* @name module:Table#existsInDatabase | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
// TODO(Rui): extract method into a proper aspect (to be used on Collection, Schema and Table). | ||
existsInDatabase () { | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.TABLES WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
const args = ['def', this.getSchema().getName(), this.getName()]; | ||
let status = false; | ||
/** | ||
* Checks whether this table is a View | ||
* @returns {Promise<boolean>} | ||
*/ | ||
Table.prototype.isView = function () { | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.VIEWS WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
const args = ['def', this._schema.getName(), this._table]; | ||
let status = false; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute(query, args, (found) => { status = !!found.length; }) | ||
.then(() => status); | ||
}, | ||
/** | ||
* Retrieve the table name. | ||
* @function | ||
* @name module:Table#getName | ||
* @returns {string} | ||
*/ | ||
getName () { | ||
return state.name; | ||
}, | ||
return this._session._client | ||
.sqlStmtExecute(query, args, (found) => { status = !!found.length; }) | ||
.then(() => status); | ||
}; | ||
/** | ||
* Retrieve the schema associated to the table. | ||
* @function | ||
* @name module:Table#getSchema | ||
* @returns {Schema} | ||
*/ | ||
getSchema () { | ||
return state.schema; | ||
}, | ||
/** | ||
* Select rows from a table | ||
* @param {...string|string[]} [ProjectedSearchExpr] - columns to be projected | ||
* @throws {Error} When an expression is invalid. | ||
* @example | ||
* // all columns should be projected | ||
* const selection = table.select() | ||
* | ||
* // arguments as columns to be projected | ||
* const selection = table.select('foo', 'bar') | ||
* | ||
* // array of columns to be projected | ||
* const selection = table.select(['foo', 'bar']) | ||
* @returns {TableSelect} | ||
*/ | ||
Table.prototype.select = function () { | ||
const fields = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
/** | ||
* Retrieve the table metadata. | ||
* @function | ||
* @name module:Table#inspect | ||
* @returns {Object} An object containing the relevant metadata. | ||
*/ | ||
inspect () { | ||
return { schema: this.getSchema().getName(), table: this.getName() }; | ||
}, | ||
return new TableSelect(this._session, this._schema, this._table, fields); | ||
}; | ||
/** | ||
* Create operation to insert rows in the table. | ||
* @function | ||
* @name module:Table#insert | ||
* @param {...string|string[]|Object} fields - column names or column-value object | ||
* @throws {Error} When the input type is invalid. | ||
* @example | ||
* // arguments as column names | ||
* table.insert('foo', 'bar') | ||
* | ||
* // array of column names | ||
* table.insert(['foo', 'bar']) | ||
* | ||
* // object with column name and value | ||
* table.insert({ foo: 'baz', bar: 'qux' }) | ||
* @returns {TableInsert} The operation instance. | ||
*/ | ||
insert () { | ||
if (!Array.isArray(arguments[0]) && typeof arguments[0] !== 'string') { | ||
const fields = arguments[0]; | ||
/** | ||
* Insert rows | ||
* @param {...string|string[]|Object} TableField - column names or column-value object | ||
* @throws {Error} When the input type is invalid. | ||
* @example | ||
* // arguments as column names | ||
* table.insert('foo', 'bar') | ||
* | ||
* // array of column names | ||
* table.insert(['foo', 'bar']) | ||
* | ||
* // object with column name and value | ||
* table.insert({ foo: 'baz', bar: 'qux' }) | ||
* @returns {TableInsert} | ||
*/ | ||
Table.prototype.insert = function () { | ||
if (!Array.isArray(arguments[0]) && typeof arguments[0] !== 'string') { | ||
const fields = arguments[0]; | ||
if (typeof fields !== 'object') { | ||
throw new Error('fields must be provided as multiple Strings, an Array or an Object with the column name and value'); | ||
} | ||
if (typeof fields !== 'object') { | ||
throw new Error('fields must be provided as multiple Strings, an Array or an Object with the column name and value'); | ||
} | ||
const columns = Object.keys(fields); | ||
const values = columns.map(column => fields[column]); | ||
const columns = Object.keys(fields); | ||
const values = columns.map(column => fields[column]); | ||
return tableInsert(this.getSession(), this.getSchema(), this.getName(), columns).values(values); | ||
} | ||
return (new TableInsert(this._session, this._schema, this._table, columns)).values(values); | ||
} | ||
const columns = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
const columns = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
return tableInsert(this.getSession(), this.getSchema(), this.getName(), columns); | ||
}, | ||
return new TableInsert(this._session, this._schema, this._table, columns); | ||
}; | ||
/** | ||
* Check whether the table is a view. | ||
* @function | ||
* @name module:Table#isView | ||
* @returns {Promise.<boolean>} | ||
*/ | ||
isView () { | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.VIEWS WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
const args = ['def', this.getSchema().getName(), this.getName()]; | ||
let status = false; | ||
/** | ||
* Run update operations | ||
* @param {String} expr Expression | ||
* @returns {TableUpdate} | ||
*/ | ||
Table.prototype.update = function (expr) { | ||
return new TableUpdate(this._session, this._schema, this._table, expr); | ||
}; | ||
return this | ||
.getSession() | ||
._client | ||
.sqlStmtExecute(query, args, (found) => { status = !!found.length; }) | ||
.then(() => status); | ||
}, | ||
/** | ||
* Create operation to delete rows from a table. | ||
* @param {string} expr Expression | ||
* @example | ||
* // delete all rows from a table | ||
* table.delete('true') | ||
* | ||
* // delete rows that match a given criteria | ||
* table.delete('`name` == "foobar"') | ||
* @returns {TableDelete} | ||
*/ | ||
Table.prototype.delete = function (expr) { | ||
return new TableDelete(this._session, this._schema, this._table, expr); | ||
}; | ||
/** | ||
* Create operation to select rows from the table. | ||
* @function | ||
* @name module:Table#select | ||
* @param {...string|string[]} [expr] - columns to be projected | ||
* @throws {Error} When an expression is invalid. | ||
* @example | ||
* // all columns should be projected | ||
* const selection = table.select() | ||
* | ||
* // arguments as columns to be projected | ||
* const selection = table.select('foo', 'bar') | ||
* | ||
* // array of columns to be projected | ||
* const selection = table.select(['foo', 'bar']) | ||
* @returns {TableSelect} The operation instance. | ||
*/ | ||
select () { | ||
const fields = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
/** | ||
* Get number of rows in this Table | ||
* | ||
* @returns {Promise.<Number>} | ||
*/ | ||
Table.prototype.count = function () { | ||
const schema = Table.escapeIdentifier(this._schema.getName()); | ||
const table = Table.escapeIdentifier(this._table); | ||
return tableSelect(this.getSession(), this.getSchema(), this.getName(), fields); | ||
}, | ||
let count = 0; | ||
/** | ||
* Create operation to update rows in the table. | ||
* @function | ||
* @name module:Table#update | ||
* @param {string} [expr] - filtering criteria | ||
* @example | ||
* // update all rows in a table | ||
* table.update('true').set('name', 'foo') | ||
* table.update().where('true').set('name', 'foo') | ||
* | ||
* // update rows that match a given criteria | ||
* table.update().where('`name` == "foo"').set('name', 'bar') | ||
* @returns {TableUpdate} The operation instance. | ||
*/ | ||
update (expr) { | ||
return tableUpdate(this.getSession(), this.getSchema(), this.getName(), expr); | ||
} | ||
}); | ||
} | ||
return this._session._client | ||
.sqlStmtExecute(`SELECT COUNT(*) FROM ${schema}.${table}`, [], row => { count = row[0]; }) | ||
.then(() => count); | ||
}; | ||
/** | ||
* Set an alias for link operation | ||
* | ||
* @param {String} alias | ||
* @returns {Table} | ||
* Internal utility function. | ||
*/ | ||
Table.prototype.as = function (alias) { | ||
return new Table(this._session, this._schema, this._table, alias, this._links); | ||
// TODO(Rui): refactor somehow. | ||
Table.escapeIdentifier = function (ident) { | ||
return '`' + (ident || '').replace('`', '``') + '`'; | ||
}; | ||
Table.prototype.inspect = function () { | ||
return { schema: this._schema.getName(), table: this._table }; | ||
}; | ||
module.exports = Table; |
@@ -29,61 +29,68 @@ /* | ||
const BaseQuery = require('./BaseQuery'); | ||
const Client = require('../Protocol/Client'); | ||
const Result = require('./Result'); | ||
const util = require('util'); | ||
const binding = require('./Binding'); | ||
const filtering = require('./Filtering'); | ||
const limiting = require('./Limiting'); | ||
/** | ||
* Operation to delete rows from a table. | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Table} table | ||
* @param {String} expression | ||
* @constructor | ||
* TableDelete factory. | ||
* @module TableDelete | ||
* @mixes Filtering | ||
* @mixes Limiting | ||
*/ | ||
function TableDelete (session, schema, table, query) { | ||
BaseQuery.call(this); | ||
this._session = session; | ||
this._schema = schema; | ||
this._table = table; | ||
this._query = query; | ||
this._bounds = {}; | ||
} | ||
util.inherits(TableDelete, BaseQuery); | ||
module.exports = TableDelete; | ||
/** | ||
* Bind statement query parameter values. | ||
* @returns {TableDelete} The operation instance. | ||
* @private | ||
* @alias module:TableDelete | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} table - table name | ||
* @param {string} [criteria] - filtering criteria expression | ||
* @returns {TableDelete} | ||
*/ | ||
TableDelete.prototype.bind = function (bind) { | ||
this._bounds = Object.assign({}, this._bounds, bind); | ||
function TableDelete (session, schema, table, criteria) { | ||
let state = Object.assign({ }, { session, schema, table }); | ||
return this; | ||
}; | ||
return Object.assign({}, filtering({ criteria }), binding(), limiting(), { | ||
/** | ||
* Execute delete operation. | ||
* @function | ||
* @name module:TableDelete#execute | ||
* @return {Promise.<Result>} | ||
*/ | ||
execute () { | ||
if (typeof this.getCriteria() !== 'string' || this.getCriteria().trim().length < 1) { | ||
return Promise.reject(new Error('delete needs a valid condition')); | ||
} | ||
/** | ||
* Set a condition for picking which rows to delete. | ||
* @param {string} query - filtering condition | ||
* @returns {TableDelete} The operation instance. | ||
*/ | ||
TableDelete.prototype.where = function (query) { | ||
this._query = query; | ||
return state | ||
.session | ||
._client | ||
.crudRemove(state.schema.getName(), state.table, Client.dataModel.TABLE, this.getCriteria(), this.getLimit(), this.getBindings()).then(state => new Result(state)); | ||
}, | ||
return this; | ||
}; | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:TableDelete#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'TableDelete'; | ||
}, | ||
/** | ||
* Execute delete operation. | ||
* @return {Promise.<Result>} | ||
*/ | ||
TableDelete.prototype.execute = function () { | ||
if (typeof this._query !== 'string' || this._query.trim().length < 1) { | ||
return Promise.reject(new Error('delete needs a valid condition')); | ||
} | ||
/** | ||
* Add <code>WHERE</code> clause (set the criteria for picking which rows to delete). | ||
* @function | ||
* @name module:TableDelete#where | ||
* @param {string} criteria - filtering criteria | ||
* @returns {TableDelete} The operation instance. | ||
*/ | ||
where (criteria) { | ||
return this.setCriteria(criteria); | ||
} | ||
}); | ||
} | ||
return this._session._client | ||
.crudRemove(this._schema.getName(), this._table, Client.dataModel.TABLE, this._query, this._limit, this._bounds).then(state => new Result(state)); | ||
}; | ||
module.exports = TableDelete; |
/* | ||
* Copyright (c) 2015, 2016, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* | ||
@@ -27,90 +27,115 @@ * MySQL Connector/Node.js is licensed under the terms of the GPLv2 | ||
"use strict"; | ||
'use strict'; | ||
const Client = require('../Protocol/Client'), | ||
Table = require('./Table'), | ||
Result = require('./Result'); | ||
const Client = require('../Protocol/Client'); | ||
const Result = require('./Result'); | ||
const parseFlexibleParamList = require('./Util/parseFlexibleParamList'); | ||
/** | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {String} table name | ||
* @param {Array<string>} fields | ||
* @constructor | ||
* TableInsert factory. | ||
* @module TableInsert | ||
* @mixes Filtering | ||
* @mixes Limiting | ||
*/ | ||
function TableInsert(session, schema, table, fields) { | ||
this._session = session; | ||
this._schema = schema; | ||
this._table = table; | ||
this._fields = fields; | ||
this._rows = []; | ||
} | ||
module.exports = TableInsert; | ||
/** | ||
* Set values for a row | ||
* | ||
* Individual values have to be provided as arguments or in form of a single array | ||
* | ||
* Both ways are shown below: | ||
* | ||
* <pre>table. | ||
* insert(["col1", "col2"]). | ||
* values("row 1, value 1", "row 1 value 2"). | ||
* values(["row 2, value 1", "row 2, value 2"]). | ||
* execute();</pre> | ||
* | ||
* @private | ||
* @alias module:TableInsert | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} table - table name | ||
* @param {Array.<string>} fields - projection | ||
* @returns {TableInsert} | ||
*/ | ||
/** | ||
* Insert rows | ||
* @param {...string|string[]} ExprOrLiteral - column values | ||
* @throws {Error} When there is a mismatch with the number columns in the query. | ||
* @example | ||
* // arguments as column values | ||
* table.insert('foo', 'bar').values('baz', 'qux') | ||
* table.insert(['foo', 'bar']).values('baz', 'qux') | ||
* | ||
* // array of column values | ||
* table.insert('foo', 'bar').values(['baz', 'qux']) | ||
* table.insert(['foo', 'bar']).values(['baz', 'qux']) | ||
* | ||
* // comma-separated string with column values | ||
* table.insert('foo', 'bar').values('baz, qux']) | ||
* table.insert(['foo', 'bar']).values('baz, qux') | ||
* | ||
* // chaining multiple inserts | ||
* table.insert('foo', 'bar') | ||
* .values(['baz', 'qux']) | ||
* .values(['quux', 'biz']) | ||
* .values('foo, bar') | ||
* @returns {TableInsert} | ||
*/ | ||
TableInsert.prototype.values = function () { | ||
const values = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
function TableInsert (session, schema, table, fields) { | ||
let state = Object.assign({ rows: [] }, { session, schema, table, fields }); | ||
if (this._fields.length !== values.length) { | ||
throw new Error(`Mismatch in column count. ${this._fields.length} fields listed, ${values.length} values provided`); | ||
} | ||
return { | ||
/** | ||
* Execute the insert operation. | ||
* @function | ||
* @name module:TableInsert#execute | ||
* @returns {Promise.<Result>} | ||
*/ | ||
execute () { | ||
const columns = state.fields.map(field => ({ name: field })); | ||
const rows = state.rows; | ||
// FIXME(ruiquelhas): this is creating a nested array, which is probably unnecessary. | ||
this._rows.push(values); | ||
return this; | ||
}; | ||
return state | ||
.session | ||
._client | ||
.crudInsert(state.schema.getName(), state.table, Client.dataModel.TABLE, { columns, rows }) | ||
.then(state => new Result(state)); | ||
}, | ||
/** | ||
* | ||
* @returns {Promise.<Result>} | ||
*/ | ||
TableInsert.prototype.execute = function () { | ||
// FIXME(ruiquelhas): shouldn't this be a concern of `crudInsert` itself? | ||
var projection = this._fields.map(function (field) { | ||
return { name: field }; | ||
}); | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:TableInsert#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'TableInsert'; | ||
}, | ||
return this._session._client.crudInsert(this._schema.getName(), this._table, Client.dataModel.TABLE, this._rows, projection).then(state => new Result(state)); | ||
}; | ||
/** | ||
* Retrieve the set of columns. | ||
* @function | ||
* @name module:TableInsert#getFields | ||
* @returns {Array.<string>} | ||
*/ | ||
getFields () { | ||
return state.fields; | ||
}, | ||
/** | ||
* Retrieve the set of row values. | ||
* @function | ||
* @name module:TableInsert#getRows | ||
* @returns {Promise.<Result>} | ||
*/ | ||
getRows () { | ||
return state.rows; | ||
}, | ||
/** | ||
* Set row values. | ||
* @function | ||
* @name module:TableInsert:values | ||
* @param {...string|string[]} ExprOrLiteral - column values | ||
* @throws {Error} When there is a mismatch with the number columns in the query. | ||
* @example | ||
* // arguments as column values | ||
* table.insert('foo', 'bar').values('baz', 'qux') | ||
* table.insert(['foo', 'bar']).values('baz', 'qux') | ||
* | ||
* // array of column values | ||
* table.insert('foo', 'bar').values(['baz', 'qux']) | ||
* table.insert(['foo', 'bar']).values(['baz', 'qux']) | ||
* | ||
* // comma-separated string with column values | ||
* table.insert('foo', 'bar').values('baz, qux']) | ||
* table.insert(['foo', 'bar']).values('baz, qux') | ||
* | ||
* // chaining multiple inserts | ||
* table.insert('foo', 'bar') | ||
* .values(['baz', 'qux']) | ||
* .values(['quux', 'biz']) | ||
* .values('foo, bar') | ||
* @returns {TableInsert} The operation instance | ||
*/ | ||
values () { | ||
const values = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
if (state.fields.length !== values.length) { | ||
throw new Error(`Mismatch in column count. ${state.fields.length} fields listed, ${values.length} values provided`); | ||
} | ||
state.rows.push(values); | ||
return this; | ||
} | ||
}; | ||
} | ||
module.exports = TableInsert; |
/* | ||
* Copyright (c) 2015, 2016, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. | ||
* | ||
@@ -29,125 +29,179 @@ * MySQL Connector/Node.js is licensed under the terms of the GPLv2 | ||
const BaseQuery = require('./BaseQuery'); | ||
const Client = require('../Protocol/Client'); | ||
const Column = require('./Column'); | ||
const Result = require('./Result'); | ||
const binding = require('./Binding'); | ||
const filtering = require('./Filtering'); | ||
const limiting = require('./Limiting'); | ||
const locking = require('./Locking'); | ||
const parseExpressionInputs = require('./Util/parseExpressionInputs'); | ||
const parseFlexibleParamList = require('./Util/parseFlexibleParamList'); | ||
const util = require('util'); | ||
/** | ||
* | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Table} table | ||
* @param {Array.<String>} projection | ||
* @constructor | ||
* TableSelect factory. | ||
* @module TableSelect | ||
* @mixes Filtering | ||
* @mixes Binding | ||
* @mixes Limiting | ||
* @mixes Locking | ||
*/ | ||
/** | ||
* @private | ||
* @alias module:TableSelect | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} table - table name | ||
* @param {Array.<string>} projection - projection expressions | ||
* @returns {TableSelect} | ||
*/ | ||
function TableSelect (session, schema, table, projection) { | ||
BaseQuery.call(this); | ||
let state = Object.assign({ joins: [] }, { session, schema, table, projection }); | ||
this._session = session; | ||
this._schema = schema; | ||
this._table = table; | ||
this._projection = projection; | ||
this._where = null; | ||
this._groupby = null; | ||
this._having = null; | ||
this._orderby = null; | ||
this._bounds = {}; | ||
this._joins = []; | ||
} | ||
return Object.assign({}, filtering(), binding(), limiting(), locking(), { | ||
/** | ||
* Execute find operation. | ||
* @function | ||
* @name module:TableSelect#execute | ||
* @param {documentCallback} [rowcb] | ||
* @param {Array<Column>} [metacb] | ||
* @return {Promise.<Result>} | ||
*/ | ||
execute (rowcb, metacb) { | ||
return state | ||
.session | ||
._client | ||
.crudFind(state.session, state.schema.getName(), state.table, Client.dataModel.TABLE, parseExpressionInputs(state.projection), this.getCriteria(), state.groupby, state.having, state.orderby, this.getLimit(), rowcb, Column.metaCB(metacb), this.getBindings(), state.joins, this.getLockingMode()) | ||
.then(state => new Result(state)); | ||
}, | ||
util.inherits(TableSelect, BaseQuery); | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:TableSelect#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'TableSelect'; | ||
}, | ||
module.exports = TableSelect; | ||
/** | ||
* Retrieve the grouping options. | ||
* @function | ||
* @name module:TableSelect#getGroupBy | ||
* @returns {object} An object with the grouping options. | ||
*/ | ||
getGroupBy () { | ||
return state.groupby; | ||
}, | ||
/** | ||
* Add where clause | ||
* @param expr | ||
* @returns {TableSelect} | ||
*/ | ||
TableSelect.prototype.where = function (expr) { | ||
this._where = expr; | ||
return this; | ||
}; | ||
/** | ||
* Retrieve the sorting options. | ||
* @function | ||
* @name module:TableSelect#getOrderBy | ||
* @returns {object} An object with the sorting options. | ||
*/ | ||
getOrderBy () { | ||
return state.orderby; | ||
}, | ||
/** | ||
* Add <code>GROUP BY</code> clause. | ||
* @param {...string|string[]} [GroupByExprStr] - columns to group by | ||
* @throws {Error} When an expression is invalid. | ||
* @example | ||
* // arguments as columns group by | ||
* const query = table.select('foo', 'bar').groupBy('foo asc', 'bar desc') | ||
* | ||
* // array of columns to group by | ||
* const query = table.select('foo', 'bar').groupBy(['foo asc', 'bar desc']) | ||
* @returns {TableSelect} | ||
*/ | ||
TableSelect.prototype.groupBy = function () { | ||
this._groupby = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
return this; | ||
}; | ||
/** | ||
* Retrieve the projection map. | ||
* @function | ||
* @name module:TableSelect#getProjection | ||
* @returns {object} An object containing the project map. | ||
*/ | ||
getProjection () { | ||
return state.projection; | ||
}, | ||
/** | ||
* Add having clause | ||
* @param expr | ||
* @returns {TableSelect} | ||
*/ | ||
TableSelect.prototype.having = function (expr) { | ||
this._having = expr; | ||
return this; | ||
}; | ||
/** | ||
* Build a view for the operation. | ||
* @function | ||
* @name module:TableSelect#getViewDefinition | ||
* @returns {string} The view SQL string. | ||
*/ | ||
getViewDefinition () { | ||
// TODO(rui.quelhas): check if this is the best place to escape the interpolated properties | ||
// console.log(Table, new Table(), Table.escapeIdentifier); | ||
// let retval = "SELECT " + state.projection.join(", ") + " FROM " + Table.escapeIdentifier(state.schema.getName()) + '.' + Table.escapeIdentifier(state.table); | ||
let view = `SELECT ${state.projection.join(', ')} FROM ${state.schema.getName()}.${state.table}`; | ||
/** | ||
* Add <code>ORDER BY</code> clause. | ||
* @param {...string|string[]} [SortExprStr] - columns (and direction) to sort | ||
* @throws {Error} When an expression is invalid. | ||
* @example | ||
* // arguments as columns (and direction) to sort | ||
* const query = table.select('foo', 'bar').orderBy('foo asc', 'bar desc') | ||
* | ||
* // array of columns (and direction) to sort | ||
* const query = table.select('foo', 'bar').orderBy(['foo asc', 'bar desc']) | ||
* @returns {TableSelect} | ||
*/ | ||
TableSelect.prototype.orderBy = function () { | ||
this._orderby = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
return this; | ||
}; | ||
if (this.getCriteria()) { | ||
view = `${view} WHERE ${this.getCriteria()}`; | ||
} | ||
TableSelect.prototype.bind = function (bind) { | ||
Object.assign(this._bounds, bind); | ||
return this; | ||
}; | ||
if (state.orderby) { | ||
view = `${view} ORDER BY ${state.orderby.join(', ')}`; | ||
} | ||
/** | ||
* Execute find operation | ||
* @param {documentCallback} [rowcb] | ||
* @param {Array<Column>} [metacb] | ||
* @return {Promise.<Result>} | ||
*/ | ||
TableSelect.prototype.execute = function (rowcb, metacb) { | ||
return this | ||
._session | ||
._client | ||
.crudFind(this._session, this._schema.getName(), this._table, Client.dataModel.TABLE, parseExpressionInputs(this._projection), this._where, this._groupby, this._having, this._orderby, this._limit, rowcb, Column.metaCB(metacb), this._bounds, this._joins) | ||
.then(state => new Result(state)); | ||
}; | ||
return view; | ||
}, | ||
TableSelect.prototype.getViewDefinition = function () { | ||
// TODO(rui.quelhas): check if this is the best place to escape the interpolated properties | ||
// console.log(Table, new Table(), Table.escapeIdentifier); | ||
// let retval = "SELECT " + this._projection.join(", ") + " FROM " + Table.escapeIdentifier(this._schema.getName()) + '.' + Table.escapeIdentifier(this._table); | ||
let view = `SELECT ${this._projection.join(', ')} FROM ${this._schema.getName()}.${this._table}`; | ||
/** | ||
* Add <code>GROUP BY</code> clause (set the grouping options of the result set). | ||
* @function | ||
* @name module:TableSelect#groupBy | ||
* @param {...string|string[]} [GroupByExprStr] - columns to group by | ||
* @throws {Error} When an expression is invalid. | ||
* @example | ||
* // arguments as columns group by | ||
* const query = table.select('foo', 'bar').groupBy('foo asc', 'bar desc') | ||
* | ||
* // array of columns to group by | ||
* const query = table.select('foo', 'bar').groupBy(['foo asc', 'bar desc']) | ||
* @returns {TableSelect} The operation instance | ||
*/ | ||
groupBy () { | ||
state.groupby = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
if (this._where) { | ||
view = `${view} WHERE ${this._where}`; | ||
} | ||
return this; | ||
}, | ||
if (this._orderby) { | ||
view = `${view} ORDER BY ${this._orderby.join(', ')}`; | ||
} | ||
/** | ||
* Add <code>HAVING</code> clause. | ||
* @function | ||
* @name module:TableSelect#having | ||
* @param {SearchConditionStr} expr - filtering criteria | ||
* @returns {TableSelect} The operation instance | ||
*/ | ||
having (expr) { | ||
state.having = expr; | ||
return view; | ||
}; | ||
return this; | ||
}, | ||
/** | ||
* Add <code>ORDER BY</code> clause (set the order options of the result set). | ||
* @function | ||
* @name module:TableSelect#orderBy | ||
* @param {...string|string[]} [SortExprStr] - columns (and direction) to sort | ||
* @throws {Error} When an expression is invalid. | ||
* @example | ||
* // arguments as columns (and direction) to sort | ||
* const query = table.select('foo', 'bar').orderBy('foo asc', 'bar desc') | ||
* | ||
* // array of columns (and direction) to sort | ||
* const query = table.select('foo', 'bar').orderBy(['foo asc', 'bar desc']) | ||
* @returns {TableSelect} The operation instance | ||
*/ | ||
orderBy () { | ||
state.orderby = parseFlexibleParamList(Array.prototype.slice.call(arguments)); | ||
return this; | ||
}, | ||
/** | ||
* Add <code>WHERE</code> clause (set the criteria for which rows to pick). | ||
* @function | ||
* @name module:TableSelect#where | ||
* @param {SearchConditionStr} criteria - filtering criteria | ||
* @returns {TableSelect} The operation instance | ||
*/ | ||
where (criteria) { | ||
return this.setCriteria(criteria); | ||
} | ||
}); | ||
} | ||
module.exports = TableSelect; |
@@ -29,83 +29,91 @@ /* | ||
const BaseQuery = require('./BaseQuery'); | ||
const Client = require('../Protocol/Client'); | ||
const Expressions = require('../Expressions'); | ||
const Result = require('./Result'); | ||
const util = require('util'); | ||
const binding = require('./Binding'); | ||
const filtering = require('./Filtering'); | ||
const limiting = require('./Limiting'); | ||
/** | ||
* Operation to update rows in a table. | ||
* @param {Session} session | ||
* @param {Schema} schema | ||
* @param {Table} table | ||
* @constructor | ||
* TableUpdate factory. | ||
* @module TableUpdate | ||
* @mixes Filtering | ||
* @mixes Binding | ||
* @mixes Limiting | ||
*/ | ||
function TableUpdate (session, schema, table, query) { | ||
BaseQuery.call(this); | ||
this._session = session; | ||
this._schema = schema; | ||
this._table = table; | ||
this._operations = []; | ||
this._query = query; | ||
this._bounds = {}; | ||
} | ||
util.inherits(TableUpdate, BaseQuery); | ||
module.exports = TableUpdate; | ||
/** | ||
* Bind statement query parameter values. | ||
* @returns {TableDelete} The operation instance. | ||
* @private | ||
* @alias module:TableUpdate | ||
* @param {Session} session - session to bind | ||
* @param {Schema} schema - associated schema | ||
* @param {string} table - table name | ||
* @param {string} criteria - criteria expression | ||
* @returns {TableUpdate} | ||
*/ | ||
TableUpdate.prototype.bind = function (bind) { | ||
this._bounds = Object.assign({}, this._bounds, bind); | ||
function TableUpdate (session, schema, table, criteria) { | ||
let state = Object.assign({ operations: [] }, { session, schema, table }); | ||
return this; | ||
}; | ||
return Object.assign({}, filtering({ criteria }), binding(), limiting(), { | ||
/** | ||
* Execute update operation. | ||
* @function | ||
* @name module:TableUpdate#execute | ||
* @return {Promise.<Result>} | ||
*/ | ||
execute () { | ||
if (typeof this.getCriteria() !== 'string' || this.getCriteria().trim().length < 1) { | ||
return Promise.reject(new Error('update needs a valid condition')); | ||
} | ||
/** | ||
* Add a field to be updated | ||
* @param {string} field Name of the field | ||
* @param {string} expr Expression | ||
* @returns {TableUpdate} Returns itself | ||
*/ | ||
TableUpdate.prototype.set = function (field, expr) { | ||
this._operations.push({ | ||
operation: 1, | ||
source: { | ||
name: field | ||
return state | ||
.session | ||
._client | ||
.crudModify(state.schema.getName(), state.table, Client.dataModel.TABLE, this.getCriteria(), state.operations, this.getLimit(), this.getBindings()) | ||
.then(state => new Result(state)); | ||
}, | ||
value: Expressions.literalOrParsedExpression(expr) | ||
}); | ||
return this; | ||
}; | ||
/** | ||
* Retrieve the class name (to avoid duck typing). | ||
* @function | ||
* @name module:TableUpdate#getClassName | ||
* @returns {string} The "class" name. | ||
*/ | ||
getClassName () { | ||
return 'TableUpdate'; | ||
}, | ||
/** | ||
* Set a condition for picking which rows to delete. | ||
* @param {string} query - filtering condition | ||
* @returns {TableUpdate} The operation instance | ||
*/ | ||
TableUpdate.prototype.where = function (query) { | ||
this._query = query; | ||
/** | ||
* Add a field to be updated with a new value. | ||
* @function | ||
* @name module:TableUpdate#set | ||
* @param {string} field - field name | ||
* @param {string} expr - value expression | ||
* @returns {TableUpdate} The operation instance | ||
*/ | ||
set (field, expr) { | ||
state.operations.push({ | ||
operation: 1, | ||
source: { | ||
name: field | ||
}, | ||
value: Expressions.literalOrParsedExpression(expr) | ||
}); | ||
return this; | ||
}; | ||
return this; | ||
}, | ||
/** | ||
* Execute update operation. | ||
* @return {Promise.<Result>} | ||
*/ | ||
TableUpdate.prototype.execute = function () { | ||
if (typeof this._query !== 'string' || this._query.trim().length < 1) { | ||
return Promise.reject(new Error('update needs a valid condition')); | ||
} | ||
/** | ||
* Add <code>WHERE</code> clause (set the criteria for picking which rows to update). | ||
* @function | ||
* @name module:TableUpdate#where | ||
* @param {string} criteria - filtering condition | ||
* @returns {TableUpdate} The operation instance | ||
*/ | ||
where (criteria) { | ||
return this.setCriteria(criteria); | ||
} | ||
}); | ||
} | ||
return this | ||
._session | ||
._client | ||
.crudModify(this._schema.getName(), this._table, Client.dataModel.TABLE, this._query, this._operations, this._limit, this._bounds) | ||
.then(state => new Result(state)); | ||
}; | ||
module.exports = TableUpdate; |
@@ -30,3 +30,3 @@ /* | ||
module.exports = function (inputs) { | ||
return inputs.map(function (field) { | ||
return (inputs || []).map(function (field) { | ||
const splitted = field.split(/\sas\s/i); | ||
@@ -48,2 +48,2 @@ | ||
}); | ||
}; | ||
}; |
@@ -30,2 +30,3 @@ /* | ||
const parseAddressList = require('./parseAddressList'); | ||
const parseAuthenticationMechanism = require('./parseAuthenticationMechanism'); | ||
const parseSchema = require('./parseSchema'); | ||
@@ -72,2 +73,3 @@ const parseSecurityOptions = require('./parseSecurityOptions'); | ||
return { | ||
auth: parseAuthenticationMechanism(base[5]), | ||
dbUser: userinfo.username, | ||
@@ -74,0 +76,0 @@ dbPassword: userinfo.password, |
@@ -1,2 +0,2 @@ | ||
/* parser generated by jison 0.4.15 */ | ||
/* parser generated by jison 0.4.17 */ | ||
/* | ||
@@ -75,8 +75,8 @@ Returns a Parser object of the following structure: | ||
var parser = (function(){ | ||
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,22],$V1=[1,14],$V2=[1,15],$V3=[1,6],$V4=[1,7],$V5=[1,23],$V6=[1,8],$V7=[1,12],$V8=[1,20],$V9=[1,18],$Va=[1,24],$Vb=[1,17],$Vc=[1,25],$Vd=[1,28],$Ve=[1,29],$Vf=[1,30],$Vg=[1,31],$Vh=[1,32],$Vi=[1,33],$Vj=[1,34],$Vk=[1,35],$Vl=[1,36],$Vm=[1,37],$Vn=[1,38],$Vo=[1,39],$Vp=[1,40],$Vq=[1,41],$Vr=[1,42],$Vs=[4,21,22,26,27,28,29,30,31,32,33,34,35,36,37,38,39,44,47,53],$Vt=[1,52],$Vu=[1,53],$Vv=[1,62],$Vw=[1,59],$Vx=[1,60],$Vy=[1,61],$Vz=[1,63],$VA=[8,10,11,13,14,15,16,25,42,50,52,58,61],$VB=[26,47],$VC=[58,60],$VD=[4,19,21,22,26,27,28,29,30,31,32,33,34,35,36,37,38,39,42,44,47,53],$VE=[60,61,64,65,66,67],$VF=[4,26,44,47,53]; | ||
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,24],$V1=[1,15],$V2=[1,16],$V3=[1,17],$V4=[1,6],$V5=[1,7],$V6=[1,25],$V7=[1,8],$V8=[1,9],$V9=[1,13],$Va=[1,22],$Vb=[1,20],$Vc=[1,21],$Vd=[1,19],$Ve=[1,26],$Vf=[1,29],$Vg=[1,30],$Vh=[1,31],$Vi=[1,32],$Vj=[1,33],$Vk=[1,34],$Vl=[1,35],$Vm=[1,36],$Vn=[1,37],$Vo=[1,38],$Vp=[1,39],$Vq=[1,40],$Vr=[1,41],$Vs=[1,42],$Vt=[1,43],$Vu=[1,44],$Vv=[4,23,24,25,30,31,32,33,34,35,36,37,38,39,40,41,42,43,48,51,58],$Vw=[1,55],$Vx=[1,56],$Vy=[1,59],$Vz=[1,69],$VA=[1,66],$VB=[1,67],$VC=[1,68],$VD=[1,70],$VE=[1,77],$VF=[8,10,11,12,14,15,16,17,18,29,46,54,56,64,67],$VG=[1,79],$VH=[30,51],$VI=[64,66],$VJ=[4,21,23,24,25,30,31,32,33,34,35,36,37,38,39,40,41,42,43,46,48,51,58],$VK=[51,58],$VL=[1,92],$VM=[2,57],$VN=[66,67,70,71,72,73],$VO=[4,30,48,51,58]; | ||
var parser = {trace: function trace() { }, | ||
yy: {}, | ||
symbols_: {"error":2,"Input":3,"EOF":4,"Expression":5,"StringOrNumber":6,"string":7,"Number":8,"Literal":9,"true":10,"false":11,"FunctionCall":12,"?":13,":":14,"StringLiteral":15,"@":16,"SQLVariable":17,"column":18,".":19,"BinaryOperator":20,"like":21,"not":22,"DocPath":23,"JSONExpression":24,"(":25,")":26,"||":27,"&&":28,"==":29,"!=":30,"+":31,"-":32,"*":33,"/":34,"%":35,"<":36,">":37,"<=":38,">=":39,"Field":40,"@.":41,"[":42,"Index":43,"]":44,"FunctionName":45,"FunctionArgs":46,",":47,"DocPathElement":48,"DocPathElements":49,"$":50,"JSONDocument":51,"{":52,"}":53,"int":54,"DEC":55,"HEX":56,"OCT":57,"`":58,"column_quoted":59,"NON_ESCAPED":60,"QUOTE":61,"string_quoted":62,"string_quoted_char":63,"HEX_ESCAPE":64,"OCT_ESCAPE":65,"CHAR_ESCAPE":66,"NAME":67,"$accept":0,"$end":1}, | ||
terminals_: {2:"error",4:"EOF",8:"Number",10:"true",11:"false",13:"?",14:":",15:"StringLiteral",16:"@",17:"SQLVariable",19:".",21:"like",22:"not",25:"(",26:")",27:"||",28:"&&",29:"==",30:"!=",31:"+",32:"-",33:"*",34:"/",35:"%",36:"<",37:">",38:"<=",39:">=",41:"@.",42:"[",43:"Index",44:"]",47:",",50:"$",52:"{",53:"}",55:"DEC",56:"HEX",57:"OCT",58:"`",60:"NON_ESCAPED",61:"QUOTE",64:"HEX_ESCAPE",65:"OCT_ESCAPE",66:"CHAR_ESCAPE",67:"NAME"}, | ||
productions_: [0,[3,1],[3,2],[6,1],[6,1],[9,1],[9,1],[9,1],[5,1],[5,1],[5,1],[5,2],[5,2],[5,1],[5,3],[5,5],[5,3],[5,3],[5,4],[5,1],[5,1],[5,3],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[40,11],[12,4],[45,1],[45,3],[46,0],[46,1],[46,3],[48,2],[48,3],[48,3],[49,1],[49,2],[23,2],[24,1],[24,5],[51,9],[54,1],[54,1],[54,1],[18,3],[59,1],[59,2],[7,3],[62,1],[62,2],[63,1],[63,1],[63,1],[63,1],[63,1]], | ||
symbols_: {"error":2,"Input":3,"EOF":4,"Expression":5,"StringOrNumber":6,"string":7,"Number":8,"Literal":9,"true":10,"false":11,"null":12,"FunctionCall":13,"?":14,":":15,"StringLiteral":16,"!":17,"@":18,"SQLVariable":19,"column":20,".":21,"BinaryOperator":22,"like":23,"not":24,"in":25,"argsList":26,"DocPath":27,"JSONExpression":28,"(":29,")":30,"||":31,"&&":32,"==":33,"!=":34,"+":35,"-":36,"*":37,"/":38,"%":39,"<":40,">":41,"<=":42,">=":43,"Field":44,"@.":45,"[":46,"Index":47,"]":48,"FunctionName":49,"FunctionArgs":50,",":51,"DocPathElement":52,"DocPathElements":53,"$":54,"Expressions":55,"{":56,"KeyValuePairs":57,"}":58,"KeyValuePair":59,"int":60,"DEC":61,"HEX":62,"OCT":63,"`":64,"column_quoted":65,"NON_ESCAPED":66,"QUOTE":67,"string_quoted":68,"string_quoted_char":69,"HEX_ESCAPE":70,"OCT_ESCAPE":71,"CHAR_ESCAPE":72,"NAME":73,"$accept":0,"$end":1}, | ||
terminals_: {2:"error",4:"EOF",8:"Number",10:"true",11:"false",12:"null",14:"?",15:":",16:"StringLiteral",17:"!",18:"@",19:"SQLVariable",21:".",23:"like",24:"not",25:"in",29:"(",30:")",31:"||",32:"&&",33:"==",34:"!=",35:"+",36:"-",37:"*",38:"/",39:"%",40:"<",41:">",42:"<=",43:">=",45:"@.",46:"[",47:"Index",48:"]",51:",",54:"$",56:"{",58:"}",61:"DEC",62:"HEX",63:"OCT",64:"`",66:"NON_ESCAPED",67:"QUOTE",70:"HEX_ESCAPE",71:"OCT_ESCAPE",72:"CHAR_ESCAPE",73:"NAME"}, | ||
productions_: [0,[3,1],[3,2],[6,1],[6,1],[9,1],[9,1],[9,1],[9,1],[5,1],[5,1],[5,1],[5,2],[5,2],[5,2],[5,1],[5,3],[5,5],[5,3],[5,3],[5,4],[5,3],[5,4],[5,3],[5,4],[5,1],[5,1],[5,3],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[22,1],[44,11],[13,4],[49,1],[49,3],[50,0],[50,1],[50,3],[52,2],[52,3],[52,3],[53,1],[53,2],[27,2],[26,3],[28,3],[28,3],[55,1],[55,3],[57,1],[57,3],[59,3],[60,1],[60,1],[60,1],[20,3],[65,1],[65,2],[7,2],[7,3],[68,1],[68,2],[69,1],[69,1],[69,1],[69,1],[69,1]], | ||
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { | ||
@@ -114,3 +114,6 @@ /* this == yyval */ | ||
break; | ||
case 10: | ||
case 8: | ||
this.$ = { type: 2, literal: Datatype.encodeScalar(null) } | ||
break; | ||
case 11: | ||
@@ -120,3 +123,3 @@ this.$ = parser.addOrdinalPlaceholder(); | ||
break; | ||
case 11: | ||
case 12: | ||
@@ -129,2 +132,13 @@ this.$ = parser.addNamedPlaceholder($$[$0]); | ||
this.$ = { | ||
type: 5, | ||
operator: { | ||
name: $$[$0-1], | ||
param: [ $$[$0] ] | ||
} | ||
} | ||
break; | ||
case 15: | ||
this.$ = { | ||
type: 1, | ||
@@ -137,3 +151,3 @@ identifier: { | ||
break; | ||
case 14: | ||
case 16: | ||
@@ -149,3 +163,3 @@ this.$ = { | ||
break; | ||
case 15: | ||
case 17: | ||
@@ -162,3 +176,3 @@ this.$ = { | ||
break; | ||
case 16: | ||
case 18: | ||
@@ -174,3 +188,3 @@ this.$ = { | ||
break; | ||
case 17: | ||
case 19: | ||
@@ -186,3 +200,3 @@ this.$ = { | ||
break; | ||
case 18: | ||
case 20: | ||
@@ -198,6 +212,50 @@ this.$ = { | ||
break; | ||
case 21: case 57: | ||
case 21: | ||
this.$ = { | ||
type: 5, | ||
operator: { | ||
name: 'in', | ||
param: [ $$[$0-2] ].concat($$[$0]) | ||
} | ||
} | ||
break; | ||
case 22: | ||
this.$ = { | ||
type: 5, | ||
operator: { | ||
name: 'not_in', | ||
param: [ $$[$0-3] ].concat($$[$0]) | ||
} | ||
} | ||
break; | ||
case 23: | ||
this.$ = { | ||
type: 5, | ||
operator: { | ||
name: 'cont_in', | ||
param: [ $$[$0-2], $$[$0] ] | ||
} | ||
} | ||
break; | ||
case 24: | ||
this.$ = { | ||
type: 5, | ||
operator: { | ||
name: 'not_cont_in', | ||
param: [ $$[$0-3], $$[$0] ] | ||
} | ||
} | ||
break; | ||
case 27: case 69: | ||
this.$ = $$[$0-1]; | ||
break; | ||
case 36: | ||
case 42: | ||
@@ -215,30 +273,30 @@ this.$ = { | ||
break; | ||
case 37: | ||
case 43: | ||
this.$ = { name: $$[$0] } | ||
break; | ||
case 38: | ||
case 44: | ||
this.$ = { name: $$[$0-1], schema_name: $$[$0-2] } | ||
break; | ||
case 40: | ||
case 46: case 59: | ||
this.$ = [ $$[$0] ] | ||
break; | ||
case 41: | ||
case 47: case 58: case 60: | ||
this.$ = $$[$0-2]; this.$.push($$[$0]); | ||
break; | ||
case 42: | ||
case 48: | ||
this.$ = { type: 1, value: $$[$0] } | ||
break; | ||
case 43: | ||
case 49: | ||
this.$ = { type: 3, index: parseInt($$[$0-1]) }; | ||
break; | ||
case 44: | ||
case 50: | ||
this.$ = { type: 4 } | ||
break; | ||
case 45: | ||
case 51: case 57: | ||
this.$ = [ $$[$0] ]; | ||
break; | ||
case 46: | ||
case 52: | ||
this.$ = $$[$0-1]; this.$.push($$[$0]); | ||
break; | ||
case 47: | ||
case 53: | ||
@@ -253,12 +311,24 @@ this.$ = { | ||
break; | ||
case 54: | ||
case 54: case 65: | ||
this.$ = $$[$0-1] | ||
break; | ||
case 55: case 58: case 63: case 64: | ||
case 55: | ||
this.$ = { type: 7, object: { fld: $$[$0-1] } } | ||
break; | ||
case 56: | ||
this.$ = { type: 8, array: { value: $$[$0-1] } }; | ||
break; | ||
case 61: | ||
this.$ = { key: $$[$0-2], value: $$[$0] } | ||
break; | ||
case 66: case 70: case 75: case 76: | ||
this.$ = $$[$0]; | ||
break; | ||
case 56: case 59: | ||
case 67: case 71: | ||
this.$ = $$[$0-1] + $$[$0]; | ||
break; | ||
case 60: case 61: case 62: | ||
case 68: | ||
this.$ = ''; | ||
break; | ||
case 72: case 73: case 74: | ||
this.$ = parser.charUnescape($$[$0]); | ||
@@ -268,4 +338,4 @@ break; | ||
}, | ||
table: [{3:1,4:[1,2],5:3,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},{1:[3]},{1:[2,1]},{4:[1,26],20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr},o($Vs,[2,8]),o($Vs,[2,9]),o($Vs,[2,10]),{15:[1,43]},{17:[1,44]},o($Vs,[2,13],{19:[1,45]}),o($Vs,[2,19]),o($Vs,[2,20]),{5:46,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},o($Vs,[2,5]),o($Vs,[2,6]),o($Vs,[2,7]),{25:[1,47]},{59:48,60:[1,49]},{19:$Vt,42:$Vu,48:51,49:50},o($Vs,[2,48]),{5:54,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},o($Vs,[2,3]),o($Vs,[2,4]),{19:[1,55],25:[2,37]},{15:[1,56]},{60:$Vv,62:57,63:58,64:$Vw,65:$Vx,66:$Vy,67:$Vz},{1:[2,2]},{5:64,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},{5:65,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},{21:[1,66]},o($VA,[2,22]),o($VA,[2,23]),o($VA,[2,24]),o($VA,[2,25]),o($VA,[2,26]),o($VA,[2,27]),o($VA,[2,28]),o($VA,[2,29]),o($VA,[2,30]),o($VA,[2,31]),o($VA,[2,32]),o($VA,[2,33]),o($VA,[2,34]),o($Vs,[2,11]),o($Vs,[2,12]),{18:67,58:$Vb},{20:27,21:$Vd,22:$Ve,26:[1,68],27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr},o($VB,[2,39],{9:4,12:5,18:9,23:10,24:11,6:13,45:16,51:19,7:21,46:69,5:70,8:$V0,10:$V1,11:$V2,13:$V3,14:$V4,15:$V5,16:$V6,25:$V7,42:$V8,50:$V9,52:$Va,58:$Vb,61:$Vc}),{58:[1,71],60:[1,72]},o($VC,[2,55]),o($Vs,[2,47],{48:73,19:$Vt,42:$Vu}),o($VD,[2,45]),{15:[1,74]},{8:[1,75],33:[1,76]},{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr,47:[1,77]},{15:[1,78]},{14:[1,79]},{60:$Vv,61:[1,80],63:81,64:$Vw,65:$Vx,66:$Vy,67:$Vz},o($VE,[2,58]),o($VE,[2,60]),o($VE,[2,61]),o($VE,[2,62]),o($VE,[2,63]),o($VE,[2,64]),o($VF,[2,16],{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr}),o($VF,[2,17],{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr}),{5:82,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},o($Vs,[2,14],{19:[1,83]}),o($Vs,[2,21]),{26:[1,84],47:[1,85]},o($VB,[2,40],{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr}),o([4,19,21,22,26,27,28,29,30,31,32,33,34,35,36,37,38,39,44,47,53],[2,54]),o($VC,[2,56]),o($VD,[2,46]),o($VD,[2,42]),{44:[1,86]},{44:[1,87]},{5:88,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},{25:[2,38]},{5:89,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},o($Vs,[2,57]),o($VE,[2,59]),o($VF,[2,18],{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr}),{18:90,58:$Vb},o($Vs,[2,36]),{5:91,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},o($VD,[2,43]),o($VD,[2,44]),{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr,44:[1,92]},{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr,47:[1,93]},o($Vs,[2,15]),o($VB,[2,41],{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr}),o($Vs,[2,49]),{15:[1,94]},{14:[1,95]},{5:96,6:13,7:21,8:$V0,9:4,10:$V1,11:$V2,12:5,13:$V3,14:$V4,15:$V5,16:$V6,18:9,23:10,24:11,25:$V7,42:$V8,45:16,50:$V9,51:19,52:$Va,58:$Vb,61:$Vc},{20:27,21:$Vd,22:$Ve,27:$Vf,28:$Vg,29:$Vh,30:$Vi,31:$Vj,32:$Vk,33:$Vl,34:$Vm,35:$Vn,36:$Vo,37:$Vp,38:$Vq,39:$Vr,53:[1,97]},o($Vs,[2,50])], | ||
defaultActions: {2:[2,1],26:[2,2],78:[2,38]}, | ||
table: [{3:1,4:[1,2],5:3,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},{1:[3]},{1:[2,1]},{4:[1,27],22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu},o($Vv,[2,9]),o($Vv,[2,10]),o($Vv,[2,11]),{16:[1,45]},{5:46,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},{19:[1,47]},o($Vv,[2,15],{21:[1,48]}),o($Vv,[2,25]),o($Vv,[2,26]),{5:49,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},o($Vv,[2,5]),o($Vv,[2,6]),o($Vv,[2,7]),o($Vv,[2,8]),{29:[1,50]},{65:51,66:[1,52]},{21:$Vw,46:$Vx,52:54,53:53},{16:$Vy,57:57,59:58},{5:61,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,55:60,56:$Vc,64:$Vd,67:$Ve},o($Vv,[2,3]),o($Vv,[2,4]),{21:[1,62],29:[2,43]},{66:$Vz,67:[1,63],68:64,69:65,70:$VA,71:$VB,72:$VC,73:$VD},{1:[2,2]},{5:71,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},{5:72,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},{23:[1,73],25:[1,74]},{5:76,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,26:75,27:11,28:12,29:$VE,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},o($VF,[2,28]),o($VF,[2,29]),o($VF,[2,30]),o($VF,[2,31]),o($VF,[2,32]),o($VF,[2,33]),o($VF,[2,34]),o($VF,[2,35]),o($VF,[2,36]),o($VF,[2,37]),o($VF,[2,38]),o($VF,[2,39]),o($VF,[2,40]),o($Vv,[2,12]),o([4,30,31,32,33,34,35,36,37,38,39,48,51,58],[2,13],{22:28,23:$Vf,24:$Vg,25:$Vh,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),o($Vv,[2,14]),{20:78,64:$Vd},{22:28,23:$Vf,24:$Vg,25:$Vh,30:$VG,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu},o($VH,[2,45],{9:4,13:5,20:10,27:11,28:12,6:14,49:18,7:23,50:80,5:81,8:$V0,10:$V1,11:$V2,12:$V3,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,29:$V9,46:$Va,54:$Vb,56:$Vc,64:$Vd,67:$Ve}),{64:[1,82],66:[1,83]},o($VI,[2,66]),o($Vv,[2,53],{52:84,21:$Vw,46:$Vx}),o($VJ,[2,51]),{16:[1,85]},{8:[1,86],37:[1,87]},{51:[1,89],58:[1,88]},o($VK,[2,59]),{15:[1,90]},{48:[1,91],51:$VL},o([48,51],$VM,{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),{16:[1,93]},o($Vv,[2,68]),{66:$Vz,67:[1,94],69:95,70:$VA,71:$VB,72:$VC,73:$VD},o($VN,[2,70]),o($VN,[2,72]),o($VN,[2,73]),o($VN,[2,74]),o($VN,[2,75]),o($VN,[2,76]),o($VO,[2,18],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),o($VO,[2,19],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),{5:96,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},{5:98,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,26:97,27:11,28:12,29:$VE,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},o($Vv,[2,21]),o($VO,[2,23],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),{5:100,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,55:99,56:$Vc,64:$Vd,67:$Ve},o($Vv,[2,16],{21:[1,101]}),o($Vv,[2,27]),{30:[1,102],51:[1,103]},o($VH,[2,46],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),o([4,21,23,24,25,30,31,32,33,34,35,36,37,38,39,40,41,42,43,48,51,58],[2,65]),o($VI,[2,67]),o($VJ,[2,52]),o($VJ,[2,48]),{48:[1,104]},{48:[1,105]},o($Vv,[2,55]),{16:$Vy,59:106},{5:107,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},o($Vv,[2,56]),{5:108,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},{29:[2,44]},o($Vv,[2,69]),o($VN,[2,71]),o($VO,[2,20],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),o($Vv,[2,22]),o($VO,[2,24],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),{30:[1,109],51:$VL},{22:28,23:$Vf,24:$Vg,25:$Vh,30:$VG,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu,51:$VM},{20:110,64:$Vd},o($Vv,[2,42]),{5:111,6:14,7:23,8:$V0,9:4,10:$V1,11:$V2,12:$V3,13:5,14:$V4,15:$V5,16:$V6,17:$V7,18:$V8,20:10,27:11,28:12,29:$V9,46:$Va,49:18,54:$Vb,56:$Vc,64:$Vd,67:$Ve},o($VJ,[2,49]),o($VJ,[2,50]),o($VK,[2,60]),o($VK,[2,61],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),o([30,48,51],[2,58],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu}),o($Vv,[2,54]),o($Vv,[2,17]),o($VH,[2,47],{22:28,23:$Vf,24:$Vg,25:$Vh,31:$Vi,32:$Vj,33:$Vk,34:$Vl,35:$Vm,36:$Vn,37:$Vo,38:$Vp,39:$Vq,40:$Vr,41:$Vs,42:$Vt,43:$Vu})], | ||
defaultActions: {2:[2,1],27:[2,2],93:[2,44]}, | ||
parseError: function parseError(str, hash) { | ||
@@ -275,3 +345,9 @@ if (hash.recoverable) { | ||
} else { | ||
throw new Error(str); | ||
function _parseError (msg, hash) { | ||
this.message = msg; | ||
this.hash = hash; | ||
} | ||
_parseError.prototype = Error; | ||
throw new _parseError(str, hash); | ||
} | ||
@@ -309,3 +385,3 @@ }, | ||
_token_stack: | ||
function lex() { | ||
var lex = function () { | ||
var token; | ||
@@ -317,3 +393,3 @@ token = lexer.lex() || EOF; | ||
return token; | ||
} | ||
}; | ||
var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; | ||
@@ -839,69 +915,83 @@ while (true) { | ||
break; | ||
case 1:return 25; | ||
case 1:return 29; | ||
break; | ||
case 2:return 26; | ||
case 2:return 30; | ||
break; | ||
case 3:return 8; | ||
break; | ||
case 4:return 10; | ||
case 4:return 8; | ||
break; | ||
case 5:return 11; | ||
case 5:return 10; | ||
break; | ||
case 6:return 21; | ||
case 6:return 11; | ||
break; | ||
case 7:return 21; | ||
case 7:return 23; | ||
break; | ||
case 8:return 22; | ||
case 8:return 23; | ||
break; | ||
case 9:return 22; | ||
case 9:return 24; | ||
break; | ||
case 10:return 13; | ||
case 10:return 24; | ||
break; | ||
case 11:return 47; | ||
case 11:return 25; | ||
break; | ||
case 12:return 27; | ||
case 12:return 25; | ||
break; | ||
case 13:return 28; | ||
case 13:return 12; | ||
break; | ||
case 14:return 29; | ||
case 14:return 12; | ||
break; | ||
case 15:return 31; | ||
case 15:return 14; | ||
break; | ||
case 16:return 32; | ||
case 16:return 51; | ||
break; | ||
case 17:return 33; | ||
case 17:return 31; | ||
break; | ||
case 18:return 34; | ||
case 18:return 32; | ||
break; | ||
case 19:return 35; | ||
case 19:return 33; | ||
break; | ||
case 20:return 29; | ||
case 20:return 35; | ||
break; | ||
case 21:return 30; | ||
case 21:return 36; | ||
break; | ||
case 22:return '!'; | ||
case 22:return 37; | ||
break; | ||
case 23:return 50; | ||
case 23:return 38; | ||
break; | ||
case 24:return 19; | ||
case 24:return 39; | ||
break; | ||
case 25:return 42; | ||
case 25:return 33; | ||
break; | ||
case 26:return 44; | ||
case 26:return 34; | ||
break; | ||
case 27:return 13; | ||
case 27:return 17; | ||
break; | ||
case 28:return 14; | ||
case 28:return 54; | ||
break; | ||
case 29:return 36; | ||
case 29:return 21; | ||
break; | ||
case 30:return 37; | ||
case 30:return 46; | ||
break; | ||
case 31:return 38; | ||
case 31:return 48; | ||
break; | ||
case 32:return 39; | ||
case 32:return 56; | ||
break; | ||
case 33:return 15; | ||
case 33:return 58; | ||
break; | ||
case 34: | ||
case 34:return 14; | ||
break; | ||
case 35:return 15; | ||
break; | ||
case 36:return 40; | ||
break; | ||
case 37:return 41; | ||
break; | ||
case 38:return 42; | ||
break; | ||
case 39:return 43; | ||
break; | ||
case 40:return 16; | ||
break; | ||
case 41: | ||
if (yy_.yytext.match(/\r|\n/) && parser.restricted) { | ||
@@ -914,3 +1004,3 @@ parser.restricted = false; | ||
break; | ||
case 35: | ||
case 42: | ||
if (yy_.yytext.match(/\r|\n/) && parser.restricted) { | ||
@@ -923,32 +1013,32 @@ parser.restricted = false; | ||
break; | ||
case 36:this.begin('string_quoted_content'); parser.charUnescapeCurrentQuote = this.match; return 61; | ||
case 43:this.begin('string_quoted_content'); parser.charUnescapeCurrentQuote = this.match; return 67; | ||
break; | ||
case 37:return 60; | ||
case 44:return 66; | ||
break; | ||
case 38:return 64; | ||
case 45:return 70; | ||
break; | ||
case 39:return 65; | ||
case 46:return 71; | ||
break; | ||
case 40:return 66; | ||
case 47:return 72; | ||
break; | ||
case 41:if (parser.charUnescapeCurrentQuote === this.match) { this.popState(); return 61; } else { return 60; } | ||
case 48:if (parser.charUnescapeCurrentQuote === this.match) { this.popState(); return 67; } else { return 66; } | ||
break; | ||
case 42:return 60; | ||
case 49:return 66; | ||
break; | ||
case 43:this.begin('backtick'); return 58; | ||
case 50:this.begin('backtick'); return 64; | ||
break; | ||
case 44:return 60; | ||
case 51:return 66; | ||
break; | ||
case 45:return 60; | ||
case 52:return 66; | ||
break; | ||
case 46:this.popState(); return 58; | ||
case 53:this.popState(); return 64; | ||
break; | ||
case 47:/* skip whitespaces */ | ||
case 54:/* skip whitespaces */ | ||
break; | ||
case 48:return 'INVALID' | ||
case 55:return 'INVALID' | ||
break; | ||
} | ||
}, | ||
rules: [/^(?:$)/,/^(?:\()/,/^(?:\))/,/^(?:(([1-9][0-9]*)))/,/^(?:true\b)/,/^(?:false\b)/,/^(?:like\b)/,/^(?:LIKE\b)/,/^(?:not\b)/,/^(?:NOT\b)/,/^(?:\?)/,/^(?:,)/,/^(?:\|\|)/,/^(?:&&)/,/^(?:==)/,/^(?:\+)/,/^(?:-)/,/^(?:\*)/,/^(?:\/)/,/^(?:%)/,/^(?:==)/,/^(?:!=)/,/^(?:!)/,/^(?:\$)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\?)/,/^(?::)/,/^(?:<)/,/^(?:>)/,/^(?:<=)/,/^(?:>=)/,/^(?:(([A-Za-z_][A-Za-z_0-9]*)))/,/^(?:\/\*(.|\r|\n)*?\*\/)/,/^(?:\/\/.*($|\r|\n))/,/^(?:((['"])))/,/^(?:\s+)/,/^(?:((\\[Xx][A-Fa-f0-9]{1,2})))/,/^(?:((\\?[0-7]{1,3})))/,/^(?:((\\[abfnrtv\\/'"])))/,/^(?:((['"])))/,/^(?:(([^\0\n])))/,/^(?:`)/,/^(?:\s+)/,/^(?:[^`])/,/^(?:`)/,/^(?:\s+)/,/^(?:.)/], | ||
conditions: {"backtick":{"rules":[44,45,46],"inclusive":false},"string_quoted_content":{"rules":[37,38,39,40,41,42],"inclusive":false},"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,43,47,48],"inclusive":true}} | ||
rules: [/^(?:$)/,/^(?:\()/,/^(?:\))/,/^(?:0\b)/,/^(?:(([1-9][0-9]*)))/,/^(?:true\b)/,/^(?:false\b)/,/^(?:like\b)/,/^(?:LIKE\b)/,/^(?:not\b)/,/^(?:NOT\b)/,/^(?:in\b)/,/^(?:IN\b)/,/^(?:NULL\b)/,/^(?:null\b)/,/^(?:\?)/,/^(?:,)/,/^(?:\|\|)/,/^(?:&&)/,/^(?:==)/,/^(?:\+)/,/^(?:-)/,/^(?:\*)/,/^(?:\/)/,/^(?:%)/,/^(?:==)/,/^(?:!=)/,/^(?:!)/,/^(?:\$)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:\?)/,/^(?::)/,/^(?:<)/,/^(?:>)/,/^(?:<=)/,/^(?:>=)/,/^(?:(([A-Za-z_][A-Za-z_0-9]*)))/,/^(?:\/\*(.|\r|\n)*?\*\/)/,/^(?:\/\/.*($|\r|\n))/,/^(?:((['"])))/,/^(?:\s+)/,/^(?:((\\[Xx][A-Fa-f0-9]{1,2})))/,/^(?:((\\?[0-7]{1,3})))/,/^(?:((\\[abfnrtv\\/'"])))/,/^(?:((['"])))/,/^(?:(([^\0\n])))/,/^(?:`)/,/^(?:\s+)/,/^(?:[^`])/,/^(?:`)/,/^(?:\s+)/,/^(?:.)/], | ||
conditions: {"backtick":{"rules":[51,52,53],"inclusive":false},"string_quoted_content":{"rules":[44,45,46,47,48,49],"inclusive":false},"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,50,54,55],"inclusive":true}} | ||
}); | ||
@@ -955,0 +1045,0 @@ return lexer; |
@@ -76,8 +76,2 @@ /* | ||
Client.joinOperatios = { | ||
"LEFT": Messages.messages['Mysqlx.Crud.Join'].enums.Operation.LEFT, | ||
"RIGHT": Messages.messages['Mysqlx.Crud.Join'].enums.Operation.RIGHT, | ||
"INNER": Messages.messages['Mysqlx.Crud.Join'].enums.Operation.INNER | ||
}; | ||
// TODO - This is a hack, see also TODO in ResponseHandler.prototype.sendMessage | ||
@@ -309,8 +303,7 @@ Client.serverGoneMessageId = -1; | ||
Client.prototype.crudInsert = function (schema, collection, model, rows, projection) { | ||
if (!rows.length) { | ||
return Promise.reject(new Error('No document provided for Crud::Insert')); | ||
} | ||
Client.prototype.crudInsert = function (schema, collection, model, data, options) { | ||
data = Object.assign({}, { columns: [], rows: [] }, data); | ||
options = Object.assign({}, { upsert: false }, options); | ||
const data = { | ||
const message = { | ||
collection: { | ||
@@ -321,6 +314,7 @@ schema: schema, | ||
data_model: model, | ||
projection: projection || [] | ||
projection: data.columns, | ||
upsert: options.upsert | ||
}; | ||
data.row = rows.map(row => ({ | ||
message.row = data.rows.map(row => ({ | ||
field: row.map(field => ({ | ||
@@ -332,9 +326,9 @@ type: Messages.messages['Mysqlx.Expr.Expr'].enums.Type.LITERAL, | ||
const buffer = this.encodeMessage(Messages.ClientMessages.CRUD_INSERT, data); | ||
const protobuf = this.encodeMessage(Messages.ClientMessages.CRUD_INSERT, message); | ||
const handler = new handlers.SqlResultHandler(); | ||
return handler.sendMessage(this._workQueue, this._stream, buffer); | ||
return handler.sendMessage(this._workQueue, this._stream, protobuf); | ||
}; | ||
Client.prototype.crudFind = function (session, schema, collection, model, projection, criteria, group, having, order, limit, rowcb, metacb, bounds, joins) { | ||
Client.prototype.crudFind = function (session, schema, collection, model, projection, criteria, group, having, order, limit, rowcb, metacb, bounds, joins, locking) { | ||
const data = { | ||
@@ -404,2 +398,6 @@ collection: { | ||
if (locking) { | ||
data.locking = locking; | ||
} | ||
if (criteria && criteria.placeholders && criteria.placeholders.named.length) { | ||
@@ -411,2 +409,3 @@ data.args = criteria.placeholders.named.map(key => DataType.encodeScalar(bounds[key])); | ||
handler = new handlers.SqlResultHandler(rowcb, metacb); | ||
return handler.sendMessage(this._workQueue, this._stream, buffer); | ||
@@ -413,0 +412,0 @@ }; |
@@ -13,2 +13,15 @@ { | ||
"TABLE": 2 | ||
}, | ||
"ViewAlgorithm": { | ||
"UNDEFINED": 1, | ||
"MERGE": 2, | ||
"TEMPTABLE": 3 | ||
}, | ||
"ViewSqlSecurity": { | ||
"INVOKER": 1, | ||
"DEFINER": 2 | ||
}, | ||
"ViewCheckOption": { | ||
"LOCAL": 1, | ||
"CASCADED": 2 | ||
} | ||
@@ -61,7 +74,2 @@ }, | ||
"tag": 2 | ||
}, | ||
"alias": { | ||
"rule": "optional", | ||
"type": "string", | ||
"tag": 3 | ||
} | ||
@@ -137,8 +145,7 @@ } | ||
}, | ||
"Join": { | ||
"Find": { | ||
"enums": { | ||
"Operation": { | ||
"LEFT": 0, | ||
"RIGHT": 1, | ||
"INNER": 2 | ||
"RowLock": { | ||
"SHARED_LOCK": 1, | ||
"EXCLUSIVE_LOCK": 2 | ||
} | ||
@@ -150,26 +157,4 @@ }, | ||
"type": "Collection", | ||
"tag": 1 | ||
}, | ||
"match": { | ||
"rule": "required", | ||
"type": "Mysqlx.Expr.Expr", | ||
"tag": 2 | ||
}, | ||
"operation": { | ||
"rule": "optional", | ||
"type": "Operation", | ||
"tag": 3, | ||
"options": { | ||
"default": "LEFT" | ||
} | ||
} | ||
} | ||
}, | ||
"Find": { | ||
"fields": { | ||
"collection": { | ||
"rule": "required", | ||
"type": "Collection", | ||
"tag": 2 | ||
}, | ||
"data_model": { | ||
@@ -215,6 +200,6 @@ "rule": "optional", | ||
}, | ||
"join": { | ||
"rule": "repeated", | ||
"type": "Join", | ||
"tag": 10 | ||
"locking": { | ||
"rule": "optional", | ||
"type": "RowLock", | ||
"tag": 12 | ||
} | ||
@@ -260,2 +245,10 @@ } | ||
"tag": 5 | ||
}, | ||
"upsert": { | ||
"rule": "optional", | ||
"type": "bool", | ||
"tag": 6, | ||
"options": { | ||
"default": false | ||
} | ||
} | ||
@@ -336,2 +329,111 @@ } | ||
} | ||
}, | ||
"CreateView": { | ||
"fields": { | ||
"collection": { | ||
"rule": "required", | ||
"type": "Collection", | ||
"tag": 1 | ||
}, | ||
"definer": { | ||
"rule": "optional", | ||
"type": "string", | ||
"tag": 2 | ||
}, | ||
"algorithm": { | ||
"rule": "optional", | ||
"type": "ViewAlgorithm", | ||
"tag": 3, | ||
"options": { | ||
"default": "UNDEFINED" | ||
} | ||
}, | ||
"security": { | ||
"rule": "optional", | ||
"type": "ViewSqlSecurity", | ||
"tag": 4, | ||
"options": { | ||
"default": "DEFINER" | ||
} | ||
}, | ||
"check": { | ||
"rule": "optional", | ||
"type": "ViewCheckOption", | ||
"tag": 5 | ||
}, | ||
"column": { | ||
"rule": "repeated", | ||
"type": "string", | ||
"tag": 6 | ||
}, | ||
"stmt": { | ||
"rule": "required", | ||
"type": "Find", | ||
"tag": 7 | ||
}, | ||
"replace_existing": { | ||
"rule": "optional", | ||
"type": "bool", | ||
"tag": 8, | ||
"options": { | ||
"default": false | ||
} | ||
} | ||
} | ||
}, | ||
"ModifyView": { | ||
"fields": { | ||
"collection": { | ||
"rule": "required", | ||
"type": "Collection", | ||
"tag": 1 | ||
}, | ||
"definer": { | ||
"rule": "optional", | ||
"type": "string", | ||
"tag": 2 | ||
}, | ||
"algorithm": { | ||
"rule": "optional", | ||
"type": "ViewAlgorithm", | ||
"tag": 3 | ||
}, | ||
"security": { | ||
"rule": "optional", | ||
"type": "ViewSqlSecurity", | ||
"tag": 4 | ||
}, | ||
"check": { | ||
"rule": "optional", | ||
"type": "ViewCheckOption", | ||
"tag": 5 | ||
}, | ||
"column": { | ||
"rule": "repeated", | ||
"type": "string", | ||
"tag": 6 | ||
}, | ||
"stmt": { | ||
"rule": "optional", | ||
"type": "Find", | ||
"tag": 7 | ||
} | ||
} | ||
}, | ||
"DropView": { | ||
"fields": { | ||
"collection": { | ||
"rule": "required", | ||
"type": "Collection", | ||
"tag": 1 | ||
}, | ||
"if_exists": { | ||
"rule": "optional", | ||
"type": "bool", | ||
"tag": 2, | ||
"options": { | ||
"default": false | ||
} | ||
} | ||
} | ||
} | ||
@@ -338,0 +440,0 @@ }, |
@@ -175,3 +175,3 @@ { | ||
"rule": "required", | ||
"type": "Expr", | ||
"type": "Mysqlx.Expr.Expr", | ||
"tag": 2 | ||
@@ -178,0 +178,0 @@ } |
{ | ||
"imports": [ | ||
"mysqlx_sql.proto", | ||
"mysqlx_resultset.proto", | ||
"mysqlx_crud.proto", | ||
"mysqlx_session.proto", | ||
"mysqlx_connection.proto", | ||
"mysqlx_expect.proto", | ||
"mysqlx_notice.proto" | ||
], | ||
"options": { | ||
@@ -31,3 +22,6 @@ "java_package": "com.mysql.cj.mysqlx.protobuf" | ||
"EXPECT_OPEN": 24, | ||
"EXPECT_CLOSE": 25 | ||
"EXPECT_CLOSE": 25, | ||
"CRUD_CREATE_VIEW": 30, | ||
"CRUD_MODIFY_VIEW": 31, | ||
"CRUD_DROP_VIEW": 32 | ||
} | ||
@@ -34,0 +28,0 @@ }, |
{ | ||
"name": "@mysql/xdevapi", | ||
"version": "8.0.7", | ||
"version": "8.0.8", | ||
"description": "MySQL Connector/Node.JS - A Node.JS driver for MySQL using the X Protocol and X DevAPI.", | ||
"author": "Johannes Schlüter <johannes.schlueter@oracle.com>", | ||
"contributors": [{ | ||
"name": "Rui Quelhas", | ||
"email": "rui.quelhas@oracle.com" | ||
}], | ||
"contributors": [ | ||
{ | ||
"name": "Rui Quelhas", | ||
"email": "rui.quelhas@oracle.com" | ||
} | ||
], | ||
"license": "GPL-2.0", | ||
@@ -11,0 +13,0 @@ "files": [ |
@@ -24,3 +24,3 @@ # MySQL Connector/Node.js with X DevAPI | ||
```sh | ||
$ npm install mysql-connector-nodejs-8.0.7.tar.gz` | ||
$ npm install mysql-connector-nodejs-8.0.8.tar.gz` | ||
``` | ||
@@ -27,0 +27,0 @@ * use the @mysql/xdevapi package from [https://npmjs.com](https://npmjs.com) and install it: |
@@ -12,28 +12,24 @@ 'use strict'; | ||
exports.setup = function () { | ||
let session; | ||
return mysqlx | ||
.getSession(config) | ||
.then(s => { | ||
session = s; | ||
return session.dropSchema(config.schema); | ||
.then(session => { | ||
return session.dropSchema(config.schema).then(() => session); | ||
}) | ||
.then(() => { | ||
return session.createSchema(config.schema); | ||
}) | ||
.then(schema => { | ||
return { session, schema }; | ||
.then(session => { | ||
return session.createSchema(config.schema).then(schema => ({ session, schema })); | ||
}); | ||
}; | ||
exports.teardown = function () { | ||
return mysqlx | ||
.getSession(config) | ||
.then(session => { | ||
return session.dropSchema(config.schema).then(() => session); | ||
}) | ||
.then(session => { | ||
exports.teardown = function (session, schema) { | ||
if (!session) { | ||
return Promise.reject(new Error(`cannot close \`${session}\` session`)); | ||
} | ||
const schemaName = !schema ? config.schema : schema.getName(); | ||
return session | ||
.dropSchema(schemaName) | ||
.then(() => { | ||
return session.close(); | ||
}); | ||
}; |
@@ -5,7 +5,12 @@ 'use strict'; | ||
const expect = require('chai').expect; | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const fixtures = require('test/fixtures'); | ||
chai.use(chaiAsPromised); | ||
const expect = chai.expect; | ||
describe('@integration document collection add', () => { | ||
let collection, schema; | ||
let session, schema, collection; | ||
@@ -15,2 +20,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -29,3 +35,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -32,0 +38,0 @@ |
@@ -9,3 +9,3 @@ 'use strict'; | ||
describe('@integration document collection find', () => { | ||
let schema, collection; | ||
let session, schema, collection; | ||
@@ -15,2 +15,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -29,3 +30,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -121,2 +122,55 @@ | ||
}); | ||
context('single document retrieval', () => { | ||
beforeEach('add fixtures', () => { | ||
return collection | ||
.add({ _id: '1', name: 'foo' }) | ||
.add({ _id: '2', name: 'bar' }) | ||
.execute(); | ||
}); | ||
it('should return an existing document with the given id', () => { | ||
const expected = { _id: '1', name: 'foo' }; | ||
return collection | ||
.getOne('1') | ||
.then(doc => expect(doc).to.deep.equal(expected)); | ||
}); | ||
it('should return null if a document with the given id does not exist', () => { | ||
return collection | ||
.getOne('3') | ||
.then(doc => expect(doc).to.be.null); | ||
}); | ||
}); | ||
context('multi-option expressions', () => { | ||
beforeEach('add fixtures', () => { | ||
return collection | ||
.add({ _id: '1', name: 'foo' }) | ||
.add({ _id: '2', name: 'bar' }) | ||
.add({ _id: '3', name: 'baz' }) | ||
.execute(); | ||
}); | ||
it('should return all documents that match a criteria specified by a grouped expression', () => { | ||
const expected = [{ _id: '1', name: 'foo' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.find("$._id in ('1', '3')") | ||
.execute(doc => doc && actual.push(doc)) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should return all documents that do not match a criteria specified by a grouped expression', () => { | ||
const expected = [{ _id: '2', name: 'bar' }]; | ||
let actual = []; | ||
return collection | ||
.find("$._id not in ('1', '3')") | ||
.execute(doc => doc && actual.push(doc)) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
}); |
@@ -29,5 +29,5 @@ 'use strict'; | ||
return collection | ||
.add({ _id: 1, name: 'foo' }) | ||
.add({ _id: 2, name: 'bar' }) | ||
.add({ _id: 3, name: 'baz' }) | ||
.add({ _id: '1', name: 'foo' }) | ||
.add({ _id: '2', name: 'bar' }) | ||
.add({ _id: '3', name: 'baz' }) | ||
.execute(); | ||
@@ -37,3 +37,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(session); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -43,3 +43,3 @@ | ||
it('should updated all documents in a collection', () => { | ||
const expected = [{ _id: 1, name: 'qux' }, { _id: 2, name: 'qux' }, { _id: 3, name: 'qux' }]; | ||
const expected = [{ _id: '1', name: 'qux' }, { _id: '2', name: 'qux' }, { _id: '3', name: 'qux' }]; | ||
let actual = []; | ||
@@ -58,3 +58,3 @@ | ||
it('should update the documents from a collection that match the criteria', () => { | ||
const expected = [{ _id: 1, name: 'foo' }, { _id: 2, name: 'qux' }, { _id: 3, name: 'baz' }]; | ||
const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'qux' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
@@ -73,3 +73,3 @@ | ||
it('should modify a given number of documents', () => { | ||
const expected = [{ _id: 1, name: 'qux' }, { _id: 2, name: 'bar' }, { _id: 3, name: 'baz' }]; | ||
const expected = [{ _id: '1', name: 'qux' }, { _id: '2', name: 'bar' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
@@ -86,2 +86,70 @@ | ||
}); | ||
context('single document replacement', () => { | ||
it('should replace the entire document if it exists', () => { | ||
const expected = [{ _id: '1', age: 23 }, { _id: '2', name: 'bar' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.replaceOne('1', { _id: '3', age: 23 }) | ||
.then(result => { | ||
expect(result.getAffectedItemsCount()).to.equal(1); | ||
return collection | ||
.find() | ||
.execute(doc => actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should do nothing if the document does not exist', () => { | ||
const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'bar' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.replaceOne('4', { _id: '1', name: 'baz', age: 23 }) | ||
.then(result => { | ||
expect(result.getAffectedItemsCount()).to.equal(0); | ||
return collection | ||
.find() | ||
.execute(doc => actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
context('multi-option expressions', () => { | ||
it('should modify all documents that match a criteria specified by a grouped expression', () => { | ||
const expected = [{ _id: '1', name: 'qux' }, { _id: '2', name: 'bar' }, { _id: '3', name: 'qux' }]; | ||
let actual = []; | ||
return collection | ||
.modify("$._id in ('1', '3')") | ||
.set('$.name', 'qux') | ||
.execute() | ||
.then(() => { | ||
return collection | ||
.find() | ||
.execute(doc => doc && actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should modify all documents that do not match a criteria specified by a grouped expression', () => { | ||
const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'qux' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.modify("$._id not in ('1', '3')") | ||
.set('$.name', 'qux') | ||
.execute() | ||
.then(() => { | ||
return collection | ||
.find() | ||
.execute(doc => doc && actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
}); |
@@ -29,5 +29,5 @@ 'use strict'; | ||
return collection | ||
.add({ _id: 1, name: 'foo' }) | ||
.add({ _id: 2, name: 'bar' }) | ||
.add({ _id: 3, name: 'baz' }) | ||
.add({ _id: '1', name: 'foo' }) | ||
.add({ _id: '2', name: 'bar' }) | ||
.add({ _id: '3', name: 'baz' }) | ||
.execute(); | ||
@@ -37,3 +37,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(session); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -61,3 +61,3 @@ | ||
it('should remove the documents from a collection that match the criteria', () => { | ||
const expected = [{ _id: 2, name: 'bar' }, { _id: 3, name: 'baz' }]; | ||
const expected = [{ _id: '2', name: 'bar' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
@@ -75,3 +75,3 @@ | ||
it('should remove a given number of documents', () => { | ||
const expected = [{ _id: 3, name: 'baz' }]; | ||
const expected = [{ _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
@@ -87,2 +87,68 @@ | ||
}); | ||
context('single document removal', () => { | ||
it('should remove an existing document with the given id', () => { | ||
const expected = [{ _id: '2', name: 'bar' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.removeOne('1') | ||
.then(result => { | ||
expect(result.getAffectedItemsCount()).to.equal(1); | ||
return collection | ||
.find() | ||
.execute(doc => actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should do nothing if no document exists with the given id', () => { | ||
const expected = [{ _id: '1', name: 'foo' }, { _id: '2', name: 'bar' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.removeOne('4') | ||
.then(result => { | ||
expect(result.getAffectedItemsCount()).to.equal(0); | ||
return collection | ||
.find() | ||
.execute(doc => actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
context('multi-option expressions', () => { | ||
it('should remove all documents that match a criteria specified by a grouped expression', () => { | ||
const expected = [{ _id: '2', name: 'bar' }]; | ||
let actual = []; | ||
return collection | ||
.remove("$._id in ('1', '3')") | ||
.execute() | ||
.then(() => { | ||
return collection | ||
.find() | ||
.execute(doc => doc && actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should remove all documents that do not match a criteria specified by a grouped expression', () => { | ||
const expected = [{ _id: '1', name: 'foo' }, { _id: '3', name: 'baz' }]; | ||
let actual = []; | ||
return collection | ||
.remove("$._id not in ('1', '3')") | ||
.execute() | ||
.then(() => { | ||
return collection | ||
.find() | ||
.execute(doc => doc && actual.push(doc)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
}); |
@@ -10,3 +10,3 @@ 'use strict'; | ||
describe('@integration collection miscellaneous tests', () => { | ||
let schema, collection; | ||
let session, schema, collection; | ||
@@ -16,2 +16,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -28,3 +29,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -69,3 +70,3 @@ | ||
]).then(() => { | ||
collection.find().execute(actual => expect(actual).to.deep.equal(expected)); | ||
return collection.find().execute(actual => expect(actual).to.deep.equal(expected)); | ||
}); | ||
@@ -89,3 +90,3 @@ }); | ||
]).then(() => { | ||
collection.find().execute(actual => expect(actual).to.be.empty); | ||
return collection.find().execute(actual => expect(actual).to.be.undefined); | ||
}); | ||
@@ -102,3 +103,3 @@ }); | ||
]).then(() => { | ||
collection.find().execute(actual => expect(actual).to.deep.equal(document2)); | ||
return collection.find().execute(actual => expect(actual).to.deep.equal(document2)); | ||
}); | ||
@@ -105,0 +106,0 @@ }); |
@@ -11,3 +11,3 @@ 'use strict'; | ||
describe('@integration relational miscellaneous tests', () => { | ||
let session; | ||
let session, schema; | ||
@@ -18,2 +18,3 @@ beforeEach('set context', () => { | ||
session = suite.session; | ||
schema = suite.schema; | ||
}); | ||
@@ -23,3 +24,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -26,0 +27,0 @@ |
@@ -9,3 +9,3 @@ 'use strict'; | ||
describe('@integration relational table delete', () => { | ||
let schema, table; | ||
let session, schema, table; | ||
@@ -15,2 +15,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -33,3 +34,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -120,2 +121,36 @@ | ||
}); | ||
context('multi-option expressions', () => { | ||
it('should return all documents that match a criteria specified by a grouped expression', () => { | ||
const expected = []; | ||
let actual = []; | ||
return table | ||
.delete() | ||
.where("`name` in ('foo', 'bar', 'baz')") | ||
.execute() | ||
.then(() => { | ||
return table | ||
.select() | ||
.execute(row => row && row.length && actual.push(row)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should return all documents that do not match a criteria specified by a grouped expression', () => { | ||
const expected = [['foo', 42], ['bar', 23], ['baz', 42]]; | ||
let actual = []; | ||
return table | ||
.delete() | ||
.where('`age` not in (23, 42)') | ||
.execute() | ||
.then(() => { | ||
return table | ||
.select() | ||
.execute(row => row && row.length && actual.push(row)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
}); |
@@ -9,3 +9,3 @@ 'use strict'; | ||
describe('@integration relational table insert', () => { | ||
let schema, table; | ||
let session, schema, table; | ||
@@ -15,2 +15,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -33,3 +34,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -36,0 +37,0 @@ |
@@ -9,3 +9,3 @@ 'use strict'; | ||
describe('@integration relational table select', () => { | ||
let schema; | ||
let session, schema; | ||
@@ -15,2 +15,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -35,3 +36,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -245,2 +246,38 @@ | ||
}); | ||
context('multi-option expressions', () => { | ||
beforeEach('add fixtures', () => { | ||
return schema | ||
.getTable('test') | ||
.insert(['test2', 'test3']) | ||
.values(['foo', 42]) | ||
.values(['bar', 23]) | ||
.values(['baz', 42]) | ||
.execute(); | ||
}); | ||
it('should return all documents that match a criteria specified by a grouped expression', () => { | ||
const expected = [[1, 'foo', 42], [3, 'baz', 42]]; | ||
let actual = []; | ||
return schema | ||
.getTable('test') | ||
.select() | ||
.where("`test2` in ('foo', 'baz')") | ||
.execute(row => row && row.length && actual.push(row)) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should return all documents that do not match a criteria specified by a grouped expression', () => { | ||
const expected = [[2, 'bar', 23]]; | ||
let actual = []; | ||
return schema | ||
.getTable('test') | ||
.select() | ||
.where('`test3` not in (50, 42)') | ||
.execute(row => row && row.length && actual.push(row)) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
}); |
@@ -9,3 +9,3 @@ 'use strict'; | ||
describe('@integration relational table update', () => { | ||
let schema, table; | ||
let session, schema, table; | ||
@@ -15,2 +15,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -43,3 +44,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -128,2 +129,38 @@ | ||
}); | ||
context('multi-option expressions', () => { | ||
it('should update all documents that match a criteria specified by a grouped expression', () => { | ||
const expected = [['foo', 50], ['bar', 50], ['baz', 42]]; | ||
let actual = []; | ||
return table | ||
.update() | ||
.where("`name` in ('foo', 'bar')") | ||
.set('age', 50) | ||
.execute() | ||
.then(() => { | ||
return table | ||
.select() | ||
.execute(row => row && row.length && actual.push(row)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
it('should update all documents that do not match a criteria specified by a grouped expression', () => { | ||
const expected = [['foo', 42], ['qux', 23], ['baz', 42]]; | ||
let actual = []; | ||
return table | ||
.update() | ||
.where('`age` not in (42, 50)') | ||
.set('name', 'qux') | ||
.execute() | ||
.then(() => { | ||
return table | ||
.select() | ||
.execute(row => row && row.length && actual.push(row)); | ||
}) | ||
.then(() => expect(actual).to.deep.equal(expected)); | ||
}); | ||
}); | ||
}); |
@@ -14,3 +14,3 @@ 'use strict'; | ||
describe('@integration session schema', () => { | ||
let schema; | ||
let session, schema; | ||
@@ -20,2 +20,3 @@ beforeEach('set context', () => { | ||
// TODO(rui.quelhas): use ES6 destructuring assignment for node >=6.0.0 | ||
session = suite.session; | ||
schema = suite.schema; | ||
@@ -26,3 +27,3 @@ }); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -32,6 +33,2 @@ | ||
const collections = ['test1', 'test2']; | ||
const expected = { | ||
[collections[0]]: schema.getCollection(collections[0]), | ||
[collections[1]]: schema.getCollection(collections[1]) | ||
}; | ||
@@ -41,4 +38,9 @@ return expect(Promise.all([ | ||
schema.createCollection(collections[1]), | ||
expect(schema.getCollections()).to.eventually.deep.equal(expected) | ||
])).to.be.fulfilled; | ||
schema.getCollections() | ||
])).to.be.fulfilled.then(result => { | ||
expect(Object.keys(result[2])).to.have.lengthOf(2); | ||
expect(result[2]).to.have.keys(collections); | ||
expect(result[2][collections[0]].inspect()).to.deep.equal(result[0].inspect()); | ||
expect(result[2][collections[1]].inspect()).to.deep.equal(result[1].inspect()); | ||
}); | ||
}); | ||
@@ -45,0 +47,0 @@ |
@@ -9,4 +9,4 @@ 'use strict'; | ||
const config = require('test/properties'); | ||
const fs = require('fs'); | ||
const fixtures = require('test/fixtures'); | ||
const fs = require('fs'); | ||
const mysqlx = require('index'); | ||
@@ -23,3 +23,3 @@ const os = require('os'); | ||
it('should connect to the server in a new session', () => { | ||
const tcpConfig = Object.assign({}, config); | ||
const tcpConfig = Object.assign({}, config, { socket: undefined }); | ||
@@ -31,3 +31,3 @@ return expect(mysqlx.getSession(tcpConfig)).to.be.fulfilled | ||
it('should connect to the server with an IPv6 host', () => { | ||
const ipv6Config = Object.assign({}, config, { host: '::1' }); | ||
const ipv6Config = Object.assign({}, config, { host: '::1', socket: undefined }); | ||
@@ -51,3 +51,3 @@ return expect(mysqlx.getSession(ipv6Config)).to.be.fulfilled | ||
it('should connect to the server insecurely if SSL/TLS is disabled explicitly', () => { | ||
const insecureConfig = Object.assign({}, config, { ssl: false }); | ||
const insecureConfig = Object.assign({}, config, { ssl: false, socket: undefined }); | ||
@@ -82,3 +82,3 @@ return expect(mysqlx.getSession(insecureConfig)).to.be.fulfilled | ||
expect(session).to.be.an.instanceof(Session); | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -174,8 +174,8 @@ return session.close(); | ||
it('should connect to the server with a local UNIX socket', function () { | ||
if (!config.socket) { | ||
if (!config.socket || os.platform() === 'win32') { | ||
return this.skip(); | ||
} | ||
// Uses the default socket allocated for MySQL. | ||
const uri = `mysqlx://${config.dbUser}:${config.dbPassword}@(${config.socket})`; | ||
// Uses the default socket allocated for MySQL and a working authentication method. | ||
const uri = `mysqlx://${config.dbUser}:${config.dbPassword}@(${config.socket})?auth=MYSQL41`; | ||
const expected = { dbUser: config.dbUser, host: undefined, port: undefined, socket: config.socket, ssl: false }; | ||
@@ -185,3 +185,3 @@ | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -198,3 +198,3 @@ return session.close(); | ||
// Uses the default socket allocated for MySQL. | ||
const uri = `mysqlx://${config.dbUser}:${config.dbPassword}@(${config.socket})?ssl-mode=REQUIRED&ssl-ca=(/path/to/ca.pem)?ssl-crl=(/path/to/crl.pem)`; | ||
const uri = `mysqlx://${config.dbUser}:${config.dbPassword}@(${config.socket})?ssl-mode=REQUIRED&ssl-ca=(/path/to/ca.pem)?ssl-crl=(/path/to/crl.pem)&auth=MYSQL41`; | ||
const expected = { dbUser: config.dbUser, host: undefined, port: undefined, socket: config.socket, ssl: false }; | ||
@@ -204,3 +204,3 @@ | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -311,3 +311,3 @@ return session.close(); | ||
it('should connect to the server with a local UNIX socket', function () { | ||
if (!config.socket) { | ||
if (!config.socket || os.platform() === 'win32') { | ||
return this.skip(); | ||
@@ -317,3 +317,3 @@ } | ||
// Uses the default socket allocated for MySQL. | ||
const uri = `${config.dbUser}:${config.dbPassword}@(${config.socket})`; | ||
const uri = `${config.dbUser}:${config.dbPassword}@(${config.socket})?auth=MYSQL41`; | ||
const expected = { dbUser: config.dbUser, host: undefined, port: undefined, socket: config.socket, ssl: false }; | ||
@@ -323,3 +323,3 @@ | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -336,3 +336,3 @@ return session.close(); | ||
// Uses the default socket allocated for MySQL. | ||
const uri = `${config.dbUser}:${config.dbPassword}@(${config.socket})?ssl-mode=REQUIRED&ssl-ca=(/path/to/ca.pem)?ssl-crl=(/path/to/crl.pem)`; | ||
const uri = `${config.dbUser}:${config.dbPassword}@(${config.socket})?ssl-mode=REQUIRED&ssl-ca=(/path/to/ca.pem)?ssl-crl=(/path/to/crl.pem)&auth=MYSQL41`; | ||
const expected = { dbUser: config.dbUser, host: undefined, port: undefined, socket: config.socket, ssl: false }; | ||
@@ -342,3 +342,3 @@ | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -371,3 +371,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -385,3 +385,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -397,3 +397,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -409,3 +409,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -424,3 +424,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -437,3 +437,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -450,3 +450,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -481,4 +481,4 @@ return session.close(); | ||
// TODO(Rui): maybe add some appdata to the object. | ||
const data = Object.assign({}, config); | ||
const expected = { dbUser: data.dbUser, host: data.host, port: data.port, socket: undefined, ssl: true }; | ||
const data = Object.assign({}, config, { socket: undefined }); | ||
const expected = { dbUser: data.dbUser, host: data.host, port: data.port, socket: data.socket, ssl: true }; | ||
@@ -488,3 +488,3 @@ return expect(mysqlx.config.save('test', data)).to.be.fulfilled | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -501,4 +501,4 @@ return session.close(); | ||
it('should save the session details the using name and a JSON config', () => { | ||
const data = Object.assign({}, config, { ssl: false }); | ||
const expected = { dbUser: data.dbUser, host: data.host, port: data.port, socket: undefined, ssl: false }; | ||
const data = Object.assign({}, config, { socket: undefined, ssl: false }); | ||
const expected = { dbUser: data.dbUser, host: data.host, port: data.port, socket: data.socket, ssl: false }; | ||
@@ -508,3 +508,3 @@ return expect(mysqlx.config.save('test', JSON.stringify(data))).to.be.fulfilled | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -527,3 +527,3 @@ return session.close(); | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -538,4 +538,4 @@ return session.close(); | ||
// TODO(Rui): maybe add some appdata to the object. | ||
const data = Object.assign({}, config, { ssl: false }); | ||
const expected = { dbUser: data.dbUser, host: data.host, port: data.port, socket: undefined, ssl: false }; | ||
const data = Object.assign({}, config, { ssl: false, socket: undefined }); | ||
const expected = { dbUser: data.dbUser, host: data.host, port: data.port, socket: data.socket, ssl: false }; | ||
@@ -550,3 +550,3 @@ return expect(mysqlx.config.save('test', data)).to.be.fulfilled | ||
.then(session => { | ||
expect(session.inspect()).to.deep.equal(expected); | ||
expect(session.inspect()).to.deep.include(expected); | ||
@@ -594,32 +594,35 @@ return session.close(); | ||
context('database management', () => { | ||
let session; | ||
beforeEach('set context', () => { | ||
return fixtures.setup().then(suite => { | ||
session = suite.session; | ||
}); | ||
}); | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(); | ||
return fixtures.teardown(session); | ||
}); | ||
it('should allow to drop an existing schema', () => { | ||
return mysqlx.getSession(config) | ||
.then(session => expect(session.dropSchema(config.schema)).to.be.fulfilled); | ||
return expect(session.dropSchema(config.schema)).to.be.fulfilled; | ||
}); | ||
it('should not fail to drop a non-existent schema', () => { | ||
return mysqlx.getSession(config) | ||
.then(session => session.dropSchema(config.schema).then(() => session)) | ||
.then(session => expect(session.dropSchema(config.schema)).to.be.fulfilled); | ||
return session.dropSchema(config.schema) | ||
.then(() => expect(session.dropSchema(config.schema)).to.be.fulfilled); | ||
}); | ||
it('should fail to drop a schema with an empty name', () => { | ||
return mysqlx.getSession(config) | ||
.then(session => expect(session.dropSchema('')).to.be.rejected); | ||
return expect(session.dropSchema('')).to.be.rejected; | ||
}); | ||
it('should fail to drop a schema with an invalid name', () => { | ||
return mysqlx.getSession(config) | ||
.then(session => expect(session.dropSchema(' ')).to.be.rejected); | ||
return expect(session.dropSchema(' ')).to.be.rejected; | ||
}); | ||
it('should fail to drop a schema with name set to `null`', () => { | ||
return mysqlx.getSession(config) | ||
.then(session => expect(session.dropSchema(null)).to.be.rejected); | ||
return expect(session.dropSchema(null)).to.be.rejected; | ||
}); | ||
}); | ||
}); |
@@ -20,3 +20,3 @@ 'use strict'; | ||
afterEach('clear context', () => { | ||
return fixtures.teardown(session); | ||
return fixtures.teardown(session, schema); | ||
}); | ||
@@ -23,0 +23,0 @@ |
@@ -9,2 +9,3 @@ 'use strict'; | ||
const isRemoteServer = process.env.NODE_TEST_MYSQL_LOCATION === 'remote'; | ||
const isSecureServer = process.env.NODE_TEST_MYSQL_SSL !== 'false'; | ||
@@ -17,3 +18,4 @@ module.exports = { | ||
schema: process.env.NODE_TEST_MYSQL_SCHEMA || 'nodejsmysqlxtest', | ||
socket: !isRemoteServer && os.platform() !== 'win32' ? socket : undefined | ||
socket: !isRemoteServer && os.platform() !== 'win32' ? socket : undefined, | ||
ssl: isSecureServer | ||
}; |
@@ -59,2 +59,9 @@ 'use strict'; | ||
}); | ||
it('should allow empty passwords', () => { | ||
const username = 'foo'; | ||
const auth = new PlainAuth({ dbUser: username }); | ||
return expect(auth.getInitialAuthData()).to.not.throw; | ||
}); | ||
}); |
@@ -6,9 +6,13 @@ 'use strict'; | ||
// npm `test` script was updated to use NODE_PATH=. | ||
const Collection = require('lib/DevAPI/Collection'); | ||
const CollectionAdd = require('lib/DevAPI/CollectionAdd'); | ||
const CollectionModify = require('lib/DevAPI/CollectionModify'); | ||
const CollectionRemove = require('lib/DevAPI/CollectionRemove'); | ||
const expect = require('chai').expect; | ||
const Client = require('lib/Protocol/Client'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const collection = require('lib/DevAPI/Collection'); | ||
const td = require('testdouble'); | ||
chai.use(chaiAsPromised); | ||
const expect = chai.expect; | ||
describe('Collection', () => { | ||
@@ -28,5 +32,3 @@ let sqlStmtExecute, getName; | ||
it('should return the collection name', () => { | ||
const collection = new Collection(null, null, 'foobar'); | ||
expect(collection.getName()).to.equal('foobar'); | ||
expect(collection(null, null, 'foobar').getName()).to.equal('foobar'); | ||
}); | ||
@@ -37,7 +39,7 @@ }); | ||
it('should return the associated schema', () => { | ||
const collection = new Collection(null, { getName }); | ||
const instance = collection(null, { getName }); | ||
td.when(getName()).thenReturn('foobar'); | ||
expect(collection.getSchema().getName()).to.equal('foobar'); | ||
expect(instance.getSchema().getName()).to.equal('foobar'); | ||
}); | ||
@@ -48,5 +50,5 @@ }); | ||
it('should return the associated session', () => { | ||
const collection = new Collection({ foo: 'bar' }); | ||
const instance = collection({ foo: 'bar' }); | ||
expect(collection.getSession()).to.deep.equal({ foo: 'bar' }); | ||
expect(instance.getSession()).to.deep.equal({ foo: 'bar' }); | ||
}); | ||
@@ -57,3 +59,3 @@ }); | ||
it('should return true if exists in database', () => { | ||
const collection = new Collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
@@ -63,7 +65,7 @@ td.when(getName()).thenReturn('bar'); | ||
return expect(collection.existsInDatabase()).to.eventually.be.true; | ||
return expect(instance.existsInDatabase()).to.eventually.be.true; | ||
}); | ||
it('should return false if it does not exist in database', () => { | ||
const collection = new Collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
@@ -73,3 +75,3 @@ td.when(getName()).thenReturn('bar'); | ||
return expect(collection.existsInDatabase()).to.eventually.be.false; | ||
return expect(instance.existsInDatabase()).to.eventually.be.false; | ||
}); | ||
@@ -80,3 +82,3 @@ }); | ||
it('should return the number of documents in a collection', () => { | ||
const collection = new Collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
@@ -86,7 +88,7 @@ td.when(getName()).thenReturn('bar'); | ||
return expect(collection.count()).to.eventually.equal(1); | ||
return expect(instance.count()).to.eventually.equal(1); | ||
}); | ||
it('should fail if an unexpected error is thrown', () => { | ||
const collection = new Collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = collection({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const error = new Error('foobar'); | ||
@@ -97,3 +99,3 @@ | ||
return expect(collection.count()).to.eventually.be.rejectedWith(error); | ||
return expect(instance.count()).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -104,3 +106,3 @@ }); | ||
it('should hide internals', () => { | ||
const collection = new Collection(null, { getName }, 'foo'); | ||
const instance = collection(null, { getName }, 'foo'); | ||
const expected = { schema: 'bar', collection: 'foo' }; | ||
@@ -110,3 +112,3 @@ | ||
expect(collection.inspect()).to.deep.equal(expected); | ||
expect(instance.inspect()).to.deep.equal(expected); | ||
}); | ||
@@ -117,5 +119,5 @@ }); | ||
it('should return an instance of the proper class', () => { | ||
const instance = (new Collection()).add({}); | ||
const instance = collection().add({}); | ||
expect(instance).to.be.an.instanceof(CollectionAdd); | ||
expect(instance.getClassName()).to.equal('CollectionAdd'); | ||
}); | ||
@@ -125,5 +127,5 @@ | ||
const documents = [{ foo: 'bar' }, { foo: 'baz' }]; | ||
const instance = (new Collection()).add(documents); | ||
const instance = collection().add(documents); | ||
expect(instance._document).to.deep.equal(documents); | ||
expect(instance.getDocuments()).to.deep.equal(documents); | ||
}); | ||
@@ -133,5 +135,5 @@ | ||
const documents = [{ foo: 'bar' }, { foo: 'baz' }]; | ||
const instance = (new Collection()).add(documents[0], documents[1]); | ||
const instance = collection().add(documents[0], documents[1]); | ||
expect(instance._document).to.deep.equal(documents); | ||
expect(instance.getDocuments()).to.deep.equal(documents); | ||
}); | ||
@@ -144,11 +146,7 @@ }); | ||
const schema = 'bar'; | ||
const collection = 'baz'; | ||
const name = 'baz'; | ||
const query = 'true'; | ||
const instance = (new Collection(session, schema, collection)).remove(query); | ||
const instance = collection(session, schema, name).remove(query); | ||
expect(instance).to.be.an.instanceOf(CollectionRemove); | ||
expect(instance._session).to.deep.equal(session); | ||
expect(instance._schema).to.deep.equal(schema); | ||
expect(instance._collection).to.deep.equal(collection); | ||
expect(instance._query).to.deep.equal(query); | ||
expect(instance.getClassName()).to.equal('CollectionRemove'); | ||
}); | ||
@@ -161,13 +159,297 @@ }); | ||
const schema = 'bar'; | ||
const collection = 'baz'; | ||
const name = 'baz'; | ||
const query = 'true'; | ||
const instance = (new Collection(session, schema, collection)).modify(query); | ||
const instance = collection(session, schema, name).modify(query); | ||
expect(instance).to.be.an.instanceOf(CollectionModify); | ||
expect(instance._session).to.deep.equal(session); | ||
expect(instance._schema).to.deep.equal(schema); | ||
expect(instance._collection).to.deep.equal(collection); | ||
expect(instance._query).to.deep.equal(query); | ||
expect(instance.getClassName()).to.equal('CollectionModify'); | ||
}); | ||
}); | ||
context('replaceOne()', () => { | ||
let crudFind, crudModify; | ||
beforeEach('create fakes', () => { | ||
crudFind = td.function(); | ||
crudModify = td.function(); | ||
}); | ||
it('should return the result of executing a modify operation for a given document', () => { | ||
const collectionName = 'foo'; | ||
const documentId = 'bar'; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "${documentId}"`; | ||
const state = { rows_affected: 1 }; | ||
const expected = new Result(state); | ||
const session = { _client: { crudFind, crudModify } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session), { ignoreExtraArgs: true }).thenResolve(); | ||
td.when(crudModify(schemaName, collectionName, type, criteria), { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(instance.replaceOne(documentId, { prop: 'qux' })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should escape the id value', () => { | ||
const collectionName = 'foo'; | ||
const documentId = 'b\\\"ar'; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "b\\\\"ar"`; | ||
const state = { rows_affected: 1 }; | ||
const expected = new Result(state); | ||
const session = { _client: { crudFind, crudModify } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session), { ignoreExtraArgs: true }).thenResolve(); | ||
td.when(crudModify(schemaName, collectionName, type, criteria), { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(instance.replaceOne(documentId, { prop: 'qux' })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should ignore any additional `_id` property', () => { | ||
const collectionName = 'foo'; | ||
const documentId = 'bar'; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "${documentId}"`; | ||
const state = { rows_affected: 1 }; | ||
const expected = new Result(state); | ||
const session = { _client: { crudFind, crudModify } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
const operation = { | ||
source: { | ||
document_path: [{ | ||
type: 1, | ||
value: 'prop' | ||
}] | ||
}, | ||
operation: 3, | ||
value: { | ||
type: 2, | ||
literal: { | ||
type: 8, | ||
v_string: { | ||
value: 'quux' | ||
} | ||
} | ||
} | ||
}; | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session), { ignoreExtraArgs: true }).thenResolve(); | ||
td.when(crudModify(schemaName, collectionName, type, criteria, [operation]), { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(instance.replaceOne(documentId, { _id: 'qux', prop: 'quux' })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should fail if an unexpected error is thrown', () => { | ||
const collectionName = 'foo'; | ||
const documentId = 'bar'; | ||
const schemaName = 'baz'; | ||
const error = new Error('foobar'); | ||
const session = { _client: { crudFind, crudModify } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session), { ignoreExtraArgs: true }).thenResolve(); | ||
td.when(crudModify(schemaName, collectionName, Client.dataModel.DOCUMENT, `$._id == "${documentId}"`), { ignoreExtraArgs: true }).thenReject(error); | ||
return expect(instance.replaceOne(documentId, { prop: 'qux' })).to.eventually.be.rejectedWith(error); | ||
}); | ||
}); | ||
context('addOrReplaceOne()', () => { | ||
let crudInsert; | ||
beforeEach('create fakes', () => { | ||
crudInsert = td.function(); | ||
}); | ||
it('should return the result of executing a "upsert" operation for a given document', () => { | ||
const collectionName = 'foobar'; | ||
const rows = [[JSON.stringify({ name: 'bar', _id: 'foo' })]]; | ||
const state = { doc_ids: ['foo'] }; | ||
const expected = new Result(state); | ||
const schemaName = 'baz'; | ||
const session = { _client: { crudInsert } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudInsert(schemaName, collectionName, Client.dataModel.DOCUMENT, { rows }, { upsert: true })).thenResolve(state); | ||
return expect(instance.addOrReplaceOne('foo', { name: 'bar' })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should escape the id value', () => { | ||
const collectionName = 'foobar'; | ||
const rows = [[JSON.stringify({ name: 'bar', _id: 'fo"o' })]]; | ||
const state = { doc_ids: ['fo"o'] }; | ||
const expected = new Result(state); | ||
const schemaName = 'baz'; | ||
const session = { _client: { crudInsert } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudInsert(schemaName, collectionName, Client.dataModel.DOCUMENT, { rows }, { upsert: true })).thenResolve(state); | ||
return expect(instance.addOrReplaceOne('fo"o', { name: 'bar' })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should ignore any additional `_id` property', () => { | ||
const collectionName = 'foobar'; | ||
const rows = [[JSON.stringify({ _id: 'foo', name: 'bar' })]]; | ||
const state = { doc_ids: ['foo'] }; | ||
const expected = new Result(state); | ||
const schemaName = 'baz'; | ||
const session = { _client: { crudInsert } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudInsert(schemaName, collectionName, Client.dataModel.DOCUMENT, { rows }, { upsert: true })).thenResolve(state); | ||
return expect(instance.addOrReplaceOne('foo', { _id: 'baz', name: 'bar' })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should fail if an unexpected error is thrown', () => { | ||
const collectionName = 'foobar'; | ||
const rows = [[JSON.stringify({ name: 'bar', _id: 'foo' })]]; | ||
const schemaName = 'baz'; | ||
const session = { _client: { crudInsert } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
const error = new Error('bazqux'); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudInsert(schemaName, collectionName, Client.dataModel.DOCUMENT, { rows }, { upsert: true })).thenReject(error); | ||
return expect(instance.addOrReplaceOne('foo', { name: 'bar' })).to.eventually.be.rejectedWith(error); | ||
}); | ||
}); | ||
context('getOne()', () => { | ||
let crudFind; | ||
beforeEach('create fakes', () => { | ||
crudFind = td.function(); | ||
}); | ||
it('should return the document instance if it exists', () => { | ||
const collectionName = 'foobar'; | ||
const documentId = 'foo'; | ||
const expected = { _id: documentId, name: 'bar' }; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "${documentId}"`; | ||
const session = { _client: { crudFind } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session, schemaName, collectionName, type, [], criteria, any, any, any, any, td.callback([expected])), { ignoreExtraArgs: true }).thenResolve(); | ||
return expect(instance.getOne(documentId)).to.eventually.deep.equal(expected); | ||
}); | ||
it('should escape the id value', () => { | ||
const collectionName = 'foobar'; | ||
const documentId = 'fo\\"o'; | ||
const expected = { _id: documentId, name: 'bar' }; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "fo\\\\"o"`; | ||
const session = { _client: { crudFind } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session, schemaName, collectionName, type, [], criteria, any, any, any, any, td.callback([expected])), { ignoreExtraArgs: true }).thenResolve(); | ||
return expect(instance.getOne(documentId)).to.eventually.deep.equal(expected); | ||
}); | ||
it('should return `null` if the document does not exist', () => { | ||
const collectionName = 'foobar'; | ||
const documentId = 'foo'; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "${documentId}"`; | ||
const session = { _client: { crudFind } }; | ||
const instance = collection(session, { getName }, collectionName); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudFind(session, schemaName, collectionName, type, [], criteria, any, any, any, any, td.callback([])), { ignoreExtraArgs: true }).thenResolve(); | ||
return expect(instance.getOne(documentId)).to.eventually.be.null; | ||
}); | ||
}); | ||
context('removeOne()', () => { | ||
let crudRemove; | ||
beforeEach('create fakes', () => { | ||
crudRemove = td.function(); | ||
}); | ||
it('should return the document instance if it exists', () => { | ||
const collectionName = 'foobar'; | ||
const documentId = 'foo'; | ||
const state = { rows_affected: 1 }; | ||
const expected = new Result(state); | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "${documentId}"`; | ||
const instance = collection({ _client: { crudRemove } }, { getName }, collectionName); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudRemove(schemaName, collectionName, type, criteria, any, any)).thenResolve(state); | ||
return expect(instance.removeOne(documentId)).to.eventually.deep.equal(expected); | ||
}); | ||
it('should escape the id value', () => { | ||
const collectionName = 'foobar'; | ||
const documentId = 'fo\\"o'; | ||
const state = { rows_affected: 1 }; | ||
const expected = new Result(state); | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "fo\\\\"o"`; | ||
const instance = collection({ _client: { crudRemove } }, { getName }, collectionName); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudRemove(schemaName, collectionName, type, criteria, any, any)).thenResolve(state); | ||
return expect(instance.removeOne(documentId)).to.eventually.deep.equal(expected); | ||
}); | ||
it('should fail if an unexpected error is thrown', () => { | ||
const collectionName = 'foobar'; | ||
const documentId = 'foo'; | ||
const schemaName = 'baz'; | ||
const type = Client.dataModel.DOCUMENT; | ||
const criteria = `$._id == "${documentId}"`; | ||
const instance = collection({ _client: { crudRemove } }, { getName }, collectionName); | ||
const error = new Error('bazqux'); | ||
const any = td.matchers.anything(); | ||
td.when(getName()).thenReturn(schemaName); | ||
td.when(crudRemove(schemaName, collectionName, type, criteria, any, any)).thenReject(error); | ||
return expect(instance.removeOne(documentId)).to.eventually.be.rejectedWith(error); | ||
}); | ||
}); | ||
}); |
@@ -5,3 +5,2 @@ 'use strict'; | ||
const CollectionAdd = require('lib/DevAPI/CollectionAdd'); | ||
const Client = require('lib/Protocol/Client'); | ||
@@ -11,2 +10,3 @@ const Result = require('lib/DevAPI/Result'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const collectionAdd = require('lib/DevAPI/CollectionAdd'); | ||
const td = require('testdouble'); | ||
@@ -41,7 +41,13 @@ | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(collectionAdd().getClassName()).to.equal('CollectionAdd'); | ||
}); | ||
}); | ||
context('add()', () => { | ||
it('should be fluent', () => { | ||
const query = (new CollectionAdd()).add({ foo: 'bar' }); | ||
const query = collectionAdd().add({ foo: 'bar' }); | ||
expect(query).to.be.an.instanceOf(CollectionAdd); | ||
expect(query.add).to.be.a('function'); | ||
}); | ||
@@ -51,5 +57,5 @@ | ||
const expected = [{ foo: 'bar' }, { foo: 'baz' }]; | ||
const query = (new CollectionAdd()).add(expected); | ||
const query = collectionAdd().add(expected); | ||
expect(query._document).to.deep.equal(expected); | ||
expect(query.getDocuments()).to.deep.equal(expected); | ||
}); | ||
@@ -59,5 +65,5 @@ | ||
const expected = [{ foo: 'bar' }, { foo: 'baz' }]; | ||
const query = (new CollectionAdd()).add(expected[0], expected[1]); | ||
const query = collectionAdd().add(expected[0], expected[1]); | ||
expect(query._document).to.deep.equal(expected); | ||
expect(query.getDocuments()).to.deep.equal(expected); | ||
}); | ||
@@ -67,5 +73,5 @@ | ||
const expected = [{ foo: 'bar' }, { foo: 'baz' }]; | ||
const query = (new CollectionAdd(fakeSession, fakeSchema, null, [{ foo: 'bar' }])).add({ foo: 'baz' }); | ||
const query = collectionAdd(fakeSession, fakeSchema, null, [{ foo: 'bar' }]).add({ foo: 'baz' }); | ||
expect(query._document).to.deep.equal(expected); | ||
expect(query.getDocuments()).to.deep.equal(expected); | ||
}); | ||
@@ -75,5 +81,5 @@ | ||
const expected = [{ foo: 'bar' }, { foo: 'baz' }]; | ||
const query = (new CollectionAdd()).add({ foo: 'bar' }).add({ foo: 'baz' }); | ||
const query = collectionAdd().add({ foo: 'bar' }).add({ foo: 'baz' }); | ||
expect(query._document).to.deep.equal(expected); | ||
expect(query.getDocuments()).to.deep.equal(expected); | ||
}); | ||
@@ -85,7 +91,8 @@ }); | ||
const documents = [{ _id: 'foo', foo: 'bar' }, { _id: 'bar', bar: 'baz' }]; | ||
const state = { ok: true }; | ||
const state = { doc_ids: ['foo', 'bar'] }; | ||
const expected = new Result(state); | ||
const query = (new CollectionAdd(fakeSession, fakeSchema, 'collection')).add([documents[0]]).add(documents[1]); | ||
const query = collectionAdd(fakeSession, fakeSchema, 'collection').add([documents[0]]).add(documents[1]); | ||
const rows = [[JSON.stringify(documents[0])], [JSON.stringify(documents[1])]]; | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, [[JSON.stringify(documents[0])], [JSON.stringify(documents[1])]])).thenResolve(state); | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, { rows }, { upsert: false })).thenResolve(state); | ||
@@ -96,8 +103,9 @@ return expect(query.execute()).to.eventually.deep.equal(expected); | ||
it('should generate an id for documents that do not have one', () => { | ||
const state = { ok: true }; | ||
const state = { doc_ids: ['baz'] }; | ||
const expected = new Result(state); | ||
const query = (new CollectionAdd(fakeSession, fakeSchema, 'collection', [{ foo: 'bar' }])); | ||
const query = collectionAdd(fakeSession, fakeSchema, 'collection', [{ foo: 'bar' }]); | ||
const rows = [[JSON.stringify({ foo: 'bar', _id: 'baz' })]]; | ||
td.when(idGenerator()).thenReturn('baz'); | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, [[JSON.stringify({ foo: 'bar', _id: 'baz' })]])).thenResolve(state); | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, { rows }, { upsert: false })).thenResolve(state); | ||
@@ -108,7 +116,8 @@ return expect(query.execute()).to.eventually.deep.equal(expected); | ||
it('should append the ids of the added documents to the response state', () => { | ||
const state = { ok: true }; | ||
const query = (new CollectionAdd(fakeSession, fakeSchema, 'collection', [{ foo: 'bar' }])); | ||
const state = {}; | ||
const query = collectionAdd(fakeSession, fakeSchema, 'collection', [{ foo: 'bar' }]); | ||
const rows = [[JSON.stringify({ foo: 'bar', _id: 'baz' })]]; | ||
td.when(idGenerator()).thenReturn('baz'); | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, [[JSON.stringify({ foo: 'bar', _id: 'baz' })]])).thenResolve(state); | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, { rows }, { upsert: false })).thenResolve(state); | ||
@@ -120,3 +129,3 @@ return expect(query.execute()).to.be.fulfilled | ||
it('should return early if no documents were provided', () => { | ||
const query = (new CollectionAdd(fakeSession, fakeSchema, 'collection', [])); | ||
const query = collectionAdd(fakeSession, fakeSchema, 'collection', []); | ||
@@ -135,3 +144,3 @@ td.when(idGenerator(), { ignoreExtraArgs: true }).thenReturn(); | ||
const expected = [0]; | ||
const query = (new CollectionAdd(fakeSession, fakeSchema, 'collection', [{ _id: expected[0], name: 'foo' }])); | ||
const query = collectionAdd(fakeSession, fakeSchema, 'collection', [{ _id: expected[0], name: 'foo' }]); | ||
@@ -148,3 +157,16 @@ td.when(idGenerator(), { ignoreExtraArgs: true }).thenReturn(); | ||
}); | ||
it('should be able to generate an "upsert" message', () => { | ||
const doc = { _id: 'foo', name: 'bar' }; | ||
const state = { doc_ids: ['foo'] }; | ||
const expected = new Result(state); | ||
const rows = [[JSON.stringify(doc)]]; | ||
const options = { upsert: true }; | ||
const query = collectionAdd(fakeSession, fakeSchema, 'collection', null, options).add(doc); | ||
td.when(crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, { rows }, options)).thenResolve(state); | ||
return expect(query.execute()).to.eventually.deep.equal(expected); | ||
}); | ||
}); | ||
}); |
@@ -6,7 +6,6 @@ 'use strict'; | ||
// npm `test` script was updated to use NODE_PATH=. | ||
const BaseQuery = require('lib/DevAPI/BaseQuery'); | ||
const CollectionFind = require('lib/DevAPI/CollectionFind'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const collectionFind = require('lib/DevAPI/CollectionFind'); | ||
const td = require('testdouble'); | ||
@@ -18,6 +17,6 @@ | ||
describe('DevAPI Collection Find', () => { | ||
context('constructor', () => { | ||
it('should be an instance of BaseQuery', () => { | ||
expect(new CollectionFind()).to.be.an.instanceof(BaseQuery); | ||
describe('DevAPI CollectionFind', () => { | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(collectionFind().getClassName()).to.equal('CollectionFind'); | ||
}); | ||
@@ -28,14 +27,14 @@ }); | ||
it('should be fluent', () => { | ||
const query = (new CollectionFind()).bind('foo', 'bar'); | ||
const query = (collectionFind()).bind('foo', 'bar'); | ||
expect(query).to.be.an.instanceof(CollectionFind); | ||
expect(query.bind).to.be.a('function'); | ||
}); | ||
it('should do nothing if no argument is provided', () => { | ||
const query = new CollectionFind(); | ||
const bounds = Object.assign({}, query._bounds); | ||
const query = collectionFind(); | ||
const mapping = Object.assign({}, query.getBindings()); | ||
query.bind(); | ||
expect(query._bounds).to.deep.equal(bounds); | ||
expect(query.getBindings()).to.deep.equal(mapping); | ||
}); | ||
@@ -45,5 +44,5 @@ | ||
const expected = { foo: 'bar' }; | ||
const query = (new CollectionFind()).bind({ foo: 'bar' }); | ||
const query = (collectionFind()).bind({ foo: 'bar' }); | ||
expect(query._bounds).to.deep.equal(expected); | ||
expect(query.getBindings()).to.deep.equal(expected); | ||
}); | ||
@@ -53,5 +52,5 @@ | ||
const expected = { foo: 'bar' }; | ||
const query = (new CollectionFind()).bind('foo', 'bar'); | ||
const query = (collectionFind()).bind('foo', 'bar'); | ||
expect(query._bounds).to.deep.equal(expected); | ||
expect(query.getBindings()).to.deep.equal(expected); | ||
}); | ||
@@ -64,7 +63,7 @@ | ||
}; | ||
const query = (new CollectionFind()) | ||
const query = (collectionFind()) | ||
.bind('foo', 'bar') | ||
.bind('baz', 'qux'); | ||
expect(query._bounds).to.deep.equal(expected); | ||
expect(query.getBindings()).to.deep.equal(expected); | ||
}); | ||
@@ -74,7 +73,7 @@ | ||
const expected = { foo: 'baz' }; | ||
const query = (new CollectionFind()) | ||
const query = (collectionFind()) | ||
.bind('foo', 'bar') | ||
.bind('foo', 'baz'); | ||
expect(query._bounds).to.deep.equal(expected); | ||
expect(query.getBindings()).to.deep.equal(expected); | ||
}); | ||
@@ -87,10 +86,26 @@ | ||
}; | ||
const query = (new CollectionFind()) | ||
const query = (collectionFind()) | ||
.bind('foo', 'bar') | ||
.bind({ bar: 'baz' }); | ||
expect(query._bounds).to.deep.equal(expected); | ||
expect(query.getBindings()).to.deep.equal(expected); | ||
}); | ||
}); | ||
context('lockShared()', () => { | ||
it('should set the correct locking mode', () => { | ||
const query = (collectionFind()).lockShared(); | ||
expect(query.getLockingMode()).to.equal(1); | ||
}); | ||
}); | ||
context('lockExclusive()', () => { | ||
it('should set the correct locking mode', () => { | ||
const query = (collectionFind()).lockExclusive(); | ||
expect(query.getLockingMode()).to.equal(2); | ||
}); | ||
}); | ||
context('execute()', () => { | ||
@@ -114,3 +129,3 @@ let fakeSession, fakeSchema; | ||
const expected = new Result(state); | ||
const query = (new CollectionFind(fakeSession, fakeSchema, null, '1 == 1')); | ||
const query = collectionFind(fakeSession, fakeSchema, null, '1 == 1'); | ||
const any = td.matchers.anything(); | ||
@@ -127,3 +142,3 @@ const execute = fakeSession._client.crudFind(any, any, any, any, any, '1 == 1'); | ||
const expected = new Result(state); | ||
const query = (new CollectionFind(fakeSession, fakeSchema)).limit(10, 0); | ||
const query = collectionFind(fakeSession, fakeSchema).limit(10, 0); | ||
const any = td.matchers.anything(); | ||
@@ -137,6 +152,6 @@ const execute = fakeSession._client.crudFind(any, any, any, any, any, any, any, any, any, { row_count: 10, offset: 0 }); | ||
it('should include the bounds', () => { | ||
it('should include the value mapping', () => { | ||
const state = { ok: true }; | ||
const expected = new Result(state); | ||
const query = (new CollectionFind(fakeSession, fakeSchema)).bind('foo', 'bar'); | ||
const query = collectionFind(fakeSession, fakeSchema).bind('foo', 'bar'); | ||
const any = td.matchers.anything(); | ||
@@ -149,3 +164,30 @@ const execute = fakeSession._client.crudFind(any, any, any, any, any, any, any, any, any, any, any, any, { foo: 'bar' }); | ||
}); | ||
it('should set the correct default locking mode', () => { | ||
const state = { ok: true }; | ||
const expected = new Result(state); | ||
const query = collectionFind(fakeSession, fakeSchema); | ||
// default locking mode | ||
const mode = 0; | ||
const any = td.matchers.anything(); | ||
const execute = fakeSession._client.crudFind(any, any, any, any, any, any, any, any, any, any, any, any, any, any, mode); | ||
td.when(execute, { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(query.execute()).eventually.deep.equal(expected); | ||
}); | ||
it('should include the latest specified locking mode', () => { | ||
const state = { ok: true }; | ||
const expected = new Result(state); | ||
const query = collectionFind(fakeSession, fakeSchema).lockShared().lockExclusive(); | ||
const mode = 2; | ||
const any = td.matchers.anything(); | ||
const execute = fakeSession._client.crudFind(any, any, any, any, any, any, any, any, any, any, any, any, any, any, mode); | ||
td.when(execute, { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(query.execute()).eventually.deep.equal(expected); | ||
}); | ||
}); | ||
}); |
@@ -7,6 +7,6 @@ 'use strict'; | ||
const Client = require('lib/Protocol/Client'); | ||
const CollectionModify = require('lib/DevAPI/CollectionModify'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const collectionModify = require('lib/DevAPI/CollectionModify'); | ||
const td = require('testdouble'); | ||
@@ -31,5 +31,11 @@ | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(collectionModify().getClassName()).to.equal('CollectionModify'); | ||
}); | ||
}); | ||
context('execute()', () => { | ||
it('should fail if a condition query is not provided', () => { | ||
const operation = new CollectionModify(); | ||
const operation = collectionModify(); | ||
@@ -40,3 +46,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('remove needs a valid condition'); | ||
it('should fail if a condition query is empty', () => { | ||
const operation = new CollectionModify(null, null, null, ''); | ||
const operation = collectionModify(null, null, null, ''); | ||
@@ -47,3 +53,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('remove needs a valid condition'); | ||
it('should fail if the condition is not valid', () => { | ||
const operation = new CollectionModify(null, null, null, ' '); | ||
const operation = collectionModify(null, null, null, ' '); | ||
@@ -54,3 +60,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('remove needs a valid condition'); | ||
it('should fail if the operation results in an error', () => { | ||
const operation = new CollectionModify({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const operation = collectionModify({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const error = new Error('foobar'); | ||
@@ -65,3 +71,3 @@ | ||
it('should succeed if the operation succeed with the state of the operation', () => { | ||
const operation = new CollectionModify({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const operation = collectionModify({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const state = { foo: 'bar' }; | ||
@@ -68,0 +74,0 @@ const expected = new Result(state); |
@@ -7,6 +7,6 @@ 'use strict'; | ||
const Client = require('lib/Protocol/Client'); | ||
const CollectionRemove = require('lib/DevAPI/CollectionRemove'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const collectionRemove = require('lib/DevAPI/CollectionRemove'); | ||
const td = require('testdouble'); | ||
@@ -31,5 +31,11 @@ | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(collectionRemove().getClassName()).to.equal('CollectionRemove'); | ||
}); | ||
}); | ||
context('execute()', () => { | ||
it('should fail if a condition query is not provided', () => { | ||
const operation = new CollectionRemove(); | ||
const operation = collectionRemove(); | ||
@@ -40,3 +46,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('remove needs a valid condition'); | ||
it('should fail if a condition query is empty', () => { | ||
const operation = new CollectionRemove(null, null, null, ''); | ||
const operation = collectionRemove(null, null, null, ''); | ||
@@ -47,3 +53,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('remove needs a valid condition'); | ||
it('should fail if the condition is not valid', () => { | ||
const operation = new CollectionRemove(null, null, null, ' '); | ||
const operation = collectionRemove(null, null, null, ' '); | ||
@@ -54,3 +60,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('remove needs a valid condition'); | ||
it('should fail if the operation results in an error', () => { | ||
const operation = new CollectionRemove({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const operation = collectionRemove({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const error = new Error('foobar'); | ||
@@ -65,3 +71,3 @@ | ||
it('should succeed if the operation succeed with the state of the operation', () => { | ||
const operation = new CollectionRemove({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const operation = collectionRemove({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const state = { foo: 'bar' }; | ||
@@ -68,0 +74,0 @@ const expected = new Result(state); |
@@ -100,2 +100,5 @@ 'use strict'; | ||
beforeEach('set environment variables', () => { | ||
process.env.APPDATA = process.env.APPDATA || '%APPDATA%'; | ||
process.env.PROGRAMDATA = process.env.PROGRAMDATA || '%PROGRAMDATA%'; | ||
platform = process.platform; | ||
@@ -108,4 +111,4 @@ Object.defineProperty(process, 'platform', { value: 'win32' }); | ||
systemConfigFile = path.resolve(os.homedir(), 'PROGRAMDATA', 'MySQL', filename); | ||
userConfigFile = path.resolve(os.homedir(), 'APPDATA', 'MySQL', filename); | ||
systemConfigFile = path.join(process.env.PROGRAMDATA, 'MySQL', filename); | ||
userConfigFile = path.join(process.env.APPDATA, 'MySQL', filename); | ||
}); | ||
@@ -279,2 +282,4 @@ | ||
beforeEach('set environment variables', () => { | ||
process.env.APPDATA = process.env.APPDATA || '%APPDATA%'; | ||
platform = process.platform; | ||
@@ -285,3 +290,3 @@ Object.defineProperty(process, 'platform', { value: 'win32' }); | ||
beforeEach('set scope variables', () => { | ||
userConfigFile = path.resolve(os.homedir(), 'APPDATA', 'MySQL', 'sessions.json'); | ||
userConfigFile = path.join(process.env.APPDATA, 'MySQL', 'sessions.json'); | ||
}); | ||
@@ -377,2 +382,4 @@ | ||
beforeEach('set environment variables', () => { | ||
process.env.APPDATA = process.env.APPDATA || '%APPDATA%'; | ||
platform = process.platform; | ||
@@ -383,3 +390,3 @@ Object.defineProperty(process, 'platform', { value: 'win32' }); | ||
beforeEach('set scope variables', () => { | ||
userConfigFile = path.resolve(os.homedir(), 'APPDATA', 'MySQL', 'sessions.json'); | ||
userConfigFile = path.join(process.env.APPDATA, 'MySQL', 'sessions.json'); | ||
}); | ||
@@ -386,0 +393,0 @@ |
@@ -6,5 +6,5 @@ 'use strict'; | ||
// npm `test` script was updated to use NODE_PATH=. | ||
const Schema = require('lib/DevAPI/Schema'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const schema = require('lib/DevAPI/Schema'); | ||
const td = require('testdouble'); | ||
@@ -29,15 +29,15 @@ | ||
it('should return true if the schema exists in database', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
td.when(sqlStmtExecute('SHOW DATABASES LIKE ?', ['foo'], td.callback(['foo']))).thenResolve(); | ||
return expect(schema.existsInDatabase()).to.eventually.be.true; | ||
return expect(instance.existsInDatabase()).to.eventually.be.true; | ||
}); | ||
it('should return false if the schema does not exist in database', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
td.when(sqlStmtExecute('SHOW DATABASES LIKE ?', ['foo'], td.callback([]))).thenResolve(); | ||
return expect(schema.existsInDatabase()).to.eventually.be.false; | ||
return expect(instance.existsInDatabase()).to.eventually.be.false; | ||
}); | ||
@@ -48,15 +48,20 @@ }); | ||
it('should return an empty object if there are no collections', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
td.when(sqlStmtExecute('list_objects', ['foo'], td.callback([]), null, 'xplugin')).thenResolve(); | ||
return expect(schema.getCollections()).to.eventually.be.an.instanceof(Object).and.be.empty; | ||
return expect(instance.getCollections()).to.eventually.be.an.instanceof(Object).and.be.empty; | ||
}); | ||
it('should return an object containing the existing collections', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = instance.getCollection('foo').inspect(); | ||
td.when(sqlStmtExecute('list_objects', ['foo'], td.callback(['foo', 'COLLECTION']), null, 'xplugin')).thenResolve(); | ||
return expect(schema.getCollections()).to.eventually.deep.equal({ foo: schema.getCollection('foo') }); | ||
return expect(instance.getCollections()).to.eventually.be.fulfilled | ||
.then(actual => { | ||
expect(actual).to.have.all.keys(['foo']); | ||
expect(actual.foo.inspect()).to.deep.equal(expected); | ||
}); | ||
}); | ||
@@ -68,4 +73,4 @@ }); | ||
it('should return the existing collection if the option to re-use is enabled', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = schema.getCollection('bar'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = instance.getCollection('bar').inspect(); | ||
const error = new Error(); | ||
@@ -76,7 +81,10 @@ error.info = { code: 1050 }; | ||
return expect(schema.createCollection('bar', { ReuseExistingObject: true })).to.eventually.deep.equal(expected); | ||
return expect(instance.createCollection('bar', { ReuseExistingObject: true })).to.eventually.be.fulfilled | ||
.then(actual => { | ||
expect(actual.inspect()).deep.equal(expected); | ||
}); | ||
}); | ||
it('should fail if the option to re-use is disabled', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const error = new Error(); | ||
@@ -87,3 +95,3 @@ error.info = { code: 1050 }; | ||
return expect(schema.createCollection('bar')).to.eventually.be.rejectedWith(error); | ||
return expect(instance.createCollection('bar')).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -94,12 +102,15 @@ }); | ||
it('should return a newly created collection', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = schema.getCollection('bar'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = instance.getCollection('bar').inspect(); | ||
td.when(sqlStmtExecute('create_collection', ['foo', 'bar'], null, null, 'xplugin')).thenResolve(); | ||
return expect(schema.createCollection('bar')).to.eventually.deep.equal(expected); | ||
return expect(instance.createCollection('bar')).to.eventually.be.fulfilled | ||
.then(actual => { | ||
expect(actual.inspect()).to.deep.equal(expected); | ||
}); | ||
}); | ||
it('should fail if some unexpected error is thrown', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const error = new Error(); | ||
@@ -110,7 +121,7 @@ error.info = {}; | ||
return expect(schema.createCollection('bar')).to.eventually.be.rejectedWith(error); | ||
return expect(instance.createCollection('bar')).to.eventually.be.rejectedWith(error); | ||
}); | ||
it('should fail if some unexpected error is thrown even if the option to re-use is enabled', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const error = new Error(); | ||
@@ -121,3 +132,3 @@ error.info = {}; | ||
return expect(schema.createCollection('bar', { ReuseExistingObject: true })).to.eventually.be.rejectedWith(error); | ||
return expect(instance.createCollection('bar', { ReuseExistingObject: true })).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -130,3 +141,3 @@ }); | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, name); | ||
const instance = schema({ _client: { sqlStmtExecute } }, name); | ||
const collection = 'bar'; | ||
@@ -136,3 +147,3 @@ | ||
return expect(schema.dropCollection(collection)).to.eventually.be.true; | ||
return expect(instance.dropCollection(collection)).to.eventually.be.true; | ||
}); | ||
@@ -142,3 +153,3 @@ | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, name); | ||
const instance = schema({ _client: { sqlStmtExecute } }, name); | ||
const collection = 'bar'; | ||
@@ -150,3 +161,3 @@ const error = new Error(); | ||
return expect(schema.dropCollection(collection)).to.eventually.be.true; | ||
return expect(instance.dropCollection(collection)).to.eventually.be.true; | ||
}); | ||
@@ -156,3 +167,3 @@ | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, name); | ||
const instance = schema({ _client: { sqlStmtExecute } }, name); | ||
const collection = 'bar'; | ||
@@ -163,3 +174,3 @@ const error = new Error('foobar'); | ||
return expect(schema.dropCollection(collection)).to.eventually.be.rejectedWith(error); | ||
return expect(instance.dropCollection(collection)).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -170,25 +181,33 @@ }); | ||
it('should return an empty object if there are no tables', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
td.when(sqlStmtExecute('list_objects', ['foo'], td.callback([]), null, 'xplugin')).thenResolve(); | ||
return expect(schema.getTables()).to.eventually.be.an.instanceof(Object).and.be.empty; | ||
return expect(instance.getTables()).to.eventually.be.an.instanceof(Object).and.be.empty; | ||
}); | ||
it('should return an object containing the existing tables', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = schema.getTable('bar'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = instance.getTable('bar').inspect(); | ||
td.when(sqlStmtExecute('list_objects', ['foo'], td.callback(['bar', 'TABLE']), null, 'xplugin')).thenResolve(); | ||
return expect(schema.getTables()).to.eventually.deep.equal({ bar: expected }); | ||
return expect(instance.getTables()).to.eventually.be.fulfilled | ||
.then(actual => { | ||
expect(actual).to.have.all.keys('bar'); | ||
expect(actual.bar.inspect()).to.deep.equal(expected); | ||
}); | ||
}); | ||
it('should return an object containing the existing views', () => { | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = schema.getTable('bar'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const expected = instance.getTable('bar').inspect(); | ||
td.when(sqlStmtExecute('list_objects', ['foo'], td.callback(['bar', 'VIEW']), null, 'xplugin')).thenResolve(); | ||
return expect(schema.getTables()).to.eventually.deep.equal({ bar: expected }); | ||
return expect(instance.getTables()).to.eventually.be.fulfilled | ||
.then(actual => { | ||
expect(actual).to.have.all.keys('bar'); | ||
expect(actual.bar.inspect()).to.deep.equal(expected); | ||
}); | ||
}); | ||
@@ -200,3 +219,3 @@ }); | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const table = 'bar'; | ||
@@ -206,3 +225,3 @@ | ||
return expect(schema.dropTable(table)).to.eventually.be.true; | ||
return expect(instance.dropTable(table)).to.eventually.be.true; | ||
}); | ||
@@ -212,3 +231,3 @@ | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const table = 'bar'; | ||
@@ -220,3 +239,3 @@ const error = new Error(); | ||
return expect(schema.dropTable(table)).to.eventually.be.true; | ||
return expect(instance.dropTable(table)).to.eventually.be.true; | ||
}); | ||
@@ -226,3 +245,3 @@ | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const table = 'bar'; | ||
@@ -233,3 +252,3 @@ const error = new Error('foobar'); | ||
return expect(schema.dropTable(table)).to.eventually.be.rejectedWith(error); | ||
return expect(instance.dropTable(table)).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -241,3 +260,3 @@ }); | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const view = 'bar'; | ||
@@ -247,3 +266,3 @@ | ||
return expect(schema.dropView(view)).to.eventually.be.true; | ||
return expect(instance.dropView(view)).to.eventually.be.true; | ||
}); | ||
@@ -253,3 +272,3 @@ | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const view = 'bar'; | ||
@@ -261,3 +280,3 @@ const error = new Error(); | ||
return expect(schema.dropView(view)).to.eventually.be.true; | ||
return expect(instance.dropView(view)).to.eventually.be.true; | ||
}); | ||
@@ -267,3 +286,3 @@ | ||
const name = 'foo'; | ||
const schema = new Schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const instance = schema({ _client: { sqlStmtExecute } }, 'foo'); | ||
const view = 'bar'; | ||
@@ -274,3 +293,3 @@ const error = new Error('foobar'); | ||
return expect(schema.dropView(view)).to.eventually.be.rejectedWith(error); | ||
return expect(instance.dropView(view)).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -281,8 +300,8 @@ }); | ||
it('should hide internals', () => { | ||
const schema = new Schema(null, 'foobar'); | ||
const instance = schema(null, 'foobar'); | ||
const expected = { schema: 'foobar' }; | ||
schema.inspect().should.deep.equal(expected); | ||
instance.inspect().should.deep.equal(expected); | ||
}); | ||
}); | ||
}); |
@@ -63,3 +63,3 @@ 'use strict'; | ||
expect(schema).to.be.an.instanceof(Schema); | ||
expect(schema.getClassName()).to.equal('Schema'); | ||
}); | ||
@@ -91,7 +91,7 @@ | ||
it('should return a clean object with the session properties', () => { | ||
const properties = { dbUser: 'foo', dbPassword: 'bar', socketFactory: { createSocket }, ssl: false }; | ||
const properties = { auth: 'PLAIN', dbUser: 'foo', dbPassword: 'bar', socketFactory: { createSocket }, ssl: false }; | ||
const session = new Session(properties); | ||
const expected = { dbUser: 'foo' }; | ||
td.when(capabilitiesGet()).thenResolve({}); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
@@ -129,3 +129,3 @@ return expect(session.connect()).to.be.fulfilled | ||
const session = new Session(properties); | ||
const expected = { foo: 'bar' }; | ||
const expected = { 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }; | ||
@@ -144,3 +144,3 @@ td.when(enableSSL({})).thenResolve(); | ||
td.when(enableSSL(), { ignoreExtraArgs: true }).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({}); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
@@ -150,3 +150,3 @@ return expect(session.connect()).to.be.fulfilled | ||
expect(td.explain(enableSSL).callCount).to.equal(0); | ||
expect(session._serverCapabilities).to.be.empty; | ||
expect(session._serverCapabilities.tls).to.be.undefined; | ||
}); | ||
@@ -160,3 +160,3 @@ }); | ||
td.when(enableSSL({})).thenReject(new Error()); | ||
td.when(capabilitiesGet()).thenResolve({ foo: 'bar' }); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
@@ -172,3 +172,3 @@ return expect(session.connect()).to.be.rejected | ||
td.when(enableSSL({ foo: 'bar' })).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({}); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
@@ -183,3 +183,3 @@ return expect(session.connect()).to.be.fulfilled; | ||
td.when(enableSSL({})).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({ tls: true }); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'], tls: true }); | ||
@@ -197,9 +197,51 @@ return expect(session.connect()).to.be.fulfilled | ||
td.when(enableSSL({})).thenReject(error); | ||
td.when(capabilitiesGet()).thenResolve({}); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
return expect(session.connect()).to.be.rejected; | ||
}); | ||
it('should select the default authentication mechanism', () => { | ||
const properties = { dbUser: 'foo', dbPassword: 'bar', socketFactory: { createSocket } }; | ||
const session = new Session(properties); | ||
td.when(enableSSL({})).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'], tls: true }); | ||
return expect(session.connect()).to.be.fulfilled | ||
.then(session => expect(session.inspect()).to.deep.include({ auth: 'PLAIN' })); | ||
}); | ||
it('should override the default authentication mechanism with the one provided by the user', () => { | ||
const properties = { auth: 'MYSQL41', dbUser: 'foo', dbPassword: 'bar', socketFactory: { createSocket } }; | ||
const session = new Session(properties); | ||
td.when(enableSSL({})).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'], tls: true }); | ||
return expect(session.connect()).to.be.fulfilled | ||
.then(session => expect(session.inspect()).to.deep.include({ auth: 'MYSQL41' })); | ||
}); | ||
}); | ||
context('insecure connections', () => { | ||
it('should select the default authentication mechanism', () => { | ||
const properties = { dbUser: 'foo', dbPassword: 'bar', socketFactory: { createSocket }, ssl: false }; | ||
const session = new Session(properties); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
return expect(session.connect()).to.be.fulfilled | ||
.then(session => expect(session.inspect()).to.deep.include({ auth: 'MYSQL41' })); | ||
}); | ||
}); | ||
context('failover', () => { | ||
let enableSSL; | ||
beforeEach('create fakes', () => { | ||
enableSSL = td.function(); | ||
Client.prototype.enableSSL = enableSSL; | ||
}); | ||
it('should failover to the next available address if the connection fails', () => { | ||
@@ -214,3 +256,3 @@ const endpoints = [{ host: 'foo', port: 1 }, { host: 'bar', port: 2 }]; | ||
td.when(capabilitiesGet()).thenResolve({}); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
td.when(createSocket(td.matchers.contains({ host: 'foo' }))).thenReject(error); | ||
@@ -259,3 +301,3 @@ td.when(createSocket(td.matchers.contains({ host: 'bar' }))).thenResolve(new Duplex()); | ||
td.when(capabilitiesGet()).thenResolve({}); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
// failover restarts from the highest priority address | ||
@@ -269,2 +311,56 @@ td.when(createSocket(), { ignoreExtraArgs: true }).thenResolve(new Duplex()); | ||
}); | ||
it('should select the default authentication mechanism for secure connections', () => { | ||
const endpoints = [{ host: 'foo', port: 1 }, { host: 'bar', port: 2 }]; | ||
const properties = { dbUser: 'baz', dbPassword: 'qux', endpoints, socketFactory: { createSocket }, ssl: true }; | ||
const session = new Session(properties); | ||
const expected = { auth: 'PLAIN', dbUser: 'baz', host: 'bar', port: 2, ssl: true }; | ||
const error = new Error(); | ||
error.code = 'ENOTFOUND'; | ||
td.when(enableSSL({})).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'], tls: true }); | ||
td.when(createSocket(td.matchers.contains({ host: 'foo' }))).thenReject(error); | ||
td.when(createSocket(td.matchers.contains({ host: 'bar' }))).thenResolve(new Duplex()); | ||
return expect(session.connect()).to.be.fulfilled | ||
.then(session => expect(session.inspect()).to.deep.include(expected)); | ||
}); | ||
it('should select the default authentication mechanism for insecure connections', () => { | ||
const endpoints = [{ host: 'foo', port: 1 }, { host: 'bar', port: 2 }]; | ||
const properties = { dbUser: 'baz', dbPassword: 'qux', endpoints, socketFactory: { createSocket }, ssl: false }; | ||
const session = new Session(properties); | ||
const expected = { auth: 'MYSQL41', dbUser: 'baz', host: 'bar', port: 2, ssl: false }; | ||
const error = new Error(); | ||
error.code = 'ENOTFOUND'; | ||
td.when(enableSSL({})).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'] }); | ||
td.when(createSocket(td.matchers.contains({ host: 'foo' }))).thenReject(error); | ||
td.when(createSocket(td.matchers.contains({ host: 'bar' }))).thenResolve(new Duplex()); | ||
return expect(session.connect()).to.be.fulfilled | ||
.then(session => expect(session.inspect()).to.deep.include(expected)); | ||
}); | ||
it('should override the default authentication mechanism with the one provided by the user', () => { | ||
const endpoints = [{ host: 'foo', port: 1 }, { host: 'bar', port: 2 }]; | ||
const properties = { auth: 'MYSQL41', dbUser: 'baz', dbPassword: 'qux', endpoints, socketFactory: { createSocket }, ssl: true }; | ||
const session = new Session(properties); | ||
const expected = { auth: 'MYSQL41', dbUser: 'baz', host: 'bar', port: 2, ssl: true }; | ||
const error = new Error(); | ||
error.code = 'ENOTFOUND'; | ||
td.when(enableSSL({})).thenResolve(); | ||
td.when(capabilitiesGet()).thenResolve({ 'authentication.mechanisms': ['PLAIN', 'MYSQL41'], tls: true }); | ||
td.when(createSocket(td.matchers.contains({ host: 'foo' }))).thenReject(error); | ||
td.when(createSocket(td.matchers.contains({ host: 'bar' }))).thenResolve(new Duplex()); | ||
return expect(session.connect()).to.be.fulfilled | ||
.then(session => expect(session.inspect()).to.deep.include(expected)); | ||
}); | ||
}); | ||
@@ -271,0 +367,0 @@ }); |
@@ -6,10 +6,11 @@ 'use strict'; | ||
// npm `test` script was updated to use NODE_PATH=. | ||
const Table = require('lib/DevAPI/Table'); | ||
const TableDelete = require('lib/DevAPI/TableDelete'); | ||
const TableInsert = require('lib/DevAPI/TableInsert'); | ||
const TableSelect = require('lib/DevAPI/TableSelect'); | ||
const TableUpdate = require('lib/DevAPI/TableUpdate'); | ||
const expect = require('chai').expect; | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const table = require('lib/DevAPI/Table'); | ||
const td = require('testdouble'); | ||
chai.use(chaiAsPromised); | ||
const expect = chai.expect; | ||
describe('Table', () => { | ||
@@ -29,5 +30,5 @@ let sqlStmtExecute, getName; | ||
it('should return the table name', () => { | ||
const table = new Table(null, null, 'foobar'); | ||
const instance = table(null, null, 'foobar'); | ||
expect(table.getName()).to.equal('foobar'); | ||
expect(instance.getName()).to.equal('foobar'); | ||
}); | ||
@@ -38,3 +39,3 @@ }); | ||
it('should return true if the table exists in database', () => { | ||
const table = new Table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.TABLES WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
@@ -45,7 +46,7 @@ | ||
return expect(table.existsInDatabase()).to.eventually.be.true; | ||
return expect(instance.existsInDatabase()).to.eventually.be.true; | ||
}); | ||
it('should return false if the table does not exist in database', () => { | ||
const table = new Table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.TABLES WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
@@ -56,3 +57,3 @@ | ||
return expect(table.existsInDatabase()).to.eventually.be.false; | ||
return expect(instance.existsInDatabase()).to.eventually.be.false; | ||
}); | ||
@@ -63,3 +64,3 @@ }); | ||
it('should return true if the table exists in database', () => { | ||
const table = new Table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.VIEWS WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
@@ -70,7 +71,7 @@ | ||
return expect(table.isView()).to.eventually.be.true; | ||
return expect(instance.isView()).to.eventually.be.true; | ||
}); | ||
it('should return false if the table does not exist in database', () => { | ||
const table = new Table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const query = 'SELECT COUNT(*) cnt FROM information_schema.VIEWS WHERE TABLE_CATALOG = ? AND TABLE_SCHEMA = ? AND TABLE_NAME = ? HAVING COUNT(*) = 1'; | ||
@@ -81,3 +82,3 @@ | ||
return expect(table.isView()).to.eventually.be.false; | ||
return expect(instance.isView()).to.eventually.be.false; | ||
}); | ||
@@ -88,5 +89,5 @@ }); | ||
it('should return an instance of the proper class', () => { | ||
const instance = (new Table()).select(); | ||
const instance = table().select(); | ||
expect(instance).to.be.an.instanceof(TableSelect); | ||
expect(instance.getClassName()).to.equal('TableSelect'); | ||
}); | ||
@@ -96,5 +97,5 @@ | ||
const expressions = ['foo', 'bar']; | ||
const instance = (new Table()).select(expressions); | ||
const instance = table().select(expressions); | ||
expect(instance._projection).to.deep.equal(expressions); | ||
expect(instance.getProjection()).to.deep.equal(expressions); | ||
}); | ||
@@ -104,5 +105,5 @@ | ||
const expressions = ['foo', 'bar']; | ||
const instance = (new Table()).select(expressions[0], expressions[1]); | ||
const instance = table().select(expressions[0], expressions[1]); | ||
expect(instance._projection).to.deep.equal(expressions); | ||
expect(instance.getProjection()).to.deep.equal(expressions); | ||
}); | ||
@@ -113,5 +114,5 @@ }); | ||
it('should return an instance of the proper class', () => { | ||
const instance = (new Table()).insert([]); | ||
const instance = table().insert([]); | ||
expect(instance).to.be.an.instanceof(TableInsert); | ||
expect(instance.getClassName()).to.equal('TableInsert'); | ||
}); | ||
@@ -121,5 +122,5 @@ | ||
const expressions = ['foo', 'bar']; | ||
const instance = (new Table()).insert(expressions); | ||
const instance = table().insert(expressions); | ||
expect(instance._fields).to.deep.equal(expressions); | ||
expect(instance.getFields()).to.deep.equal(expressions); | ||
}); | ||
@@ -129,5 +130,5 @@ | ||
const expressions = ['foo', 'bar']; | ||
const instance = (new Table()).insert(expressions[0], expressions[1]); | ||
const instance = table().insert(expressions[0], expressions[1]); | ||
expect(instance._fields).to.deep.equal(expressions); | ||
expect(instance.getFields()).to.deep.equal(expressions); | ||
}); | ||
@@ -137,11 +138,11 @@ | ||
const expressions = ['foo', 'bar']; | ||
const instance = (new Table()).insert({ foo: 'baz', bar: 'qux' }); | ||
const instance = table().insert({ foo: 'baz', bar: 'qux' }); | ||
expect(instance._fields).to.deep.equal(expressions); | ||
expect(instance.getFields()).to.deep.equal(expressions); | ||
}); | ||
it('should throw an error if the fields are invalid', () => { | ||
const table = new Table(); | ||
const instance = table(); | ||
expect(() => table.insert()).to.throw(Error); | ||
expect(() => instance.insert()).to.throw(Error); | ||
}); | ||
@@ -152,3 +153,3 @@ }); | ||
it('should return the number of records found', () => { | ||
const table = new Table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const count = 3; | ||
@@ -159,7 +160,7 @@ | ||
return expect(table.count()).to.eventually.equal(count); | ||
return expect(instance.count()).to.eventually.equal(count); | ||
}); | ||
it('should fail if an expected error is thrown', () => { | ||
const table = new Table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const instance = table({ _client: { sqlStmtExecute } }, { getName }, 'foo'); | ||
const error = new Error('foobar'); | ||
@@ -170,3 +171,3 @@ | ||
return expect(table.count()).to.eventually.be.rejectedWith(error); | ||
return expect(instance.count()).to.eventually.be.rejectedWith(error); | ||
}); | ||
@@ -177,3 +178,3 @@ }); | ||
it('should hide internals', () => { | ||
const table = new Table(null, { getName }, 'foo'); | ||
const instance = table(null, { getName }, 'foo'); | ||
const expected = { schema: 'bar', table: 'foo' }; | ||
@@ -183,3 +184,3 @@ | ||
expect(table.inspect()).to.deep.equal(expected); | ||
expect(instance.inspect()).to.deep.equal(expected); | ||
}); | ||
@@ -192,12 +193,7 @@ }); | ||
const schema = 'bar'; | ||
const table = 'baz'; | ||
const name = 'baz'; | ||
const query = 'true'; | ||
const instance = (new Table(session, schema, table)).delete(query); | ||
const instance = (table(session, schema, name)).delete(query); | ||
expect(instance).to.be.an.instanceOf(TableDelete); | ||
expect(instance._session).to.deep.equal(session); | ||
expect(instance._schema).to.deep.equal(schema); | ||
expect(instance._table).to.deep.equal(table); | ||
expect(instance._query).to.deep.equal(query); | ||
expect(instance.where).to.be.a('function'); | ||
expect(instance.getClassName()).to.equal('TableDelete'); | ||
}); | ||
@@ -210,14 +206,9 @@ }); | ||
const schema = 'bar'; | ||
const table = 'baz'; | ||
const name = 'baz'; | ||
const query = 'true'; | ||
const instance = (new Table(session, schema, table)).update(query); | ||
const instance = (table(session, schema, name)).update(query); | ||
expect(instance).to.be.an.instanceOf(TableUpdate); | ||
expect(instance._session).to.deep.equal(session); | ||
expect(instance._schema).to.deep.equal(schema); | ||
expect(instance._table).to.deep.equal(table); | ||
expect(instance._query).to.deep.equal(query); | ||
expect(instance.where).to.be.a('function'); | ||
expect(instance.getClassName()).to.equal('TableUpdate'); | ||
}); | ||
}); | ||
}); |
@@ -7,6 +7,6 @@ 'use strict'; | ||
const Client = require('lib/Protocol/Client'); | ||
const TableDelete = require('lib/DevAPI/TableDelete'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const tableDelete = require('lib/DevAPI/TableDelete'); | ||
const td = require('testdouble'); | ||
@@ -31,8 +31,14 @@ | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(tableDelete().getClassName()).to.equal('TableDelete'); | ||
}); | ||
}); | ||
context('where()', () => { | ||
it('should set the operation condition', () => { | ||
const query = 'foo'; | ||
const operation = (new TableDelete()).where(query); | ||
const operation = tableDelete().where(query); | ||
expect(operation._query).to.equal(query); | ||
expect(operation.getCriteria()).to.equal(query); | ||
}); | ||
@@ -43,9 +49,7 @@ }); | ||
it('should fail if a condition query is not provided', () => { | ||
const operation = new TableDelete(); | ||
return expect(operation.execute()).to.eventually.be.rejectedWith('delete needs a valid condition'); | ||
return expect(tableDelete().execute()).to.eventually.be.rejectedWith('delete needs a valid condition'); | ||
}); | ||
it('should fail if a condition query is empty', () => { | ||
const operation = new TableDelete(null, null, null, ''); | ||
const operation = tableDelete(null, null, null, ''); | ||
@@ -56,3 +60,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('delete needs a valid condition'); | ||
it('should fail if a condition query is not valid', () => { | ||
const operation = new TableDelete(null, null, null, ' '); | ||
const operation = tableDelete(null, null, null, ' '); | ||
@@ -63,3 +67,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('delete needs a valid condition'); | ||
it('should fail if the operation results in an error', () => { | ||
const operation = new TableDelete({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const operation = tableDelete({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const error = new Error('foobar'); | ||
@@ -74,3 +78,3 @@ | ||
it('should succeed if the operation succeed with the state of the operation', () => { | ||
const operation = new TableDelete({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const operation = tableDelete({ _client: { crudRemove } }, { getName }, 'foo', 'bar'); | ||
const state = { foo: 'bar' }; | ||
@@ -77,0 +81,0 @@ const expected = new Result(state); |
@@ -8,4 +8,4 @@ 'use strict'; | ||
const Result = require('lib/DevAPI/Result'); | ||
const TableInsert = require('lib/DevAPI/TableInsert'); | ||
const expect = require('chai').expect; | ||
const tableInsert = require('lib/DevAPI/TableInsert'); | ||
const td = require('testdouble'); | ||
@@ -32,2 +32,8 @@ | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(tableInsert().getClassName()).to.equal('TableInsert'); | ||
}); | ||
}); | ||
context('execute()', () => { | ||
@@ -37,6 +43,7 @@ it('should include the fields and projection values', () => { | ||
const expected = new Result(state); | ||
const query = new TableInsert(fakeSession, fakeSchema, 'table', ['foo', 'bar']).values(['baz', 'qux']); | ||
const projection = [{ name: 'foo' }, { name: 'bar' }]; | ||
const query = tableInsert(fakeSession, fakeSchema, 'table', ['foo', 'bar']).values(['baz', 'qux']); | ||
const columns = [{ name: 'foo' }, { name: 'bar' }]; | ||
const rows = [['baz', 'qux']]; | ||
td.when(crudInsert('schema', 'table', Client.dataModel.TABLE, [['baz', 'qux']], projection)).thenResolve(state); | ||
td.when(crudInsert('schema', 'table', Client.dataModel.TABLE, { columns, rows })).thenResolve(state); | ||
@@ -49,5 +56,5 @@ return query.execute().should.eventually.deep.equal(expected); | ||
it('should be fluent', () => { | ||
const query = (new TableInsert(null, null, null, ['foo'])).values('bar'); | ||
const query = tableInsert(null, null, null, ['foo']).values('bar'); | ||
expect(query).to.be.an.instanceof(TableInsert); | ||
expect(query.values).to.be.a('function'); | ||
}); | ||
@@ -57,5 +64,5 @@ | ||
const values = ['baz', 'qux']; | ||
const query = (new TableInsert(null, null, null, ['foo', 'bar'])).values(values); | ||
const query = tableInsert(null, null, null, ['foo', 'bar']).values(values); | ||
expect(query._rows).to.deep.equal([values]); | ||
expect(query.getRows()).to.deep.equal([values]); | ||
}); | ||
@@ -65,5 +72,5 @@ | ||
const values = ['baz', 'qux']; | ||
const query = (new TableInsert(null, null, null, ['foo', 'bar'])).values(values[0], values[1]); | ||
const query = tableInsert(null, null, null, ['foo', 'bar']).values(values[0], values[1]); | ||
expect(query._rows).to.deep.equal([values]); | ||
expect(query.getRows()).to.deep.equal([values]); | ||
}); | ||
@@ -73,3 +80,3 @@ | ||
const values = ['baz', 'qux']; | ||
const query = (new TableInsert(null, null, null, ['foo'])); | ||
const query = tableInsert(null, null, null, ['foo']); | ||
@@ -76,0 +83,0 @@ expect(() => query.values(values[0], values[1])).to.throw(Error); |
@@ -7,7 +7,6 @@ 'use strict'; | ||
// npm `test` script was updated to use NODE_PATH=. | ||
const BaseQuery = require('lib/DevAPI/BaseQuery'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const TableSelect = require('lib/DevAPI/TableSelect'); | ||
const expect = require('chai').expect; | ||
const parseExpressionInputs = require('lib/DevAPI/Util/parseExpressionInputs'); | ||
const tableSelect = require('lib/DevAPI/TableSelect'); | ||
const td = require('testdouble'); | ||
@@ -34,8 +33,24 @@ | ||
context('constructor', () => { | ||
it('should be an instance of BaseQuery', () => { | ||
expect(new TableSelect()).to.be.an.instanceof(BaseQuery); | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(tableSelect().getClassName()).to.equal('TableSelect'); | ||
}); | ||
}); | ||
context('lockShared()', () => { | ||
it('should set the correct locking mode', () => { | ||
const query = tableSelect().lockShared(); | ||
expect(query.getLockingMode()).to.equal(1); | ||
}); | ||
}); | ||
context('lockExclusive()', () => { | ||
it('should set the correct locking mode', () => { | ||
const query = tableSelect().lockExclusive(); | ||
expect(query.getLockingMode()).to.equal(2); | ||
}); | ||
}); | ||
context('execute()', () => { | ||
@@ -45,3 +60,3 @@ it('should acknowledge the projection values', () => { | ||
const expected = new Result(state); | ||
const query = new TableSelect(fakeSession, fakeSchema, 'table', ['foo', 'bar']); | ||
const query = tableSelect(fakeSession, fakeSchema, 'table', ['foo', 'bar']); | ||
@@ -53,2 +68,29 @@ const call = crudFind(fakeSession, 'schema', 'table', Client.dataModel.TABLE, parseExpressionInputs(['foo', 'bar'])); | ||
}); | ||
it('should set the correct default locking mode', () => { | ||
const state = { ok: true }; | ||
const expected = new Result(state); | ||
const query = tableSelect(fakeSession, fakeSchema); | ||
// default locking mode | ||
const mode = 0; | ||
const any = td.matchers.anything(); | ||
const execute = fakeSession._client.crudFind(any, any, any, any, any, any, any, any, any, any, any, any, any, any, mode); | ||
td.when(execute, { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(query.execute()).eventually.deep.equal(expected); | ||
}); | ||
it('should include the latest specified locking mode', () => { | ||
const state = { ok: true }; | ||
const expected = new Result(state); | ||
const query = tableSelect(fakeSession, fakeSchema).lockShared().lockExclusive(); | ||
const mode = 2; | ||
const any = td.matchers.anything(); | ||
const execute = fakeSession._client.crudFind(any, any, any, any, any, any, any, any, any, any, any, any, any, any, mode); | ||
td.when(execute, { ignoreExtraArgs: true }).thenResolve(state); | ||
return expect(query.execute()).eventually.deep.equal(expected); | ||
}); | ||
}); | ||
@@ -59,3 +101,3 @@ | ||
const expected = 'SELECT foo, bar FROM schema.table'; | ||
const query = new TableSelect(fakeSession, fakeSchema, 'table', ['foo', 'bar']); | ||
const query = tableSelect(fakeSession, fakeSchema, 'table', ['foo', 'bar']); | ||
@@ -67,3 +109,3 @@ expect(query.getViewDefinition()).to.equal(expected); | ||
const expected = 'SELECT * FROM schema.table WHERE foo = "baz"'; | ||
const query = (new TableSelect(fakeSession, fakeSchema, 'table', ['*'])).where('foo = "baz"'); | ||
const query = tableSelect(fakeSession, fakeSchema, 'table', ['*']).where('foo = "baz"'); | ||
@@ -75,3 +117,3 @@ expect(query.getViewDefinition()).to.equal(expected); | ||
const expected = 'SELECT foo FROM schema.table ORDER BY bar'; | ||
const query = (new TableSelect(fakeSession, fakeSchema, 'table', ['foo'])).orderBy(['bar']); | ||
const query = tableSelect(fakeSession, fakeSchema, 'table', ['foo']).orderBy(['bar']); | ||
@@ -84,5 +126,5 @@ expect(query.getViewDefinition()).to.equal(expected); | ||
it('should be fluent', () => { | ||
const query = (new TableSelect()).orderBy(); | ||
const query = tableSelect().orderBy(); | ||
expect(query).to.be.an.instanceof(TableSelect); | ||
expect(query.orderBy).to.be.a('function'); | ||
}); | ||
@@ -92,5 +134,5 @@ | ||
const parameters = ['foo desc', 'bar desc']; | ||
const query = (new TableSelect()).orderBy(parameters); | ||
const query = tableSelect().orderBy(parameters); | ||
expect(query._orderby).to.deep.equal(parameters); | ||
expect(query.getOrderBy()).to.deep.equal(parameters); | ||
}); | ||
@@ -100,5 +142,5 @@ | ||
const parameters = ['foo desc', 'bar desc']; | ||
const query = (new TableSelect()).orderBy(parameters[0], parameters[1]); | ||
const query = tableSelect().orderBy(parameters[0], parameters[1]); | ||
expect(query._orderby).to.deep.equal(parameters); | ||
expect(query.getOrderBy()).to.deep.equal(parameters); | ||
}); | ||
@@ -109,5 +151,5 @@ }); | ||
it('should be fluent', () => { | ||
const query = (new TableSelect()).groupBy(); | ||
const query = tableSelect().groupBy(); | ||
expect(query).to.be.an.instanceof(TableSelect); | ||
expect(query.groupBy).to.be.a('function'); | ||
}); | ||
@@ -117,5 +159,5 @@ | ||
const grouping = ['foo', 'bar']; | ||
const query = (new TableSelect()).groupBy(grouping); | ||
const query = tableSelect().groupBy(grouping); | ||
expect(query._groupby).to.deep.equal(grouping); | ||
expect(query.getGroupBy()).to.deep.equal(grouping); | ||
}); | ||
@@ -125,7 +167,7 @@ | ||
const grouping = ['foo', 'bar']; | ||
const query = (new TableSelect()).groupBy(grouping[0], grouping[1]); | ||
const query = tableSelect().groupBy(grouping[0], grouping[1]); | ||
expect(query._groupby).to.deep.equal(grouping); | ||
expect(query.getGroupBy()).to.deep.equal(grouping); | ||
}); | ||
}); | ||
}); |
@@ -7,6 +7,6 @@ 'use strict'; | ||
const Client = require('lib/Protocol/Client'); | ||
const TableUpdate = require('lib/DevAPI/TableUpdate'); | ||
const Result = require('lib/DevAPI/Result'); | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const tableUpdate = require('lib/DevAPI/TableUpdate'); | ||
const td = require('testdouble'); | ||
@@ -31,8 +31,14 @@ | ||
context('getClassName()', () => { | ||
it('should return the correct class name (to avoid duck typing)', () => { | ||
expect(tableUpdate().getClassName()).to.equal('TableUpdate'); | ||
}); | ||
}); | ||
context('where()', () => { | ||
it('should set the operation condition', () => { | ||
const query = 'foo'; | ||
const operation = (new TableUpdate()).where(query); | ||
const operation = tableUpdate().where(query); | ||
expect(operation._query).to.equal(query); | ||
expect(operation.getCriteria()).to.equal(query); | ||
}); | ||
@@ -43,3 +49,3 @@ }); | ||
it('should fail if a condition query is not provided', () => { | ||
const operation = new TableUpdate(); | ||
const operation = tableUpdate(); | ||
@@ -50,3 +56,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('update needs a valid condition'); | ||
it('should fail if a condition query is empty', () => { | ||
const operation = new TableUpdate(null, null, null, ''); | ||
const operation = tableUpdate(null, null, null, ''); | ||
@@ -57,3 +63,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('update needs a valid condition'); | ||
it('should fail if a condition query is not valid', () => { | ||
const operation = new TableUpdate(null, null, null, ' '); | ||
const operation = tableUpdate(null, null, null, ' '); | ||
@@ -64,3 +70,3 @@ return expect(operation.execute()).to.eventually.be.rejectedWith('update needs a valid condition'); | ||
it('should fail if the operation results in an error', () => { | ||
const operation = new TableUpdate({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const operation = tableUpdate({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const error = new Error('foobar'); | ||
@@ -75,3 +81,3 @@ | ||
it('should succeed if the operation succeed with the state of the operation', () => { | ||
const operation = new TableUpdate({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const operation = tableUpdate({ _client: { crudModify } }, { getName }, 'foo', 'bar'); | ||
const state = { foo: 'bar' }; | ||
@@ -78,0 +84,0 @@ const expected = new Result(state); |
@@ -32,3 +32,9 @@ 'use strict'; | ||
}); | ||
it('should allow empty or undefined inputs', () => { | ||
['', undefined, null].forEach(input => { | ||
return expect(parseExpressionInputs(input)).to.be.empty; | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -12,2 +12,3 @@ 'use strict'; | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -33,2 +34,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -54,2 +56,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -75,2 +78,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -96,2 +100,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -117,2 +122,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -138,2 +144,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -159,2 +166,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -180,2 +188,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -201,2 +210,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -223,2 +233,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -248,2 +259,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -298,2 +310,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -319,2 +332,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -340,2 +354,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -361,2 +376,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -382,2 +398,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -403,2 +420,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -426,2 +444,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -447,2 +466,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -467,2 +487,25 @@ dbPassword: 'password', | ||
context('authentication method', () => { | ||
it('should parse an URI with a specific authentication method', () => { | ||
const expected = { | ||
auth: 'AUTHMETHOD', | ||
dbUser: 'user', | ||
dbPassword: 'password', | ||
endpoints: [{ | ||
host: 'localhost', | ||
port: 33060, | ||
socket: undefined | ||
}], | ||
schema: undefined, | ||
ssl: true, | ||
sslOptions: { | ||
ca: undefined, | ||
crl: undefined | ||
} | ||
}; | ||
expect(parseUri('mysqlx://user:password@localhost:33060?auth=authMethod')).to.deep.equal(expected); | ||
}); | ||
}); | ||
it('should throw an error if the host is not valid', () => { | ||
@@ -476,2 +519,3 @@ expect(() => parseUri('mysql#x')).to.throw(Error, 'Invalid URI'); | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -497,2 +541,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -518,2 +563,3 @@ dbPassword: '', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -539,2 +585,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -560,2 +607,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -581,2 +629,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -602,2 +651,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -623,2 +673,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: undefined, | ||
@@ -645,2 +696,3 @@ dbPassword: undefined, | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -674,2 +726,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -725,2 +778,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -746,2 +800,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -767,2 +822,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -788,2 +844,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -809,2 +866,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -830,2 +888,3 @@ dbPassword: 'password', | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
@@ -850,40 +909,67 @@ dbPassword: 'password', | ||
it('should parse a connection string with a pct-encoded UNIX socket', () => { | ||
const expected = { | ||
dbUser: 'user', | ||
dbPassword: 'password', | ||
endpoints: [{ | ||
host: undefined, | ||
port: undefined, | ||
socket: './path/to/socket' | ||
}], | ||
schema: undefined, | ||
ssl: true, | ||
sslOptions: { | ||
ca: undefined, | ||
crl: undefined | ||
} | ||
}; | ||
context('local sockets', () => { | ||
it('should parse a connection string with a pct-encoded UNIX socket', () => { | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
dbPassword: 'password', | ||
endpoints: [{ | ||
host: undefined, | ||
port: undefined, | ||
socket: './path/to/socket' | ||
}], | ||
schema: undefined, | ||
ssl: true, | ||
sslOptions: { | ||
ca: undefined, | ||
crl: undefined | ||
} | ||
}; | ||
expect(parseUri('user:password@.%2Fpath%2Fto%2Fsocket')).to.deep.equal(expected); | ||
expect(parseUri('user:password@.%2Fpath%2Fto%2Fsocket')).to.deep.equal(expected); | ||
}); | ||
it('should parse an URI with a custom-encoded UNIX socket', () => { | ||
const expected = { | ||
auth: '', | ||
dbUser: 'user', | ||
dbPassword: 'password', | ||
endpoints: [{ | ||
host: undefined, | ||
port: undefined, | ||
socket: './path/to/socket' | ||
}], | ||
schema: 'schema', | ||
ssl: true, | ||
sslOptions: { | ||
ca: undefined, | ||
crl: undefined | ||
} | ||
}; | ||
expect(parseUri('user:password@(./path/to/socket)/schema')).to.deep.equal(expected); | ||
}); | ||
}); | ||
it('should parse an URI with a custom-encoded UNIX socket', () => { | ||
const expected = { | ||
dbUser: 'user', | ||
dbPassword: 'password', | ||
endpoints: [{ | ||
host: undefined, | ||
port: undefined, | ||
socket: './path/to/socket' | ||
}], | ||
schema: 'schema', | ||
ssl: true, | ||
sslOptions: { | ||
ca: undefined, | ||
crl: undefined | ||
} | ||
}; | ||
context('authentication method', () => { | ||
it('should parse an URI with a specific authentication method', () => { | ||
const expected = { | ||
auth: 'AUTHMETHOD', | ||
dbUser: 'user', | ||
dbPassword: 'password', | ||
endpoints: [{ | ||
host: 'localhost', | ||
port: 33060, | ||
socket: undefined | ||
}], | ||
schema: undefined, | ||
ssl: true, | ||
sslOptions: { | ||
ca: undefined, | ||
crl: undefined | ||
} | ||
}; | ||
expect(parseUri('user:password@(./path/to/socket)/schema')).to.deep.equal(expected); | ||
expect(parseUri('user:password@localhost:33060?auth=authMethod')).to.deep.equal(expected); | ||
}); | ||
}); | ||
@@ -890,0 +976,0 @@ |
@@ -8,3 +8,3 @@ 'use strict'; | ||
describe('@connections-secure-by-default parseSecurityOptions', () => { | ||
describe('parseSecurityOptions', () => { | ||
it('should enable ssl if any of the security related properties are provided', () => { | ||
@@ -11,0 +11,0 @@ // TODO(Rui): add `ssl-mode=VERIFY_CA`, `ssl-mode=VERIFY_IDENTITY` and/or `ssl-mode=VERIFY_CRL`? |
@@ -34,2 +34,26 @@ "use strict"; | ||
{ | ||
should: 'allow lowercase null', | ||
in: 'null', | ||
exp: { | ||
type: 2, | ||
literal: Datatype.encodeScalar(null) | ||
} | ||
}, | ||
{ | ||
should: 'allow uppercase null', | ||
in: 'NULL', | ||
exp: { | ||
type: 2, | ||
literal: Datatype.encodeScalar(null) | ||
} | ||
}, | ||
{ | ||
should: 'allow zero', | ||
in: '0', | ||
exp: { | ||
type: 2, | ||
literal: Datatype.encodeScalar(0) | ||
} | ||
}, | ||
{ | ||
should: 'allow whitespaces within strings', | ||
@@ -62,2 +86,20 @@ in: '"foo bar"', | ||
{ | ||
should: 'allow empty quoted strings', | ||
in: "''", | ||
exp: { | ||
type: 2, | ||
literal: Datatype.encodeScalar('') | ||
} | ||
}, | ||
{ | ||
should: 'allow empty double quoted strings', | ||
in: '""', | ||
exp: { | ||
type: 2, | ||
literal: Datatype.encodeScalar('') | ||
} | ||
}, | ||
{ | ||
should: 'allow escaped quotes within strings', | ||
@@ -170,2 +212,19 @@ in: '"foo\\\"bar"', | ||
{ | ||
should: 'allow negation', | ||
in: '!false', | ||
exp: { | ||
type: 5, | ||
operator: { | ||
name: '!', | ||
param: [ | ||
{ | ||
type: 2, | ||
literal: Datatype.encodeScalar(false) | ||
} | ||
] | ||
} | ||
} | ||
}, | ||
{ | ||
should: 'allow functions without arguments', | ||
@@ -342,2 +401,2 @@ in: 'concat()', | ||
}); | ||
}); | ||
}); |
@@ -9,3 +9,4 @@ 'use strict'; | ||
const SqlResultHandler = require('lib/Protocol/ResponseHandler').SqlResultHandler; | ||
const expect = require('chai').expect; | ||
const chai = require('chai'); | ||
const chaiAsPromised = require('chai-as-promised'); | ||
const isEqual = require('lodash.isequal'); | ||
@@ -15,10 +16,15 @@ const proxyquire = require('proxyquire'); | ||
chai.use(chaiAsPromised); | ||
const expect = chai.expect; | ||
describe('Client', () => { | ||
let on, sendMessage; | ||
let on, sendMessage, fakeSendMessage; | ||
beforeEach('create fakes', () => { | ||
on = td.function(); | ||
fakeSendMessage = td.function(); | ||
sendMessage = SqlResultHandler.prototype.sendMessage; | ||
SqlResultHandler.prototype.sendMessage = td.function(); | ||
SqlResultHandler.prototype.sendMessage = fakeSendMessage; | ||
}); | ||
@@ -36,3 +42,3 @@ | ||
expect(client._stream).to.equal(stream); | ||
return expect(client._stream).to.equal(stream); | ||
}); | ||
@@ -43,3 +49,3 @@ | ||
expect(client._workQueue).to.be.an.instanceof(WorkQueue); | ||
return expect(client._workQueue).to.be.an.instanceof(WorkQueue); | ||
}); | ||
@@ -50,3 +56,3 @@ | ||
expect(client._danglingFragment).to.be.false; | ||
return expect(client._danglingFragment).to.be.false; | ||
}); | ||
@@ -59,6 +65,7 @@ | ||
td.when(on('data')).thenCallback('foo'); | ||
td.when(handleNetworkFragment('foo')).thenReturn(); | ||
Client.call({ handleNetworkFragment }, stream); | ||
td.verify(handleNetworkFragment('foo'), { times: 1 }); | ||
return expect(td.explain(handleNetworkFragment).callCount).to.equal(1); | ||
}); | ||
@@ -71,6 +78,7 @@ | ||
td.when(on('close')).thenCallback(); | ||
td.when(handleServerClose('foo')).thenReturn(); | ||
Client.call({ handleServerClose }, stream); | ||
td.verify(handleServerClose(), { times: 1 }); | ||
return expect(td.explain(handleServerClose).callCount).to.equal(1); | ||
}); | ||
@@ -225,7 +233,7 @@ }); | ||
// FIXME(ruiquelhas): test the promise interface. | ||
context('crudFind()', () => { | ||
it('should encode grouping expressions correctly', () => { | ||
const client = new Client({ on }); | ||
const expected = [{ | ||
const expected = { ok: true }; | ||
const grouping = [{ | ||
type: 1, | ||
@@ -241,22 +249,59 @@ identifier: { | ||
}]; | ||
const match = td.matchers.argThat(data => isEqual(data.grouping, expected)); | ||
const message = new Buffer('foobar'); | ||
const encodeMessage = td.function(); | ||
const matcher = td.matchers.argThat(data => isEqual(data.grouping, grouping)); | ||
client.encodeMessage = td.function(); | ||
client.crudFind(null, null, null, null, null, null, ['foo', 'bar']); | ||
client.encodeMessage = encodeMessage; | ||
td.verify(client.encodeMessage(Messages.ClientMessages.CRUD_FIND, match), { times: 1 }); | ||
td.when(encodeMessage(Messages.ClientMessages.CRUD_FIND, matcher)).thenReturn(message); | ||
td.when(fakeSendMessage(client._workQueue, client._stream, message)).thenResolve(expected); | ||
return expect(client.crudFind(null, null, null, null, null, null, ['foo', 'bar'])).to.eventually.deep.equal(expected); | ||
}); | ||
it('should send encoded message to the server', () => { | ||
const handler = new SqlResultHandler(); | ||
it('shoud encode a specific row locking mode', () => { | ||
const expected = { ok: true }; | ||
const client = new Client({ on }); | ||
const encodeMessage = td.function(); | ||
const mode = 1; | ||
const message = new Buffer('foobar'); | ||
client.encodeMessage = td.function(); | ||
client.encodeMessage = encodeMessage; | ||
td.when(client.encodeMessage(td.matchers.anything(), td.matchers.anything())).thenReturn('foobar'); | ||
td.when(encodeMessage(Messages.ClientMessages.CRUD_FIND, td.matchers.contains({ locking: mode }))).thenReturn(message); | ||
td.when(fakeSendMessage(client._workQueue, client._stream, message)).thenResolve(expected); | ||
client.crudFind(); | ||
return expect(client.crudFind(null, null, null, null, null, null, null, null, null, null, null, null, null, null, mode)).to.eventually.deep.equal(expected); | ||
}); | ||
td.verify(handler.sendMessage(client._workQueue, client._stream, 'foobar'), { times: 1 }); | ||
it('should not encode the default row locking mode', () => { | ||
const expected = { ok: true }; | ||
const client = new Client({ on }); | ||
const encodeMessage = td.function(); | ||
const mode = 0; | ||
const message = new Buffer('foobar'); | ||
client.encodeMessage = encodeMessage; | ||
const matcher = td.matchers.argThat(data => !data.locking); | ||
td.when(encodeMessage(Messages.ClientMessages.CRUD_FIND, matcher)).thenReturn(message); | ||
td.when(fakeSendMessage(client._workQueue, client._stream, message)).thenResolve(expected); | ||
return expect(client.crudFind(null, null, null, null, null, null, null, null, null, null, null, null, null, null, mode)).to.eventually.deep.equal(expected); | ||
}); | ||
it('should fail if the message cannot be sent', () => { | ||
const error = new Error('foobar'); | ||
const client = new Client({ on }); | ||
const encodeMessage = td.function(); | ||
const message = new Buffer('foobar'); | ||
client.encodeMessage = encodeMessage; | ||
td.when(encodeMessage(Messages.ClientMessages.CRUD_FIND, td.matchers.anything())).thenReturn(message); | ||
td.when(fakeSendMessage(client._workQueue, client._stream, message)).thenReject(error); | ||
return expect(client.crudFind()).to.eventually.be.rejectedWith(error); | ||
}); | ||
}); | ||
@@ -266,18 +311,23 @@ | ||
it('should encode field names correctly', () => { | ||
const expected = { ok: true }; | ||
const client = new Client({ on }); | ||
const expected = ['foo', 'bar']; | ||
const handler = new SqlResultHandler(); | ||
const match = td.matchers.argThat(data => isEqual(data.projection, expected)); | ||
const columns = ['foo', 'bar']; | ||
const message = new Buffer('foobar'); | ||
const encodeMessage = td.function(); | ||
const match = td.matchers.argThat(data => isEqual(data.projection, columns)); | ||
client.encodeMessage = td.function(); | ||
client.encodeMessage = encodeMessage; | ||
td.when(handler.sendMessage(), { ignoreExtraArgs: true }).thenResolve(); | ||
td.when(encodeMessage(Messages.ClientMessages.CRUD_INSERT, match)).thenReturn(message); | ||
td.when(fakeSendMessage(client._workQueue, client._stream, message)).thenResolve(expected); | ||
return client.crudInsert(null, null, null, [['baz', 'qux']], expected) | ||
.then(() => td.verify(client.encodeMessage(Messages.ClientMessages.CRUD_INSERT, match), { times: 1 })); | ||
return expect(client.crudInsert(null, null, null, { columns, rows: [['baz', 'qux']] })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should encode row values correctly', () => { | ||
const expected = { ok: true }; | ||
const client = new Client({ on }); | ||
const expected = [{ | ||
const message = new Buffer('foobar'); | ||
const encodeMessage = td.function(); | ||
const rows = [{ | ||
field: [{ | ||
@@ -301,56 +351,25 @@ type: 2, | ||
}]; | ||
const handler = new SqlResultHandler(); | ||
const match = td.matchers.argThat(data => isEqual(data.row, expected)); | ||
const matcher = td.matchers.argThat(data => isEqual(data.row, rows)); | ||
td.when(handler.sendMessage(), { ignoreExtraArgs: true }).thenResolve(); | ||
client.encodeMessage = encodeMessage; | ||
client.encodeMessage = td.function(); | ||
td.when(encodeMessage(Messages.ClientMessages.CRUD_INSERT, matcher)).thenReturn(message); | ||
td.when(fakeSendMessage(client._workQueue, client._stream, message)).thenResolve(expected); | ||
return client.crudInsert(null, null, null, [['foo', 'bar']]) | ||
.then(() => td.verify(client.encodeMessage(Messages.ClientMessages.CRUD_INSERT, match), { times: 1 })); | ||
return expect(client.crudInsert(null, null, null, { rows: [['foo', 'bar']] })).to.eventually.deep.equal(expected); | ||
}); | ||
it('should send encoded message to the server', () => { | ||
it('should fail if the message cannot be sent', () => { | ||
const client = new Client({ on }); | ||
const expected = { foo: 'bar' }; | ||
const handler = new SqlResultHandler(); | ||
client.encodeMessage = td.function(); | ||
td.when(client.encodeMessage(), { ignoreExtraArgs: true }).thenReturn('foobar'); | ||
td.when(handler.sendMessage(client._workQueue, client._stream, 'foobar')).thenResolve(expected); | ||
return client.crudInsert(null, null, null, [[]]).should.become(expected); | ||
}); | ||
it('should throw error if no documents are provided', () => { | ||
const client = new Client({ on }); | ||
const handler = new SqlResultHandler(); | ||
client.encodeMessage = td.function(); | ||
return client.crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, {}) | ||
.catch(err => { | ||
expect(err).to.be.an.instanceof(Error); | ||
expect(err.message).to.equal('No document provided for Crud::Insert'); | ||
td.verify(client.encodeMessage(), { ignoreExtraArgs: true, times: 0 }); | ||
td.verify(handler.sendMessage(), { ignoreExtraArgs: true, times: 0 }); | ||
}); | ||
}); | ||
// TODO(rui.quelhas): add the same test for `crudFind` | ||
it('should throw error if the message cannot be sent', () => { | ||
const client = new Client({ on }); | ||
const error = new Error('foo'); | ||
const handler = new SqlResultHandler(); | ||
const encodeMessage = td.function(); | ||
client.encodeMessage = td.function(); | ||
client.encodeMessage = encodeMessage; | ||
td.when(client.encodeMessage(), { ignoreExtraArgs: true }).thenReturn(); | ||
td.when(handler.sendMessage(), { ignoreExtraArgs: true }).thenReject(error); | ||
td.when(encodeMessage(), { ignoreExtraArgs: true }).thenReturn(); | ||
td.when(fakeSendMessage(), { ignoreExtraArgs: true }).thenReject(error); | ||
return client.crudInsert(null, null, null, [['foo', 'bar']]).should.be.rejectedWith(error); | ||
return expect(client.crudInsert(null, null, null, { rows: [['foo', 'bar']] })).to.eventually.be.rejectedWith(error); | ||
}); | ||
}); | ||
}); |
@@ -51,4 +51,4 @@ 'use strict'; | ||
const promise = Promise.all([ | ||
protocol.crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, [[{ _id: 123 }]]), | ||
protocol.crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, [[{ _id: 456 }]]) | ||
protocol.crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, { rows: [[{ _id: 123 }]] }), | ||
protocol.crudInsert('schema', 'collection', Client.dataModel.DOCUMENT, { rows: [[{ _id: 456 }]] }) | ||
]); | ||
@@ -55,0 +55,0 @@ |
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
898346
182
21061
75