Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DB Lib #36

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3786889
First implementation of db lib
Jan 20, 2021
bfe0c5a
Support for long async call stack
Feb 15, 2021
e5ba3a7
Merge branch 'master' into db-lib
Mar 28, 2021
e3ad0ee
Minor fixes
Mar 28, 2021
0e113ca
sql.empty
Mar 28, 2021
0242dfd
db.sql
May 11, 2021
bda1681
Streamline sql
May 11, 2021
ffd8733
Minor fix
May 11, 2021
e70ad15
Minor method rename
May 11, 2021
c20e50a
Replace pgp helpers with own ones
May 12, 2021
7c304f5
sql template str with no special chars
May 14, 2021
3e6cb4c
Simplify Sql
May 15, 2021
bc2a80d
Remove pg-promise
May 15, 2021
0db0a2f
Add more sql helpers
May 15, 2021
365ace3
Improved db update and insert
May 22, 2021
10ca4bc
Minor fixes
May 22, 2021
1912c79
Minor imporvements
May 23, 2021
f117e26
Avoid generating SQL with identation
May 25, 2021
b27a601
Merge branch 'master' into db-lib
May 26, 2021
1088074
Minor change in docs
May 26, 2021
1588c95
Fixes
May 27, 2021
3c1e00d
Disallow stringifying Sql
May 27, 2021
c227328
Dasllow `db.query(string)`
May 27, 2021
89a487d
Fix password duration in the query
May 27, 2021
b3996a8
Decouple `sql`, reduce insert/update queryies
May 28, 2021
bb37228
Optimize no-columns case, simplify sql.sets
May 28, 2021
77bed9a
Fix ordering of toValue calls
May 28, 2021
d705e11
Unite sql, format and db
May 28, 2021
e7cf0b3
lib/db -> lib/index
May 28, 2021
0e783b4
Import lib/db from index only
May 28, 2021
2a6215b
Expose a `parseOnServer` options
May 28, 2021
859d5be
Simplify sql
May 29, 2021
dacf510
Make Queue external
May 30, 2021
48e9989
Import db/lib thru its index.js only
May 30, 2021
5901aed
Merge branch 'master' into db-lib
May 30, 2021
e892fab
Merge branch 'master' into db-lib
May 30, 2021
816e31b
Minor fixes
May 30, 2021
9ecd1ba
Merge branch 'master' into db-lib
May 31, 2021
df7310c
Minor refactor and imporvement of sql.insert
May 31, 2021
b6bc5ce
ditto
May 31, 2021
fca53a1
Add some sql tests
May 31, 2021
7050e4b
Minor change in a test
May 31, 2021
fd75c0c
Add some tests for `sql.insert`
May 31, 2021
a6a1e40
Add test for inserting of multiple rows
May 31, 2021
56f4594
Move lib in external repo Hrid
Jul 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions db.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@ const { db } = require('db')
const { delay } = require('utils/promise')

