Adding base SQL class which processes defined JSON structure into SQL.

This commit is contained in:
mike12345567 2021-06-03 16:31:24 +01:00
parent 01bd6f40bc
commit 0fba3cc8fd
5 changed files with 342 additions and 25 deletions

View File

@ -77,6 +77,7 @@
"jimp": "0.16.1",
"joi": "17.2.1",
"jsonschema": "1.4.0",
"knex": "^0.95.6",
"koa": "2.7.0",
"koa-body": "4.2.0",
"koa-compress": "4.0.1",

View File

@ -0,0 +1,11 @@
exports.Operation = {
CREATE: "CREATE",
READ: "READ",
UPDATE: "UPDATE",
DELETE: "DELETE",
}
exports.SortDirection = {
ASCENDING: "ASCENDING",
DESCENDING: "DESCENDING",
}

View File

@ -0,0 +1,124 @@
const { Operation, SortDirection } = require("./constants")
const BASE_LIMIT = 5000
function addFilters(query, filters) {
function iterate(structure, fn) {
for (let [key, value] of Object.entries(structure)) {
fn(key, value)
}
}
if (filters.string) {
iterate(filters.string, (key, value) => {
query = query.where(key, "like", `${value}%`)
})
}
if (filters.range) {
iterate(filters.range, (key, value) => {
if (!value.high || !value.low) {
return
}
query = query.whereBetween(key, [value.low, value.high])
})
}
if (filters.equal) {
iterate(filters.equal, (key, value) => {
query = query.where({ [key]: value })
})
}
if (filters.notEqual) {
iterate(filters.notEqual, (key, value) => {
query = query.whereNot({ [key]: value })
})
}
if (filters.empty) {
iterate(filters.empty, key => {
query = query.whereNull(key)
})
}
if (filters.notEmpty) {
iterate(filters.notEmpty, key => {
query = query.whereNotNull(key)
})
}
return query
}
function buildCreate(knex, json) {
const { endpoint, body } = json
let query = knex(endpoint.entityId)
return query.insert(body).toString()
}
function buildRead(knex, json, limit) {
const { endpoint, resource, filters, sort, paginate } = json
let query = knex(endpoint.entityId)
// handle select
if (resource.fields && resource.fields.length > 0) {
query = query.select(resource.fields)
} else {
query = query.select("*")
}
// handle where
query = addFilters(query, filters)
// handle sorting
if (sort) {
for (let [key, value] of Object.entries(sort)) {
const direction = value === SortDirection.ASCENDING ? "asc" : "desc"
query = query.orderBy(key, direction)
}
}
// handle pagination
if (paginate.page && paginate.limit) {
const page = paginate.page <= 1 ? 0 : paginate.page - 1
const offset = page * paginate.limit
query = query.offset(offset).limit(paginate.limit)
} else if (paginate.limit) {
query = query.limit(paginate.limit)
} else {
query.limit(limit)
}
return query.toString()
}
function buildUpdate(knex, json) {
const { endpoint, body, filters } = json
let query = knex(endpoint.entityId)
query = addFilters(query, filters)
return query.update(body).toString()
}
function buildDelete(knex, json) {
const { endpoint, filters } = json
let query = knex(endpoint.entityId)
query = addFilters(query, filters)
return query.delete().toString()
}
class SqlQueryBuilder {
// pass through client to get flavour of SQL
constructor(client, limit = BASE_LIMIT) {
this._client = client
this._limit = limit
}
buildQuery(json) {
const { endpoint } = json
const knex = require("knex")({ client: this._client })
const operation = endpoint.operation
switch (operation) {
case Operation.CREATE:
return buildCreate(knex, json)
case Operation.READ:
return buildRead(knex, json, this._limit)
case Operation.UPDATE:
return buildUpdate(knex, json)
case Operation.DELETE:
return buildDelete(knex, json)
default:
throw `Operation ${operation} type is not supported by SQL query builder`
}
}
}
module.exports = SqlQueryBuilder

