Commit 4f387dc7 authored by Milan Wikarski's avatar Milan Wikarski
Browse files

Version 2.0.0: new structure, syntax... QUERY, INSERT, SELECT

parent 880f1238
DB_HOST=postgresql.websupport.sk
DB_PORT=5432
DB_NAME=z5t9ovx7
DB_USER=z5t9ovx7
DB_PASS=Tu0fC`L}f>
\ No newline at end of file
node_modules
\ No newline at end of file
Milan Wikarski <wikarski.milan@creanet.sk> (milanwikarski.me) Milan Wikarski <wikarski.milan@creanet.sk> (milanwikarski.me)
Marcel Odumorek <odumorek@creanet.sk> (https://www.linkedin.com/in/marcel-odumorek-b776a7171/) \ No newline at end of file
\ No newline at end of file
module.exports = {
Query: require('./query.class'),
Select: require('./select.class'),
Insert: require('./insert.class')
};
const Query = require('./query.class');
const { Pool } = require('pg');
const { ValuesMixin } = require('../mixins');
/**
* Class for execution of INSERT queries
*
* @class Insert
*/
class Insert extends ValuesMixin(Query) {
/**
* Creates an instance of Insert.
*
* @param {Pool} db
* @param {string} table
*/
constructor(db, table) {
super(db);
this.table = table;
}
get canExecute() {
return this._values != null;
}
get query() {
return `INSERT INTO ${this.table} ${this.valuesSQL}`;
}
}
module.exports = Insert;
const { Pool } = require('pg');
class Query {
/**
* Creates an instance of Query.
*
* @param {Pool} db
* @param {string} [query='']
* @param {array} [data=[]]
*/
constructor(db, query = '', data = []) {
this._db = db;
this._query = query;
this.data = data;
this.executed = false;
this.success = null;
this.rows = null;
this.err = null;
}
/**
* Getter for query; can be overridden
*
* @readonly
*/
get query() {
return this._query;
}
/**
* Returns `true` if Query can be executed, `false` otherwise
* - Note, that this function DOES NOT check syntax
*
* @readonly
*/
get canExecute() {
return this._query != '';
}
/**
* Executes a generic query
* - returns `true` if successful; `false` otherwise
*
* @returns {Promise.<boolean>} success
* @memberof Query
*/
async execute() {
if (!this.canExecute) throw new Error('This query cannot be executed yet');
return new Promise(resolve => {
this._db.query(this.query, this.data, (err, res) => {
this.executed = true;
if (err) {
this.success = false;
this.err = err;
resolve(false);
} else {
this.rows = res.rows;
resolve(true);
}
});
});
}
}
module.exports = Query;
const Query = require('./query.class');
const { Pool } = require('pg');
const {
ColumnsMixin,
LimitMixin,
OffsetMixin,
WhereMixin
} = require('../mixins');
/**
* Class for execution of SELECT queries
*
* @class Select
*/
class Select extends WhereMixin(OffsetMixin(LimitMixin(ColumnsMixin(Query)))) {
/**
* Creates an instance of Select.
*
* @param {Pool} db
* @param {string} table
*/
constructor(db, table) {
super(db);
this.table = table;
}
get canExecute() {
return this._columns != null;
}
get query() {
return `SELECT ${this.columnsSQL} FROM ${this.table} ${this.whereSQL} ${this.limitSQL} ${this.offsetSQL}`;
}
}
module.exports = Select;
const { Pool } = require('pg');
const { Query, Select, Insert } = require('./classes');
/** /**
* @module db * Class for communication with PostgreSQL database
*
* @private @method _getWhere
* @description
* Parses the where parameter and returns query substring
* @param {Object, String} where
*
* @private @method _getLimit
* @description
* Parses the limit parameter and returns query substring
* @param {Number} limit
*
* @private @method _getReturning
* @description
* Parses the returning parameter and returns query substring
* @param {Array, String} returning
*
* @private @method _sendResponse
* @description
* Executes query with given data
* Transforms object keys to camel case
* @param {String} q - query to be executed
* @param {Array} data = [] - data to replace placeholders in query
*
* @public @method query
* @description
* Executes query
* @param {String} q - The query to be executed
* @param {Array} data = [] - data to replace placeholders in query
* *
* @public @method tableExists * @class DB
* @description
* Determines wether a table or a view with such name exists
* @param {String} tableName - the name of the table
*/ */
const { Pool } = require('pg');
const { snake2camel } = require('./utils/case');
class DB { class DB {
constructor(params) { /**
this._db = new Pool(params); *
} * @param {Object} params
* @param {string} params.host - Connection Host
_lorem() { * @param {number} params.port - Connection Port
return 'ipsum'; * @param {string} params.database - DB Name
} * @param {string} params.user - DB User
* @param {string} params.password - DB Password
_getWhere(where) { */
if (where instanceof Object && Object.entries(where).length) { constructor({ host, port, database, user, password }) {
return ( this._db = new Pool({ host, port, database, user, password });
' WHERE ' + }
Object.entries(where)
.map(([key, value]) => /**
value instanceof Object * Creates a Query object
? `"${key}" ${value.operator} '${value.value}'` *
: `"${key}" = '${value}'` * @param {string} query
) * @param {Array.<any>} data
.join(' AND ') * @returns {Query} query
); */
} else if (typeof where === 'string' && where.length) { query(query, data = []) {
return ' WHERE ' + where; return new Query(this._db, query, data);
} }
return ''; /**
} * Creates a Select object
*
_getLimit(limit) { * @param {string} table
if (!isNaN(Number(limit)) && limit != null) { * @returns {Select} select
return ' LIMIT ' + limit; */
} select(table) {
return new Select(this._db, table);
return ''; }
}
/**
_getOffset(offset) { * Creates an Insert object
if (!isNaN(Number(offset)) && offset != null) { * @param {string} table
return " OFFSET " + offset; * @returns {Insert} insert
} */
insert(table) {
return ""; return new Insert(this._db, table);
}
_getReturning(returning) {
if (typeof returning === 'string') {
return ` RETURNING ${returning}`;
} else if (Array.isArray(returning)) {
return ` RETURNING ${returning.map(column => `"${column}"`).join(',')}`;
}
return '';
}
_sendResponse(q, data = []) {
return new Promise((resolve, reject) => {
this._db.query(q, data, (err, res) =>
err ? reject(err) : resolve(res.rows.map(record => snake2camel(record)))
);
});
}
query(q, data = []) {
return new Promise((resolve, reject) => {
this._db.query(q, data, (err, res) => (err ? reject(err) : resolve(res)));
});
}
tableExists(tableName) {
return this._db.query(
'SELECT EXISTS( SELECT 1 FROM information_schema.tables WHERE table_name=$1 )',
[tableName],
(err, res) => (err ? reject(err) : resolve(res.rows[0][0]))
);
}
select(tableName, columns = '*', where = null, limit = null, offset = null) {
let q;
if (typeof columns === 'string') {
q = `SELECT ${columns} FROM ${tableName}`;
} else if (Array.isArray(columns)) {
q = `SELECT ${columns
.map(column => `"${column}"`)
.join(',')} FROM ${tableName}`;
} else {
throw new TypeError(
columns +
' is of invalid type; valid types are String and Array of String'
);
}
q += this._getWhere(where);
q += this._getLimit(limit);
q += this._getOffset(offset);
return this._sendResponse(q);
}
insert(tableName, data, returning) {
let keys = Object.keys(Array.isArray(data) ? data[0] : data).map(
key => `"${key}"`
);
let values = Array.isArray(data)
? [].concat(...data.map(record => Object.values(record)))
: Object.values(data);
let placeholders;
if (Array.isArray(data)) {
let index = 1;
placeholders =
'VALUES ' +
data
.map(
() =>
`(${Array.from({ length: keys.length }, () => '$' + index++)})`
)
.join(',');
} else {
placeholders = `VALUES(${Array.from(
{ length: keys.length },
(record, index) => '$' + (index + 1)
)})`;
}
keys = `(${keys.join(',')})`;
let q = `INSERT INTO ${tableName} ${keys} ${placeholders}`;
q += this._getReturning(returning);
return this._sendResponse(q, values);
}
delete(tableName, where = null, returning = null) {
let q = `DELETE FROM ${tableName}`;
q += this._getWhere(where);
q += this._getReturning(returning);
return this._sendResponse(q);
}
update(tableName, columnNames, data, conditionColumn, conditionData) {
// Setup static beginning of query
var q = [`UPDATE ${tableName}`];
q.push('SET');
// Create another array storing each set command
// and assigning a number value for parameterized q
var set = [];
Object.values(columnNames).forEach(function(key, i) {
set.push(key + ' = ($' + (i + 1) + ')');
});
q.push(set.join(', '));
// Add the WHERE statement to look up by id
q.push(`WHERE ${conditionColumn} = '${conditionData}';`);
q = q.join(' ');
return this._sendResponse(q, data);
} }
} }
......
const is = require('../utils/types');
const ColumnsMixin = C =>
class ColumnsMixin extends C {
constructor() {
super(...arguments);
this._columns = null;
}
get columnsSQL() {
if (is.string(this._columns)) {
return this._columns;
}
return this._columns.join(',');
}
/**
* Sets the names of columns. Valid values are:
* - `'*'` - select all colums
* - `{string}` Name of single column
* - `{array.<string>}` Array with column names
*
* @param {string|string[]} columns
*/
columns(columns) {
if (!is(columns, ['string', 'array']))
throw TypeError('columns is of invalid type');
this._columns = columns;
return this;
}
};
module.exports = ColumnsMixin;
module.exports = {
ColumnsMixin: require('./columns.mixin'),
LimitMixin: require('./limit.mixin'),
OffsetMixin: require('./offset.mixin'),
ValuesMixin: require('./values.mixin'),
WhereMixin: require('./where.mixin')
};
const is = require('../utils/types');
const LimitMixin = C =>
class LimitMixin extends C {
constructor() {
super(...arguments);
this._limit = null;
}
get limitSQL() {
if (this._limit == null) {
return '';
}
return `LIMIT ${this._limit}`;
}
/**
* Sets the limit of query. Valid values are:
* - `{number}` - maximal number of rows
*
* @param {number} limit
*/
limit(limit) {
if (!is(limit, 'numeric')) throw TypeError('limit is of invalid type');
this._limit = limit;
return this;
}
};
module.exports = LimitMixin;
const is = require('../utils/types');
const OffsetMixin = C =>
class OffsetMixin extends C {
constructor() {
super(...arguments);
this._offset = null;
}
get offsetSQL() {
if (this._offset == null) {
return '';
}
return `OFFSET ${this._offset}`;
}
/**
* Sets the offset of query. Valid values are:
* - `{number}` - maximal number of rows
*
* @param {number} offset
*/
offset(offset) {
if (!is(offset, 'numeric')) throw TypeError('offset is of invalid type');
this._offset = offset;
return this;
}
};
module.exports = OffsetMixin;
const is = require('../utils/types');
const ValuesMixin = C =>
class ValuesMixin extends C {
constructor() {
super(...arguments);
this._offset = null;
}
get valuesSQL() {
const head = Object.keys(this._values[0]).join(',');
const body = this._values
.map(record => `(${Object.values(record).map(value => `'${value}'`)})`)
.join(', ');
return `(${head}) VALUES ${body}`;
}
/**
* Sets the values of query. Valid values are:
* - `{Object}` - one record with key:value pairs
* - `{Array<Object>} - multiple records
*
* @param {Array<Object>} values
*/
values(values) {
if (!is(values, ['array', 'object']))
throw new TypeError('values is of invalid type');
if (!is.array(values)) {
values = [values];
}
this._values = values;
return this;
}
};
module.exports = ValuesMixin;
const is = require('../utils/types');
const WhereMixin = C =>
class WhereMixin extends C {
constructor() {
super(...arguments);
this._where = null;
}
get whereSQL() {
if (this._where == null) {
return '';
}
return `WHERE ${this._where}`;
}
/**
* Sets the where of query. Valid values are:
* - `{string}` - SQL expression
*
* @param {Array<Object>} where
*/
where(where) {
if (!is(where, ['string', 'object']))
throw new TypeError('where is of invalid type');
if (is.object(where)) {
this._where = Object.entries(where)
.map(([key, value]) => `${key} = '${value}'`)
.join(' AND ');
} else {
this._where = where;
}
return this;
}
};
module.exports = WhereMixin;
...@@ -6,14 +6,14 @@ ...@@ -6,14 +6,14 @@
"_location": "/pg", "_location": "/pg",
"_phantomChildren": {}, "_phantomChildren": {},
"_requested": { "_requested": {
"type": "tag",
"registry": true,
"raw": "pg",
"name": "pg",
"escapedName": "pg", "escapedName": "pg",
"fetchSpec": "latest",
"name": "pg",
"raw": "pg",
"rawSpec": "", "rawSpec": "",
"registry": true,
"saveSpec": null, "saveSpec": null,
"fetchSpec": "latest"