test('parallel sub transactions', async t => {
await db.query(`
await db.sql`
DROP TABLE IF EXISTS test_tx;
CREATE TABLE test_tx (
id SERIAL PRIMARY KEY,
"desc" TEXT
)
`)
`

async function runQueries (tx) {
for (const x of [1, 2, 3, 4, 5]) {
await tx.query('INSERT INTO test_tx ("desc") VALUES ($1)', [`query ${x}`])
await tx.sql`INSERT INTO test_tx ("desc") VALUES (${`query ${x}`})`
await delay(100)
}
}

async function runSubTx (tx) {
await tx.tx(async tx => {
await tx.query('INSERT INTO test_tx ("desc") VALUES (\'back-rolled\')')
await tx.sql`INSERT INTO test_tx ("desc") VALUES ('back-rolled')`
await delay(2.5e3)
// eslint-disable-next-line promise/catch-or-return
delay(100).then(() => tx.query('INSERT INTO test_tx ("desc") VALUES (\'leaked out\')')) // a bug-like case
delay(100).then(() => tx.sql`INSERT INTO test_tx ("desc") VALUES ('leaked out')'`) // a bug-like case
.catch(e => t.ok(e)) // a bug-like case
await tx.query('SOMETHING WRONG')
await tx.sql`SOMETHING WRONG`
}).catch(console.log)
}

Expand All @@ -36,7 +36,7 @@ test('parallel sub transactions', async t => {
])
})

const rows = await db.query('SELECT "desc" FROM test_tx')
const rows = await db.sql`SELECT "desc" FROM test_tx`

t.deepEqual(rows, [
{ desc: 'query 1' },
Expand Down
42 changes: 14 additions & 28 deletions db/cli.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,31 @@
// should only be ran through package.json scripts

const argv = require('mri')(process.argv.slice(2), {
boolean: true,
stopEarly: true,
})
const migratio = require('migratio')

const { pgpDB: db, sql, pgp } = require('db')

const command = argv._[0]
const fs = require('fs')
const { migrate } = require('postgres-migrations')
const { db } = require('db')

async function run () {
async function run (command) {
switch (command) {
case 'up':
case 'down':
case 'current':
return migratio[command]({
directory: 'db/migration',
tableName: 'migration',
verbose: true,
revision: argv.r,
db,
})
case 'migrate':
return db.task(t => migrate({ client: t.pgClient }, 'db/migration'))
case 'drop':
return db.query('DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;')
case 'seed':
return db.tx(t => {
return t.query(sql('seed'))
})
return db.sql`DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;`
case 'seed': {
const text = fs.readFileSync('db/seed.sql', 'utf8')
return db.tx(t => t.query({ text }))
}
default:
throw new Error(`"${command}" is not a valid migration command`)
}
}

run()
run(...process.argv.slice(2))
.then(() => {
pgp.end()
db.pgPool.end()
return process.exit(0)
})
.catch(e => {
console.error(e)
pgp.end()
db.pgPool.end()
return process.exit(1)
})
48 changes: 13 additions & 35 deletions db/index.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,25 @@
const pgp = require('pg-promise')({
// error (_err, e) { console.log('------\n', e.query) },
// query (e) { console.log('------\n', e.query) },
})

const pg = require('pg')
const { Database, Sql, sql, format } = require('hrid')
const error = require('error')
const { wrapDatabase } = require('utils/pgp-wrappers')

/// PG stuff

// https://github.com/brianc/node-pg-types/issues/50
const DATE_OID = 1082
pgp.pg.types.setTypeParser(DATE_OID, v => v)
// Don't store DB dates in Date!
pg.types.setTypeParser(1082, v => v)

/// DB stuff

const pgpDB = pgp(process.env.DATABASE_URL)
const pgPool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
})

const db = wrapDatabase(pgpDB, {
queryErrorHandler: e => {
const db = new Database(pgPool, {
queryErrorHandler: (e, query) => {
// console.log('------\n', query)
throw error('db.query', e)
},
debug: process.env.NODE_ENV !== 'production',
})

/// sql(file)

const queryFiles = new Map()

function sql (filename) {
if (!queryFiles.has(filename) || process.env.NODE_ENV === 'development') {
queryFiles.set(filename, new pgp.QueryFile(`${filename}.sql`, {
compress: process.env.NODE_ENV === 'production',
debug: process.env.NODE_ENV === 'development',
}))
}

return queryFiles.get(filename)
}

module.exports = {
db,
pgpDB,
helper: pgp.helpers,
pgp,
Sql,
sql,
util: pgp.utils,
format,
}
14 changes: 7 additions & 7 deletions dev-docs/loaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ const productRepo = require('repo/product')
// ... map, ...

async function list ({ limit = 10, includeUsers = false }) {
const orders = await db.any(`
const orders = await db.sql`
SELECT *
FROM "order"
LIMIT $[limit]
`, {limit})
LIMIT ${limit}
`
.then(map)

const loadUserWithRoles = async userId => {
Expand Down Expand Up @@ -177,12 +177,12 @@ const loadByUserId = loader.all({
const { byKeyed } = require('utils/data')

const loadFullName = loader((db, lockingClause) => async userIds => {
const rows = await db.any(`
const rows = await db.sql`
SELECT id, (first_name || ' ' || last_name) as "fullName"
FROM "user"
WHERE id IN ($1:csv)
${lockingClause} // <- remember to use the second argument
`, [userIds])
WHERE id = ANY (${userIds})
${sql.raw(lockingClause)} // <- remember to use the second argument
`

return userIds.map(byKeyed(rows, 'id', 'fullName', null))
}, {
Expand Down
22 changes: 2 additions & 20 deletions error.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
const NestedError = require('nested-error-stacks')
const _ = require('lodash')
const assert = require('assert')
const { QueryFileError, QueryResultError, queryResultErrorCode } = require('pg-promise').errors

const toml = require('utils/toml')

const errors = toml.parseFile('error.toml')

const inProduction = process.env.NODE_ENV === 'production'

// queryResultErrorCode:
// noData: int
// multiple: int
// notEmpty: int
const queryResultErrorName = _.invert(queryResultErrorCode)

function getCode (ec) {
const code = _.get(errors, ec)
if (!code) throw new TypeError(`invalid error const: ${ec}`)
return code
}

function checkDBErrorMappingKey (key) {
if (!key.includes('_') && !(key in queryResultErrorCode)) {
throw new TypeError(`invalid dbErrorHandler mapping key: ${key}`)
}
}

class GenericError extends NestedError {
constructor (ec, cause, status) {
super(ec, cause)
Expand Down Expand Up @@ -59,17 +46,14 @@ function error (ec, cause, status) {

function dbErrorHandler (mapping = {}) {
if (!inProduction) {
_.keys(mapping).forEach(checkDBErrorMappingKey)
_.values(mapping).filter(_.isString).forEach(getCode)
_.values(mapping).forEach(getCode)
}

return err => {
let cause = err
while (cause instanceof DatabaseError) cause = cause.nested

const key = cause instanceof QueryResultError
? queryResultErrorName[cause.code]
: cause.constraint
const key = cause.constraint

const handle = key && mapping[key]

Expand All @@ -90,8 +74,6 @@ error.AssertionError = assert.AssertionError
error.DatabaseError = DatabaseError
error.GenericError = GenericError
error.HttpError = HttpError
error.QueryFileError = QueryFileError
error.QueryResultError = QueryResultError
error.ValidationError = ValidationError

module.exports = error
13 changes: 4 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"connection-string": "^3.2.0",
"crypto-random-string": "^3.2.0",
"dotenv-safe": "^8.2.0",
"hrid": "^0.1.0",
"joi": "^17.2.1",
"joi-to-swagger": "^5.0.1",
"js-yaml": "^3.14.0",
Expand All @@ -30,13 +31,11 @@
"koa-send": "^5.0.0",
"lodash": "^4.17.21",
"memo-args": "gist:e941ee28eb4e4072ebbe9d696fdfb7af",
"migratio": "^2.0.3",
"mri": "^1.1.5",
"nested-error-stacks": "^2.1.0",
"nodemailer": "^6.4.16",
"npm-run-all": "^4.1.5",
"pg-promise": "^10.5.2",
"queue": "gist:b22ca12fcee4e1d4c3c0f85d43080f92",
"pg": "^8.6.0",
"postgres-migrations": "^5.1.1",
"redoc-cli": "^0.9.8",
"swagger-ui-dist": "^3.25.3"
},
Expand All @@ -63,15 +62,11 @@
"docs:build:bundle": "run-s docs:build docs:bundle",
"docs:serve:watch": "redoc-cli serve docs/api/docs.json --watch",
"ci": "run-s lint coverage",
"db:recreate": "run-s db:drop \"db up\"",
"db:migrate": "run-s \"db up\"",
"db:drop": "run-s \"db drop\"",
"db:seed": "run-s \"db seed\"",
"dev": "NODE_PATH=. node-dev --no-notify index.js",
"lint": "eslint .",
"db": "NODE_PATH=. node -r ./env db/cli.js",
"start": "NODE_PATH=. node index.js",
"test": "NODE_ENV=test run-s db:recreate db:seed test:run",
"test": "NODE_ENV=test run-s \"db drop\" \"db migrate\" \"db seed\" test:run",
"test:run": "NODE_PATH=. tape -r ./env '{!(node_modules)/**/*.test.js,./*.test.js}' | tap-difflet",
"coverage": "nyc --reporter=lcov --reporter=text npm run test"
}
Expand Down
Loading