Browse Source

Fixing an issue with relationship modal breaking when multiple data sources available to relate to, also fixing an pile of issues with creating and reading rows from SQL server plus.

pull/4089/head
mike12345567 5 years ago
parent
commit
b34cef26c3
  1. 13
      packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte
  2. 2
      packages/server/src/definitions/datasource.ts
  3. 83
      packages/server/src/integrations/base/sql.ts
  4. 35
      packages/server/src/integrations/microsoftSqlServer.ts
  5. 61
      packages/server/src/integrations/mysql.ts
  6. 2
      packages/server/src/integrations/utils.ts

13
packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte

@ -59,9 +59,6 @@
let deletion
$: checkConstraints(field)
$: tableOptions = $tables.list.filter(
opt => opt._id !== $tables.draft._id && opt.type === table.type
)
$: required = !!field?.constraints?.presence || primaryDisplay
$: uneditable =
$tables.selected?._id === TableNames.USERS &&
@ -88,6 +85,14 @@
field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE
$: relationshipOptions = getRelationshipOptions(field)
$: external = table.type === "external"
// in the case of internal tables the sourceId will just be undefined
$: tableOptions = $tables.list.filter(
opt =>
opt._id !== $tables.draft._id &&
opt.type === table.type &&
table.sourceId === opt.sourceId
)
$: console.log(tableOptions)
async function saveColumn() {
if (field.type === AUTO_TYPE) {
@ -174,7 +179,7 @@
if (!field || !field.tableId) {
return null
}
const linkTable = tableOptions.find(table => table._id === field.tableId)
const linkTable = tableOptions?.find(table => table._id === field.tableId)
if (!linkTable) {
return null
}

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

@ -119,7 +119,7 @@ export interface SortJson {
export interface PaginationJson {
limit: number
page: string | number
page?: string | number
}
export interface RelationshipsJson {

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

@ -216,6 +216,10 @@ class InternalBuilder {
query = query.orderBy(key, direction)
}
}
if (this.client === "mssql" && !sort && paginate?.limit) {
// @ts-ignore
query = query.orderBy(json.meta?.table?.primary[0])
}
query = this.addFilters(tableName, query, filters)
// @ts-ignore
let preQuery: KnexQuery = knex({
@ -301,6 +305,85 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
// @ts-ignore
return query.toSQL().toNative()
}
async getReturningRow(queryFn: Function, json: QueryJson) {
if (!json.extra || !json.extra.idFilter) {
return {}
}
const input = this._query({
endpoint: {
...json.endpoint,
operation: Operation.READ,
},
resource: {
fields: [],
},
filters: json.extra.idFilter,
paginate: {
limit: 1,
},
meta: json.meta,
})
return queryFn(input, Operation.READ)
}
// 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(id: any, json: QueryJson) {
if (!id || !json.meta?.table || !json.meta.table.primary) {
return json
}
const primaryKey = json.meta.table.primary?.[0]
json.extra = {
idFilter: {
equal: {
[primaryKey]: id,
},
},
}
return json
}
// this function recreates the returning functionality of postgres
async queryWithReturning(
json: QueryJson,
queryFn: Function,
processFn: Function = (result: any) => result
) {
const sqlClient = this.getSqlClient()
const operation = this._operation(json)
const input = this._query(json, { disableReturning: true })
if (Array.isArray(input)) {
const responses = []
for (let query of input) {
responses.push(await queryFn(query, operation))
}
return responses
}
let row
// need to manage returning, a feature mySQL can't do
if (operation === Operation.DELETE) {
row = processFn(await this.getReturningRow(queryFn, json))
}
const response = await queryFn(input, operation)
const results = processFn(response)
// same as delete, manage returning
if (operation === Operation.CREATE || operation === Operation.UPDATE) {
let id
if (sqlClient === "mssql") {
id = results?.[0].id
} else if (sqlClient === "mysql") {
id = results?.insertId
}
row = processFn(
await this.getReturningRow(queryFn, this.checkLookupKeys(id, json))
)
}
if (operation !== Operation.READ) {
return row
}
return results.length ? results : [{ [operation.toLowerCase()]: true }]
}
}
module.exports = SqlQueryBuilder

35
packages/server/src/integrations/microsoftSqlServer.ts

@ -1,8 +1,9 @@
import {
Integration,
DatasourceFieldTypes,
QueryTypes,
Integration,
Operation,
QueryJson,
QueryTypes,
SqlQuery,
} from "../definitions/datasource"
import { getSqlQuery } from "./utils"
@ -103,15 +104,26 @@ module MSSQLModule {
json: DatasourceFieldTypes.JSON,
}
async function internalQuery(client: any, query: SqlQuery) {
async function internalQuery(
client: any,
query: SqlQuery,
operation: string | undefined = undefined
) {
const request = client.request()
try {
if (Array.isArray(query.bindings)) {
let count = 0
for (let binding of query.bindings) {
client.input(`p${count++}`, binding)
request.input(`p${count++}`, binding)
}
}
return await client.query(query.sql)
// this is a hack to get the inserted ID back,
// no way to do this with Knex nicely
const sql =
operation === Operation.CREATE
? `${query.sql}; SELECT SCOPE_IDENTITY() AS id;`
: query.sql
return await request.query(sql)
} catch (err) {
// @ts-ignore
throw new Error(err)
@ -180,8 +192,7 @@ module MSSQLModule {
async connect() {
try {
const client = await this.pool.connect()
this.client = client.request()
this.client = await this.pool.connect()
} catch (err) {
// @ts-ignore
throw new Error(err)
@ -276,10 +287,12 @@ module MSSQLModule {
async query(json: QueryJson) {
await this.connect()
const operation = this._operation(json).toLowerCase()
const input = this._query(json)
const response = await internalQuery(this.client, input)
return response.recordset ? response.recordset : [{ [operation]: true }]
const operation = this._operation(json)
const queryFn = (query: any, op: string) =>
internalQuery(this.client, query, op)
const processFn = (result: any) =>
result.recordset ? result.recordset : [{ [operation]: true }]
return this.queryWithReturning(json, queryFn, processFn)
}
}

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

@ -223,67 +223,12 @@ module MySQLModule {
return results.length ? results : [{ deleted: true }]
}
async getReturningRow(json: QueryJson) {
if (!json.extra || !json.extra.idFilter) {
return {}
}
const input = this._query({
endpoint: {
...json.endpoint,
operation: Operation.READ,
},
fields: [],
filters: json.extra.idFilter,
paginate: {
limit: 1,
},
})
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) {
const operation = this._operation(json)
this.client.connect()
const input = this._query(json, { disableReturning: true })
if (Array.isArray(input)) {
const responses = []
for (let query of input) {
responses.push(await internalQuery(this.client, query, false))
}
return responses
}
let row
// need to manage returning, a feature mySQL can't do
if (operation === operation.DELETE) {
row = this.getReturningRow(json)
}
const results = await internalQuery(this.client, input, false)
// same as delete, manage returning
if (operation === Operation.CREATE || operation === Operation.UPDATE) {
row = this.getReturningRow(this.checkLookupKeys(results, json))
}
const queryFn = (query: any) => internalQuery(this.client, query, false)
const output = await this.queryWithReturning(json, queryFn)
this.client.end()
if (operation !== Operation.READ) {
return row
}
return results.length ? results : [{ [operation.toLowerCase()]: true }]
return output
}
}

2
packages/server/src/integrations/utils.ts

@ -1,4 +1,4 @@
import { SqlQuery } from "../definitions/datasource"
import { Operation, SqlQuery } from "../definitions/datasource"
import { Datasource, Table } from "../definitions/common"
import { SourceNames } from "../definitions/datasource"
const { DocumentTypes, SEPARATOR } = require("../db/utils")

Loading…
Cancel
Save