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

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)
Marcel Odumorek <odumorek@creanet.sk> (https://www.linkedin.com/in/marcel-odumorek-b776a7171/)
\ No newline at end of file
Milan Wikarski <wikarski.milan@creanet.sk> (milanwikarski.me)
\ 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
*
* @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
* Class for communication with PostgreSQL database
*
* @public @method tableExists
* @description
* Determines wether a table or a view with such name exists
* @param {String} tableName - the name of the table
* @class DB
*/
const { Pool } = require('pg');
const { snake2camel } = require('./utils/case');
class DB {
constructor(params) {
this._db = new Pool(params);
}
_lorem() {
return 'ipsum';
}
_getWhere(where) {
if (where instanceof Object && Object.entries(where).length) {
return (
' WHERE ' +
Object.entries(where)
.map(([key, value]) =>
value instanceof Object
? `"${key}" ${value.operator} '${value.value}'`
: `"${key}" = '${value}'`
)
.join(' AND ')
);
} else if (typeof where === 'string' && where.length) {
return ' WHERE ' + where;
}
return '';
}
_getLimit(limit) {
if (!isNaN(Number(limit)) && limit != null) {
return ' LIMIT ' + limit;
}
return '';
}
_getOffset(offset) {
if (!isNaN(Number(offset)) && offset != null) {
return " OFFSET " + offset;
}
return "";
}
_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);
/**
*
* @param {Object} params
* @param {string} params.host - Connection Host
* @param {number} params.port - Connection Port
* @param {string} params.database - DB Name
* @param {string} params.user - DB User
* @param {string} params.password - DB Password
*/
constructor({ host, port, database, user, password }) {
this._db = new Pool({ host, port, database, user, password });
}
/**
* Creates a Query object
*
* @param {string} query
* @param {Array.<any>} data
* @returns {Query} query
*/
query(query, data = []) {
return new Query(this._db, query, data);
}
/**
* Creates a Select object
*
* @param {string} table
* @returns {Select} select
*/
select(table) {
return new Select(this._db, table);
}
/**
* Creates an Insert object
* @param {string} table
* @returns {Insert} insert
*/
insert(table) {
return new Insert(this._db, table);
}
}
......
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 @@
"_location": "/pg",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "pg",
"name": "pg",
"escapedName": "pg",
"fetchSpec": "latest",
"name": "pg",
"raw": "pg",
"rawSpec": "",
"registry": true,
"saveSpec": null,
"fetchSpec": "latest"
"type": "tag"
},
"_requiredBy": [
"#USER",
......@@ -21,11 +21,12 @@
],
"_resolved": "https://registry.npmjs.org/pg/-/pg-7.8.2.tgz",
"_shasum": "d53ffcbbaa789e15e80ffec570603294d90116e8",
"_shrinkwrap": null,
"_spec": "pg",
"_where": "/Users/macuser/Projekty/packages/db",
"author": {
"name": "Brian Carlson",
"email": "brian.m.carlson@gmail.com"
"email": "brian.m.carlson@gmail.com",
"name": "Brian Carlson"
},
"bugs": {
"url": "https://github.com/brianc/node-postgres/issues"
......@@ -71,6 +72,9 @@
"main": "./lib",
"minNativeVersion": "2.0.0",
"name": "pg",
"optionalDependencies": {},
"readme": "# node-postgres\n\n[![Build Status](https://secure.travis-ci.org/brianc/node-postgres.svg?branch=master)](http://travis-ci.org/brianc/node-postgres)\n[![Dependency Status](https://david-dm.org/brianc/node-postgres.svg)](https://david-dm.org/brianc/node-postgres)\n<span class=\"badge-npmversion\"><a href=\"https://npmjs.org/package/pg\" title=\"View this project on NPM\"><img src=\"https://img.shields.io/npm/v/pg.svg\" alt=\"NPM version\" /></a></span>\n<span class=\"badge-npmdownloads\"><a href=\"https://npmjs.org/package/pg\" title=\"View this project on NPM\"><img src=\"https://img.shields.io/npm/dm/pg.svg\" alt=\"NPM downloads\" /></a></span>\n\nNon-blocking PostgreSQL client for Node.js. Pure JavaScript and optional native libpq bindings.\n\n## Install\n\n```sh\n$ npm install pg\n```\n\n---\n## :star: [Documentation](https://node-postgres.com) :star:\n\n\n### Features\n\n* Pure JavaScript client and native libpq bindings share _the same API_\n* Connection pooling\n* Extensible JS<->PostgreSQL data-type coercion\n* Supported PostgreSQL features\n * Parameterized queries\n * Named statements with query plan caching\n * Async notifications with `LISTEN/NOTIFY`\n * Bulk import & export with `COPY TO/COPY FROM`\n\n### Extras\n\nnode-postgres is by design pretty light on abstractions. These are some handy modules we've been using over the years to complete the picture.\nThe entire list can be found on our [wiki](https://github.com/brianc/node-postgres/wiki/Extras).\n\n## Support\n\nnode-postgres is free software. If you encounter a bug with the library please open an issue on the [GitHub repo](https://github.com/brianc/node-postgres). If you have questions unanswered by the documentation please open an issue pointing out how the documentation was unclear & I will do my best to make it better!\n\nWhen you open an issue please provide:\n- version of Node\n- version of Postgres\n- smallest possible snippet of code to reproduce the problem\n\nYou can also follow me [@briancarlson](https://twitter.com/briancarlson) if that's your thing. I try to always announce noteworthy changes & developments with node-postgres on Twitter.\n\n### Professional Support\n\nI offer professional support for node-postgres. I provide implementation, training, and many years of expertise on how to build applications with Node, Express, PostgreSQL, and React/Redux. Please contact me at [brian.m.carlson@gmail.com](mailto:brian.m.carlson@gmail.com) to discuss how I can help your company be more successful!\n\n### Sponsorship :star:\n\nIf you are benefiting from node-postgres and would like to help keep the project financially sustainable please visit Brian Carlson's [Patreon page](https://www.patreon.com/node_postgres).\n\n## Contributing\n\n__:heart: contributions!__\n\nI will __happily__ accept your pull request if it:\n- __has tests__\n- looks reasonable\n- does not break backwards compatibility\n\n## Troubleshooting and FAQ\n\nThe causes and solutions to common errors can be found among the [Frequently Asked Questions (FAQ)](https://github.com/brianc/node-postgres/wiki/FAQ)\n\n## License\n\nCopyright (c) 2010-2019 Brian Carlson (brian.m.carlson@gmail.com)\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n",
"readmeFilename": "README.md",
"repository": {
"type": "git",
"url": "git://github.com/brianc/node-postgres.git"
......
......@@ -20,5 +20,8 @@
"license": "ISC",
"dependencies": {
"pg": "^7.8.2"
},
"devDependencies": {
"dotenv": "^8.2.0"
}
}
require('dotenv').config();
const DB = require('../index');
const db = new DB({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASS
});
const select = async () => {
console.log('SELECT');
const select = db
.select('lorem')
.columns('*')
.where('id > 2');
console.log(select.query);
if (await select.execute()) {
console.log(select.rows);
} else {
console.log(select.err);
}
};
const insert = async () => {
console.log('INSERT');
const insert = db.insert('lorem').values([
{ foo: 'Ahoj', bar: 12 },
{ foo: 'Čau', bar: 52 }
]);
if (await insert.execute()) {
console.log(insert.rows);
} else {
console.log(insert.err);
}
};
(async () => {
await select();
// await insert();
// await select();
process.exit();
})();
// const select = db
// .select()
// .columns('*')
// .limit(5)
// .offset(5);
/**
* Returns `true` if val is a string; `false` otherwise
*
* @param {*} val
* @returns {boolean}
*/
function isString(val) {
return typeof val == 'string';
}
/**
* Returns `true` if val is an array; `false` otherwise
*
* @param {*} val
* @returns {boolean}
*/