Skip to content

Commit 8ea4352

Browse files
committed
Added upsertion
1 parent 6d0db06 commit 8ea4352

File tree

7 files changed

+171
-99
lines changed

7 files changed

+171
-99
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,4 @@ dist
107107
*.db
108108

109109
# Jest
110-
*.test.js
110+
jest

index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ interface DeleteOptions extends BaseOptions {
4444
where?: (string | BooleanExpression)[]
4545
}
4646

47+
interface UpsertOptions extends InsertOptions {
48+
set: (string | BooleanExpression)[],
49+
where?: (string | BooleanExpression)[]
50+
}
51+
4752
interface BooleanExpression {
4853
lhs: string,
4954
operator: string,

index.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
const { PromiseDB } = require('./lib/promisedb');
1+
module.exports = {
2+
// PromiseDB class
3+
PromiseDB: require('./lib/promisedb').PromiseDB,
24

3-
// Export query functions and PromiseDB class
4-
module.exports = require('./lib/queries');
5-
module.exports.PromiseDB = PromiseDB;
5+
// Query functions
6+
...require('./lib/queries'),
67

7-
// Export conditions (expressions, operators, etc.)
8-
module.exports.expression = {
9-
...require('./lib/expressions/boolean'),
10-
...require('./lib/expressions/numeric')
11-
};
8+
// Increment and decrement
9+
increment: column => `${column} = ${column} + 1`,
10+
decrement: column => `${column} = ${column} - 1`,
1211

13-
module.exports.operator = require('./lib/operators/logic');
12+
// Expression functions
13+
expression: {
14+
...require('./lib/expressions/boolean'),
15+
...require('./lib/expressions/numeric')
16+
},
17+
18+
// Operator variables
19+
operator: require('./lib/operators/logic'),
20+
21+
// PSQLError class (for error checking)
22+
PSQLError: require('./util/psql-error').PSQLError
23+
}

lib/queries.js

Lines changed: 99 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
const { PromiseDB } = require('./promisedb');
2-
const sqlstr = require('../util/sqlstr');
2+
const { accessError, asyncError } = require('../util/psql-error');
3+
const str = require('../util/string');
34
const sp = require('synchronized-promise');
45

56
// Current database (undefined if not open)
67
var db = undefined;
78

89
/**
910
* Opens a database file.
10-
* @param {string} file Path to the database file
11+
* @param {string} file Relative path to the database file
1112
*/
1213
function open(file) {
1314
db = new PromiseDB(file);
@@ -21,10 +22,19 @@ function close() {
2122
db = undefined;
2223
}
2324

25+
/**
26+
* Retrieves the database, if open.
27+
* @returns {PromiseDB|never}
28+
*/
29+
function get() {
30+
if (db instanceof PromiseDB) return db;
31+
accessError();
32+
}
33+
2434
/**
2535
* Dynamically opens and closes a database if a file is provided.
26-
* @param {string} file
27-
* @returns {void|Promise<Function>}
36+
* @param {string} file Relative path to the database file
37+
* @returns {void|Promise<void>}
2838
*/
2939
function dynamicAccess(file) {
3040
// If file is undefined or db is already open, do nothing
@@ -37,90 +47,104 @@ function dynamicAccess(file) {
3747
try {
3848
open(file);
3949
resolve({ then: close });
40-
} catch(error) {
50+
} catch (error) {
4151
reject(error);
4252
}
4353
});
4454
}
4555

46-
module.exports = {
47-
// Export database open/close functions (synchronous!)
48-
open, close,
56+
/* ================ QUERIES ================ */
4957

50-
/**
51-
* Retrieves the database, if open.
52-
* @returns {PromiseDB}
53-
*/
54-
getDatabase: function() {
55-
if (db instanceof PromiseDB) return db;
56-
throw new Error('Database is not open.');
57-
},
58+
/**
59+
* Asynchronous database query.
60+
* @param {RunOptions} options SQL statement
61+
* @returns {QueryPromise}
62+
*/
63+
async function run(options) {
64+
dynamicAccess(options.file);
65+
return await db.query(options.statement, options.args);
66+
}
5867

59-
/**
60-
* Asynchronous database query.
61-
* @param {RunOptions} options SQL statement
62-
* @returns {QueryPromise}
63-
*/
64-
run: async function(options) {
65-
dynamicAccess(options.file);
66-
return await db.query(options.statement, options.args);
67-
},
68+
/**
69+
* Asynchronous instert query.
70+
* @param {InsertOptions} options
71+
* @returns {Promise<void>}
72+
*/
73+
async function insert(options) {
74+
dynamicAccess(options.file);
75+
const sql = str.insertStr(options);
76+
await db.query(sql, options.values);
77+
}
6878

69-
/**
70-
* Asynchronous instert query.
71-
* @param {InsertOptions} options
72-
* @returns {Promise<void>}
73-
*/
74-
insert: async function(options) {
75-
dynamicAccess(options.file);
76-
const sql = sqlstr.insertStr(options);
77-
await db.query(sql, options.values);
78-
},
79+
/**
80+
* Asynchronous selection query.
81+
* @param {SelectionOptions} options
82+
* @returns {SelectionPromise}
83+
*/
84+
async function select(options) {
85+
dynamicAccess(options.file);
86+
const { sql, args } = str.selectStr(options);
87+
const data = await db.query(sql, args);
88+
return (data.length > 0 && options.first) ? data[0] : data;
89+
}
7990

80-
/**
81-
* Asynchronous selection query.
82-
* @param {SelectionOptions} options
83-
* @returns {SelectionPromise}
84-
*/
85-
select: async function(options) {
86-
dynamicAccess(options.file);
87-
const { sql, args } = sqlstr.selectStr(options);
88-
const data = await db.query(sql, args);
89-
return (data.length > 0 && options.first) ? data[0] : data;
90-
},
91+
/**
92+
* Asynchronous update query.
93+
* @param {UpdateOptions} options
94+
* @returns {Promise<void>}
95+
*/
96+
async function update(options) {
97+
dynamicAccess(options.file);
98+
const { sql, args } = str.updateStr(options);
99+
await db.query(sql, args);
100+
}
91101

92-
/**
93-
* Asynchronous update query.
94-
* @param {UpdateOptions} options
95-
* @returns {Promise<void>}
96-
*/
97-
update: async function(options) {
98-
dynamicAccess(options.file);
99-
const { sql, args } = sqlstr.updateStr(options);
100-
await db.query(sql, args);
101-
},
102+
/**
103+
* Asynchronous delete query.
104+
* @param {DeleteOptions} options
105+
* @returns {Promise<void>}
106+
*/
107+
async function remove(options) {
108+
dynamicAccess(options.file);
109+
const { sql, args } = str.deleteStr(options);
110+
await db.query(sql, args);
111+
}
112+
113+
/**
114+
* Asynchronous upsert query.
115+
* @param {UpsertOptions} options
116+
* @returns {Promise<void>}
117+
*/
118+
async function upsert(options) {
119+
await insert(options).catch(async error => {
120+
if (error.code !== 'SQLITE_CONSTRAINT') throw new error;
121+
if (!options.table) options.table = options.into;
122+
await update(options);
123+
});
124+
}
125+
126+
/**
127+
* Synchronous query.
128+
* @param {Function} query
129+
* @param {string[]|BaseOptions} options
130+
* @returns {QueryRetval}
131+
*/
132+
function sync(query, options = []) {
133+
if (query.constructor.name !== 'AsyncFunction') asyncError();
134+
return sp(query)(options);
135+
}
136+
137+
module.exports = {
138+
// Export database open/close functions (synchronous!)
139+
open, close, get,
140+
141+
// Queries
142+
run, insert, select, update, remove, upsert, sync,
102143

103144
/**
104-
* Asynchronous delete query.
145+
* Asynchronous delete query. Optional overload, since 'delete' is reserved keyword.
105146
* @param {DeleteOptions} options
106147
* @returns {Promise<void>}
107148
*/
108-
delete: async function(options) {
109-
dynamicAccess(options.file);
110-
const { sql, args } = sqlstr.deleteStr(options);
111-
await db.query(sql, args);
112-
},
113-
114-
/**
115-
* Synchronous query.
116-
* @param {function} query
117-
* @param {string[]|BaseOptions} options
118-
* @returns {QueryRetval}
119-
*/
120-
sync: function(query, options = []) {
121-
if (query.constructor.name !== 'AsyncFunction')
122-
throw new Error('Function is not asynchronous.');
123-
124-
return sp(query)(options);
125-
}
149+
delete: async options => await remove(options)
126150
}
File renamed without changes.

util/psql-error.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class PSQLError extends Error {
2+
constructor(name, message) {
3+
super(message);
4+
this.name = name;
5+
}
6+
}
7+
8+
module.exports = {
9+
PSQLError,
10+
11+
/**
12+
* Access error - database is not open!
13+
*/
14+
accessError: function() {
15+
throw new PSQLError('PSQLAccessError', 'Database is not open.');
16+
},
17+
18+
/**
19+
* Async error - tried to run a synchronous function with sync
20+
*/
21+
asyncError: function() {
22+
throw new PSQLError('PSQLAsyncError', 'Function is not asynchronous.');
23+
},
24+
25+
/**
26+
* Missing type error.
27+
* @param {string} missing Name of the missing type
28+
*/
29+
typeError: function(missing) {
30+
throw new PSQLError('PSQLTypeError', `No ${missing} specified.`);
31+
}
32+
}

util/sqlstr.js renamed to util/string.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
const { parseColumns, parseValues, parseClause } = require('./sqlhelp');
1+
const { parseColumns, parseValues, parseClause } = require('./helpers');
2+
const { typeError } = require('./psql-error');
3+
24
const emptyClause = { stmt: '', conditions: new Array() };
35

46
module.exports = {
@@ -7,9 +9,9 @@ module.exports = {
79
* @param {InsertOptions} options Arguments passed into {@link queries}
810
* @returns {string}
911
*/
10-
insertStr: function(options) {
11-
if (!(options.table || options.into) || !options.values)
12-
throw new Error('No table or values specified.');
12+
insertStr: function(options) {
13+
if (!(options.table || options.into)) typeError('table');
14+
if (!options.values) typeError('values');
1315

1416
let stmt = `INSERT INTO ${options.table ?? options.into}`;
1517

@@ -23,11 +25,11 @@ module.exports = {
2325
/**
2426
* Generates an SQL selection statement string.
2527
* @param {SelectionOptions} options Arguments passed into {@link queries}
26-
* @returns {string}
28+
* @returns {Object}
2729
*/
2830
selectStr: function(options) {
29-
if (!options.all && !options.columns)
30-
throw new Error('No columns specified.');
31+
if (!(options.table || options.from)) typeError('table');
32+
if (!(options.all || options.columns)) typeError('columns');
3133

3234
let stmt = options.all ? 'SELECT *' : 'SELECT ';
3335
let whereClause = emptyClause;
@@ -47,11 +49,11 @@ module.exports = {
4749
/**
4850
* Generates an SQL update statement string.
4951
* @param {UpdateOptions} options Arguments passed into {@link queries}
50-
* @returns {string}
52+
* @returns {Object}
5153
*/
5254
updateStr: function(options) {
53-
if (!options.table || !options.set)
54-
throw new Error('No table or columns specified.');
55+
if (!options.table) typeError('table');
56+
if (!options.set) typeError('set values');
5557

5658
let stmt = `UPDATE ${options.table}`;
5759

@@ -71,11 +73,10 @@ module.exports = {
7173
/**
7274
* Generates an SQL delete statement string.
7375
* @param {DeleteOptions} options Arguments passed into {@link queries}
74-
* @returns {string}
76+
* @returns {Object}
7577
*/
7678
deleteStr: function(options) {
77-
if (!(options.table || options.from))
78-
throw new Error('No table specified.');
79+
if (!(options.table || options.from)) typeError('table');
7980

8081
let stmt = `DELETE FROM ${options.table ?? options.from}`;
8182
let whereClause = emptyClause;

0 commit comments

Comments
 (0)