This commit is contained in:
Martin McKeaveney 2021-06-15 13:37:21 +01:00
commit 3eaaff9901
8 changed files with 117 additions and 46 deletions

View File

@ -34,8 +34,8 @@ exports.save = async function (ctx) {
if (ctx.query.refresh) {
const PlusConnector = plusIntegrations[datasource.source].integration
const connector = new PlusConnector(ctx.request.body.config, datasource)
await connector.init()
const connector = new PlusConnector(ctx.request.body.config)
await connector.init(datasource._id)
datasource.entities = connector.tables
}

View File

@ -2,15 +2,33 @@ const CouchDB = require("../../../db")
const { makeExternalQuery } = require("./utils")
const { DataSourceOperation, SortDirection } = require("../../../constants")
async function buildIDFilter(id) {
if (!id) {
return {}
async function getTable(appId, datasourceId, tableName) {
const db = new CouchDB(appId)
const datasource = await db.get(datasourceId)
if (!datasource || !datasource.entities) {
throw "Datasource is not configured fully."
}
return Object.values(datasource.entities).find(
entity => entity.name === tableName
)
}
function buildIDFilter(id, table) {
if (!id || !table) {
return null
}
// if used as URL parameter it will have been joined
if (typeof id === "string") {
id = id.split(",")
}
const primary = table.primary
const equal = {}
for (let field of primary) {
// work through the ID and get the parts
equal[field] = id.shift()
}
// TODO: work out how to use the schema to get filter
return {
equal: {
id: id,
},
equal,
}
}
@ -18,20 +36,24 @@ async function handleRequest(
appId,
operation,
tableId,
{ id, row, filters, sort, paginate }
{ id, row, filters, sort, paginate } = {}
) {
let [datasourceId, tableName] = tableId.split("/")
let idFilter = buildIDFilter(id)
const parts = tableId.split("_")
let tableName = parts.pop()
let datasourceId = parts.join("_")
const table = await getTable(appId, datasourceId, tableName)
if (!table) {
throw `Unable to process query, table "${tableName}" not defined.`
}
// try and build an id filter if required
let idFilters = buildIDFilter(id)
let json = {
endpoint: {
datasourceId,
entityId: tableName,
operation,
},
filters: {
...filters,
...idFilter,
},
filters: idFilters != null ? idFilters : filters,
sort,
paginate,
body: row,
@ -65,15 +87,25 @@ exports.save = async ctx => {
}
exports.fetchView = async ctx => {
// TODO: don't know what this does for external
// there are no views in external data sources, shouldn't ever be called
// for now just fetch
ctx.params.tableId = ctx.params.viewName.split("all_")[1]
return exports.fetch(ctx)
}
exports.fetchTableRows = async ctx => {
// TODO: this is a basic read?
exports.fetch = async ctx => {
const appId = ctx.appId
const tableId = ctx.params.tableId
ctx.body = await handleRequest(appId, DataSourceOperation.READ, tableId)
}
exports.find = async ctx => {
// TODO: single find
const appId = ctx.appId
const id = ctx.params.rowId
const tableId = ctx.params.tableId
ctx.body = await handleRequest(appId, DataSourceOperation.READ, tableId, {
id,
})
}
exports.destroy = async ctx => {
@ -85,7 +117,18 @@ exports.destroy = async ctx => {
}
exports.bulkDestroy = async ctx => {
// TODO: iterate through rows, build a large OR filter?
const appId = ctx.appId
const { rows } = ctx.request.body
const tableId = ctx.params.tableId
// TODO: this can probably be optimised to a single SQL statement in the future
let promises = []
for (let row of rows) {
promises.push(handleRequest(appId, DataSourceOperation.DELETE, tableId, {
id: row._id,
}))
}
await Promise.all(promises)
ctx.body = { response: { ok: true }, rows }
}
exports.search = async ctx => {
@ -123,7 +166,6 @@ exports.validate = async ctx => {
}
exports.fetchEnrichedRow = async ctx => {
// TODO: should this join?
const appId = ctx.appId
ctx.body = {}
// TODO: How does this work
ctx.throw(501, "Not implemented")
}

View File

@ -1,8 +1,11 @@
const internal = require("./internal")
const external = require("./external")
const { DocumentTypes } = require("../../../db/utils")
function pickApi(tableId) {
// TODO: go to external
if (tableId.includes(DocumentTypes.DATASOURCE)) {
return external
}
return internal
}
@ -33,7 +36,6 @@ exports.patch = async ctx => {
}
exports.save = async function (ctx) {
// TODO: this used to handle bulk delete, need to update builder/client
const appId = ctx.appId
const tableId = getTableId(ctx)
try {
@ -55,10 +57,10 @@ exports.fetchView = async function (ctx) {
}
}
exports.fetchTableRows = async function (ctx) {
exports.fetch = async function (ctx) {
const tableId = getTableId(ctx)
try {
ctx.body = await pickApi(tableId).fetchTableRows(ctx)
ctx.body = await pickApi(tableId).fetch(ctx)
} catch (err) {
ctx.throw(400, err)
}

View File

@ -142,7 +142,7 @@ exports.fetchView = async ctx => {
// if this is a table view being looked for just transfer to that
if (viewName.startsWith(TABLE_VIEW_BEGINS_WITH)) {
ctx.params.tableId = viewName.substring(4)
return exports.fetchTableRows(ctx)
return exports.fetch(ctx)
}
const db = new CouchDB(appId)
@ -195,7 +195,7 @@ exports.fetchView = async ctx => {
return rows
}
exports.fetchTableRows = async ctx => {
exports.fetch = async ctx => {
const appId = ctx.appId
const db = new CouchDB(appId)

View File

@ -24,7 +24,7 @@ router
"/api/:tableId/rows",
paramResource("tableId"),
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
rowController.fetchTableRows
rowController.fetch
)
.get(
"/api/:tableId/rows/:rowId",
@ -38,7 +38,6 @@ router
authorized(PermissionTypes.TABLE, PermissionLevels.READ),
rowController.search
)
.post(
"/api/:tableId/rows",
paramResource("tableId"),

View File

@ -3,6 +3,8 @@ const { DataSourceOperation, SortDirection } = require("../../constants")
const BASE_LIMIT = 5000
function addFilters(query, filters) {
// if all or specified in filters, then everything is an or
const allOr = !!filters.allOr
function iterate(structure, fn) {
for (let [key, value] of Object.entries(structure)) {
fn(key, value)
@ -13,7 +15,8 @@ function addFilters(query, filters) {
}
if (filters.string) {
iterate(filters.string, (key, value) => {
query = query.where(key, "like", `${value}%`)
const fnc = allOr ? "orWhere" : "where"
query = query[fnc](key, "like", `${value}%`)
})
}
if (filters.range) {
@ -21,27 +24,32 @@ function addFilters(query, filters) {
if (!value.high || !value.low) {
return
}
query = query.whereBetween(key, [value.low, value.high])
const fnc = allOr ? "orWhereBetween" : "whereBetween"
query = query[fnc](key, [value.low, value.high])
})
}
if (filters.equal) {
iterate(filters.equal, (key, value) => {
query = query.where({ [key]: value })
const fnc = allOr ? "orWhere" : "where"
query = query[fnc]({ [key]: value })
})
}
if (filters.notEqual) {
iterate(filters.notEqual, (key, value) => {
query = query.whereNot({ [key]: value })
const fnc = allOr ? "orWhereNot" : "whereNot"
query = query[fnc]({ [key]: value })
})
}
if (filters.empty) {
iterate(filters.empty, key => {
query = query.whereNull(key)
const fnc = allOr ? "orWhereNull" : "whereNull"
query = query[fnc](key)
})
}
if (filters.notEmpty) {
iterate(filters.notEmpty, key => {
query = query.whereNotNull(key)
const fnc = allOr ? "orWhereNotNull" : "whereNotNull"
query = query[fnc](key)
})
}
return query

View File

@ -82,12 +82,16 @@ class PostgresPlus extends Sql {
this.client = this.pool
}
async init() {
async init(datasourceId) {
let keys = []
try {
const primaryKeysResponse = await this.client.query(this.PRIMARY_KEYS_SQL)
const primaryKeys = {}
for (let table of primaryKeysResponse.rows) {
primaryKeys[table.column_name] = table.primary_key
keys.push(table.column_name)
}
} catch (err) {
// TODO: this try catch method isn't right
keys = ["id"]
}
const columnsResponse = await this.client.query(this.COLUMNS_SQL)
@ -100,9 +104,9 @@ class PostgresPlus extends Sql {
// table key doesn't exist yet
if (!tables[tableName]) {
tables[tableName] = {
_id: `${this.datasource._id}${SEPARATOR}${tableName}`,
_id: `${datasourceId}${SEPARATOR}${tableName}`,
// TODO: this needs to accommodate composite keys
primary: primaryKeys[tableName],
primary: keys,
name: tableName,
schema: {},
}

View File

@ -102,6 +102,22 @@ describe("SQL query builder", () => {
})
})
it("should test for multiple IDs with OR", () => {
const query = sql._query(generateReadJson({
filters: {
equal: {
age: 10,
name: "John",
},
allOr: true,
}
}))
expect(query).toEqual({
bindings: [10, "John", limit],
sql: `select * from "${TABLE_NAME}" where ("age" = $1) or ("name" = $2) limit $3`
})
})
it("should test an create statement", () => {
const query = sql._query(generateCreateJson(TABLE_NAME, {
name: "Michael",