View File

@ -0,0 +1,120 @@
const Sql = require("../base/sql")
const TABLE_NAME = "test"
function endpoint(table, operation) {
return {
datasourceId: "Postgres",
operation: operation,
entityId: table || TABLE_NAME,
}
}
function generateReadJson({ table, fields, filters, sort, paginate} = {}) {
return {
endpoint: endpoint(table || TABLE_NAME, "READ"),
resource: {
fields: fields || [],
},
filters: filters || {},
sort: sort || {},
paginate: paginate || {},
}
}
function generateCreateJson(table = TABLE_NAME, body = {}) {
return {
endpoint: endpoint(table, "CREATE"),
body,
}
}
function generateUpdateJson(table = TABLE_NAME, body = {}, filters = {}) {
return {
endpoint: endpoint(table, "UPDATE"),
filters,
body,
}
}
function generateDeleteJson(table = TABLE_NAME, filters = {}) {
return {
endpoint: endpoint(table, "DELETE"),
filters,
}
}
describe("SQL query builder", () => {
const limit = 500
const client = "pg"
let sql
beforeEach(() => {
sql = new Sql(client, limit)
})
it("should test a basic read", () => {
const query = sql.buildQuery(generateReadJson())
expect(query).toEqual(`select * from "${TABLE_NAME}" limit ${limit}`)
})
it("should test a read with specific columns", () => {
const query = sql.buildQuery(generateReadJson({
fields: ["name", "age"]
}))
expect(query).toEqual(`select "name", "age" from "${TABLE_NAME}" limit ${limit}`)
})
it("should test a where string starts with read", () => {
const query = sql.buildQuery(generateReadJson({
filters: {
string: {
name: "John",
}
}
}))
expect(query).toEqual(`select * from "${TABLE_NAME}" where "name" like 'John%' limit ${limit}`)
})
it("should test a where range read", () => {
const query = sql.buildQuery(generateReadJson({
filters: {
range: {
age: {
low: 2,
high: 10,
}
}
}
}))
expect(query).toEqual(`select * from "${TABLE_NAME}" where "age" between 2 and 10 limit ${limit}`)
})
it("should test an create statement", () => {
const query = sql.buildQuery(generateCreateJson(TABLE_NAME, {
name: "Michael",
age: 45,
}))
expect(query).toEqual(`insert into "${TABLE_NAME}" ("age", "name") values (45, 'Michael')`)
})
it("should test an update statement", () => {
const query = sql.buildQuery(generateUpdateJson(TABLE_NAME, {
name: "John"
}, {
equal: {
id: 1001,
}
}))
expect(query).toEqual(`update "${TABLE_NAME}" set "name" = 'John' where "id" = 1001`)
})
it("should test a delete statement", () => {
const query = sql.buildQuery(generateDeleteJson(TABLE_NAME, {
equal: {
id: 1001,
}
}))
expect(query).toEqual(`delete from "${TABLE_NAME}" where "id" = 1001`)
})
})

View File

