Browse Source

Fixes issue #2616 - this is a slightly complex fix and handles a few other issues with mysql (around returning on creation of a row and relationships) - a new mechanism is now used for pagination and limiting which makes sure the limits are applied to the outer table rather than the combination of the outer and the joined.

pull/2724/head
mike12345567 5 years ago
parent
commit
1952dc308e
  1. 2
      packages/server/scripts/integrations/postgres/docker-compose.yml
  2. 3
      packages/server/src/api/controllers/row/ExternalRequest.ts
  3. 5
      packages/server/src/definitions/datasource.ts
  4. 44
      packages/server/src/integrations/base/sql.ts
  5. 21
      packages/server/src/integrations/mysql.ts

2
packages/server/scripts/integrations/postgres/docker-compose.yml

@ -15,7 +15,7 @@ services:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql - ./init.sql:/docker-entrypoint-initdb.d/init.sql
pgadmin: pgadmin:
container_name: pgadmin container_name: pgadmin-pg
image: dpage/pgadmin4 image: dpage/pgadmin4
restart: always restart: always
environment: environment:

3
packages/server/src/api/controllers/row/ExternalRequest.ts

@ -544,6 +544,9 @@ module External {
extra: { extra: {
idFilter: buildFilters(id || generateIdForRow(row, table), {}, table), idFilter: buildFilters(id || generateIdForRow(row, table), {}, table),
}, },
meta: {
table,
}
} }
// can't really use response right now // can't really use response right now
const response = await makeExternalQuery(appId, json) const response = await makeExternalQuery(appId, json)

5
packages/server/src/definitions/datasource.ts

@ -1,3 +1,5 @@
import {Table} from "./common";
export enum Operation { export enum Operation {
CREATE = "CREATE", CREATE = "CREATE",
READ = "READ", READ = "READ",
@ -136,6 +138,9 @@ export interface QueryJson {
sort?: SortJson sort?: SortJson
paginate?: PaginationJson paginate?: PaginationJson
body?: object body?: object
meta?: {
table?: Table,
}
extra?: { extra?: {
idFilter?: SearchFilters idFilter?: SearchFilters
} }

44
packages/server/src/integrations/base/sql.ts

@ -1,7 +1,5 @@
import { Knex, knex } from "knex" import { Knex, knex } from "knex"
const BASE_LIMIT = 5000 const BASE_LIMIT = 5000
// if requesting a single row then need to up the limit for the sake of joins
const SINGLE_ROW_LIMIT = 100
import { import {
QueryJson, QueryJson,
SearchFilters, SearchFilters,
@ -146,46 +144,48 @@ function buildCreate(
function buildRead(knex: Knex, json: QueryJson, limit: number): KnexQuery { function buildRead(knex: Knex, json: QueryJson, limit: number): KnexQuery {
let { endpoint, resource, filters, sort, paginate, relationships } = json let { endpoint, resource, filters, sort, paginate, relationships } = json
const tableName = endpoint.entityId const tableName = endpoint.entityId
let query: KnexQuery = knex(tableName)
// select all if not specified // select all if not specified
if (!resource) { if (!resource) {
resource = { fields: [] } resource = { fields: [] }
} }
let selectStatement: string|string[] = "*"
// handle select // handle select
if (resource.fields && resource.fields.length > 0) { if (resource.fields && resource.fields.length > 0) {
// select the resources as the format "table.columnName" - this is what is provided // select the resources as the format "table.columnName" - this is what is provided
// by the resource builder further up // by the resource builder further up
query = query.select(resource.fields.map(field => `${field} as ${field}`)) selectStatement = resource.fields.map(field => `${field} as ${field}`)
} else {
query = query.select("*")
}
// handle where
query = addFilters(tableName, query, filters)
// handle join
query = addRelationships(query, tableName, relationships)
// handle sorting
if (sort) {
for (let [key, value] of Object.entries(sort)) {
const direction = value === SortDirection.ASCENDING ? "asc" : "desc"
query = query.orderBy(key, direction)
}
} }
let foundLimit = limit || BASE_LIMIT let foundLimit = limit || BASE_LIMIT
// handle pagination // handle pagination
let foundOffset: number | null = null
if (paginate && paginate.page && paginate.limit) { if (paginate && paginate.page && paginate.limit) {
// @ts-ignore // @ts-ignore
const page = paginate.page <= 1 ? 0 : paginate.page - 1 const page = paginate.page <= 1 ? 0 : paginate.page - 1
const offset = page * paginate.limit const offset = page * paginate.limit
foundLimit = paginate.limit foundLimit = paginate.limit
query = query.offset(offset) foundOffset = offset
} else if (paginate && paginate.limit) { } else if (paginate && paginate.limit) {
foundLimit = paginate.limit foundLimit = paginate.limit
} }
if (foundLimit === 1) { // start building the query
foundLimit = SINGLE_ROW_LIMIT let query: KnexQuery = knex(tableName).limit(foundLimit)
if (foundOffset) {
query = query.offset(foundOffset)
} }
query = query.limit(foundLimit) if (sort) {
return query for (let [key, value] of Object.entries(sort)) {
const direction = value === SortDirection.ASCENDING ? "asc" : "desc"
query = query.orderBy(key, direction)
}
}
query = addFilters(tableName, query, filters)
// @ts-ignore
let preQuery: KnexQuery = knex({
// @ts-ignore
[tableName]: query,
}).select(selectStatement)
// handle joins
return addRelationships(preQuery, tableName, relationships)
} }
function buildUpdate( function buildUpdate(

21
packages/server/src/integrations/mysql.ts

@ -104,7 +104,7 @@ module MySQLModule {
client: any, client: any,
query: SqlQuery, query: SqlQuery,
connect: boolean = true connect: boolean = true
): Promise<any[]> { ): Promise<any[]|any> {
// Node MySQL is callback based, so we must wrap our call in a promise // Node MySQL is callback based, so we must wrap our call in a promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (connect) { if (connect) {
@ -238,6 +238,23 @@ module MySQLModule {
return internalQuery(this.client, input, false) return internalQuery(this.client, input, false)
} }
// when creating if an ID has been inserted need to make sure
// the id filter is enriched with it before trying to retrieve the row
checkLookupKeys(results: any, json: QueryJson) {
if (!results?.insertId || !json.meta?.table || !json.meta.table.primary) {
return json
}
const primaryKey = json.meta.table.primary?.[0]
json.extra = {
idFilter: {
equal: {
[primaryKey]: results.insertId
},
}
}
return json
}
async query(json: QueryJson) { async query(json: QueryJson) {
const operation = this._operation(json) const operation = this._operation(json)
this.client.connect() this.client.connect()
@ -250,7 +267,7 @@ module MySQLModule {
const results = await internalQuery(this.client, input, false) const results = await internalQuery(this.client, input, false)
// same as delete, manage returning // same as delete, manage returning
if (operation === Operation.CREATE || operation === Operation.UPDATE) { if (operation === Operation.CREATE || operation === Operation.UPDATE) {
row = this.getReturningRow(json) row = this.getReturningRow(this.checkLookupKeys(results, json))
} }
this.client.end() this.client.end()
if (operation !== Operation.READ) { if (operation !== Operation.READ) {

Loading…
Cancel
Save