@ -1057,10 +1057,10 @@
"@babel/helper-validator-identifier" "^7.14.0"
to-fast-properties "^2.0.0"
"@budibase/auth@^0.9.19":
version "0.9.19"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.19.tgz#f939dabed8c5294d850b4bbac972659f1f65927d"
integrity sha512-9jOW+yntZ9296KqSX6BQyyxqqe6geqfdhPBurvFRfPe9hngiMXuOrdxIsYe1DFtJOAQdJE7leV0lpdDf15QznQ==
"@budibase/auth@^0.9.24":
version "0.9.24"
resolved "https://registry.yarnpkg.com/@budibase/auth/-/auth-0.9.24.tgz#7c8e02076025d97734a5815a7c17562a566c23c2"
integrity sha512-uYYw29mOtzeQ16uBaOmcT+OabGhXxeTcXFexayZDv4s9d6F3wpvxi/t7MMemCmUP0cSxuFd9DOiJj58iMLILDA==
dependencies:
aws-sdk "^2.901.0"
bcryptjs "^2.4.3"
@ -1078,10 +1078,10 @@
uuid "^8.3.2"
zlib "^1.0.5"
"@budibase/bbui@^0.9.19":
version "0.9.19"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.19.tgz#72f10b46ce59de0fe381d200c62bfafd94c6d1ef"
integrity sha512-TBVE9iS+BNYj/F6s0guGBAb3HLRABDoSRQ+HuM66890g6d7BcxkjYn5mK8v3TDNw9OzTP1MVsPTNF6doH1nsAA==
"@budibase/bbui@^0.9.24":
version "0.9.24"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-0.9.24.tgz#ee0c2b5cbbf478492b525ef365c09311b0a59a70"
integrity sha512-FaZo1zk2CN/MeCarAw9qaDMO3PDIBzGTV5cZ1wHg5KDAEoTx4xLkxDdCbJOLbA42zBPUks5Mru7C9qomQUc8yQ==
dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1"
@ -1126,12 +1126,12 @@
svelte-flatpickr "^3.1.0"
svelte-portal "^1.0.0"
"@budibase/client@^0.9.19":
version "0.9.19"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.19.tgz#1be110304cc431bbe958eea086e7dc30e1fe261f"
integrity sha512-5Zf5aEnI4PEg1sOlLtaIZNifQpF2zfmRvG6EIGtCcgF3a0JJd+8Pwnhyki/X9SxhOhJZIboyJAufTD9LqjfVcQ==
"@budibase/client@^0.9.24":
version "0.9.24"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.9.24.tgz#a6bcc641f961715c1701c4dcc03fe0fb7a8de9bd"
integrity sha512-L3MIpRU0ncsFh9uPk2BcoZ1D52Of1LZNJGWxPt8QdhBvtLuKqD6sM3rdKuUA0EsUOn7hgPIZl82SEolQQz9Dyw==
dependencies:
"@budibase/string-templates" "^0.9.19"
"@budibase/string-templates" "^0.9.24"
regexparam "^1.3.0"
shortid "^2.2.15"
svelte-spa-router "^3.0.5"
@ -1168,22 +1168,22 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@budibase/standard-components@^0.9.19":
version "0.9.19"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.19.tgz#0acceee62334144229069680b887e80f16d37280"
integrity sha512-geq48LKT5JFHkmfhTKtvvV+9Iu5zEziEJga5zGh8r29JZiVW0W8a3qx2fTGlw65m3yqFUgtlVD/hSGgWXlatsw==
"@budibase/standard-components@^0.9.24":
version "0.9.24"
resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.9.24.tgz#f14b81fea6a1b2f7d5212b523ec9097be505af89"
integrity sha512-TWdfi044EaT1NigZB02NmTARfamlN8anHjc87QZfNdbdPxD9tgLxcPe/sPaPd5ACBjVC6McL3sKJs2xCbB78Ig==
dependencies:
"@budibase/bbui" "^0.9.19"
"@budibase/bbui" "^0.9.24"
"@spectrum-css/page" "^3.0.1"
"@spectrum-css/vars" "^3.0.1"
apexcharts "^3.22.1"
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^0.9.19":
version "0.9.19"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.19.tgz#a579fda12167f00ef08fc9528710883214814e8d"
integrity sha512-CD6pzNYwjtIxNofnwtsQWkI9Ty/3GCP63ekYAAR+ox/pd0mNmcG7SZiSorb2ZeLCMujI4afHuDoTVPy28su33g==
"@budibase/string-templates@^0.9.24":
version "0.9.24"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.9.24.tgz#8feea5e1510b8e615b5db2c58b7907b64ebdd8bf"
integrity sha512-CeACQYbzsxrvjcrcbzEIRHbcJgy7siXz1ybxBxgErcKX/WhVpPKKAcviyW+5m9RnRWpD3OWOfHFZdzrVreVxjw==
dependencies:
"@budibase/handlebars-helpers" "^0.11.3"
dayjs "^1.10.4"
@ -3630,6 +3630,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
colorette@^1.2.1, colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
@ -3647,6 +3652,11 @@ commander@^2.5.0, commander@^2.8.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
commoner@^0.10.1:
version "0.10.8"
resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5"
@ -3868,7 +3878,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
debug@4, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
debug@4, debug@4.3.1, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
@ -4426,6 +4436,11 @@ eslint@^6.8.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
esm@^3.2.25:
version "3.2.25"
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
esmangle-evaluator@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz#620d866ef4861b3311f75766d52a8572bb3c6336"
@ -5111,6 +5126,11 @@ get-value@^2.0.3, get-value@^2.0.6:
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
getopts@2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b"
integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@ -5679,6 +5699,11 @@ inquirer@^7.0.0:
strip-ansi "^6.0.0"
through "^2.3.6"
interpret@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
into-stream@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
@ -6865,6 +6890,25 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
knex@^0.95.6:
version "0.95.6"
resolved "https://registry.yarnpkg.com/knex/-/knex-0.95.6.tgz#5fc60ffc2935567bf122925526b1b06b8dbca785"
integrity sha512-noRcmkJl1MdicUbezrcr8OtVLcqQ/cfLIwgAx5EaxNxQOIJff88rBeyLywUScGhQNd/b78DIKKXZzLMrm6h/cw==
dependencies:
colorette "1.2.1"
commander "^7.1.0"
debug "4.3.1"
escalade "^3.1.1"
esm "^3.2.25"
getopts "2.2.5"
interpret "^2.2.0"
lodash "^4.17.21"
pg-connection-string "2.4.0"
rechoir "^0.7.0"
resolve-from "^5.0.0"
tarn "^3.0.1"
tildify "2.0.0"
koa-body@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/koa-body/-/koa-body-4.2.0.tgz#37229208b820761aca5822d14c5fc55cee31b26f"
@ -8337,7 +8381,7 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pg-connection-string@^2.4.0:
pg-connection-string@2.4.0, pg-connection-string@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10"
integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==
@ -9065,6 +9109,13 @@ recast@^0.11.17:
private "~0.1.5"
source-map "~0.5.0"
rechoir@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca"
integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==
dependencies:
resolve "^1.9.0"
redis-commands@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
@ -9304,7 +9355,7 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.10.0, resolve@^1.14.2:
resolve@^1.10.0, resolve@^1.14.2, resolve@^1.9.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
@ -10205,6 +10256,11 @@ tarn@^1.1.5:
resolved "https://registry.yarnpkg.com/tarn/-/tarn-1.1.5.tgz#7be88622e951738b9fa3fb77477309242cdddc2d"
integrity sha512-PMtJ3HCLAZeedWjJPgGnCvcphbCOMbtZpjKgLq3qM5Qq9aQud+XHrL0WlrlgnTyS8U+jrjGbEXprFcQrxPy52g==
tarn@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.1.tgz#ebac2c6dbc6977d34d4526e0a7814200386a8aec"
integrity sha512-6usSlV9KyHsspvwu2duKH+FMUhqJnAh6J5J/4MITl8s94iSUQTLkJggdiewKv4RyARQccnigV48Z+khiuVZDJw==
tedious@^6.6.2:
version "6.7.0"
resolved "https://registry.yarnpkg.com/tedious/-/tedious-6.7.0.tgz#ad02365f16f9e0416b216e13d3f83c53addd42ca"
@ -10306,6 +10362,11 @@ through@^2.3.6, through@^2.3.8, through@~2.3.4:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tildify@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a"
integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==
time-stamp@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"