Final fix for #8882 - adding text to show the error - as well as fixing an issue with many to many updating correctly.

This commit is contained in:
mike12345567 2022-12-02 17:28:37 +00:00
parent 2a51091ffb
commit 0ca1c359a5
7 changed files with 186 additions and 89 deletions

View File

@ -45,6 +45,23 @@
const touched = writable({}) const touched = writable({})
function invalidThroughTable({ through, throughTo, throughFrom }) {
// need to know the foreign key columns to check error
if (!through || !throughTo || !throughFrom) {
return false
}
const throughTable = plusTables.find(tbl => tbl._id === through)
const otherColumns = Object.values(throughTable.schema).filter(
col => col.name !== throughFrom && col.name !== throughTo
)
for (let col of otherColumns) {
if (col.constraints?.presence && !col.autocolumn) {
return true
}
}
return false
}
function checkForErrors(fromRelate, toRelate) { function checkForErrors(fromRelate, toRelate) {
const isMany = const isMany =
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
@ -59,6 +76,10 @@
if ($touched.through && isMany && !fromRelate.through) { if ($touched.through && isMany && !fromRelate.through) {
errObj.through = tableNotSet errObj.through = tableNotSet
} }
if ($touched.through && invalidThroughTable(fromRelate)) {
errObj.through =
"Table contains non-nullable columns which aren't generated"
}
if ($touched.foreign && !isMany && !fromRelate.fieldName) { if ($touched.foreign && !isMany && !fromRelate.fieldName) {
errObj.foreign = "Please pick the foreign key" errObj.foreign = "Please pick the foreign key"
} }

View File

@ -588,7 +588,10 @@ export class ExternalRequest {
for (let [colName, { isMany, rows, tableId }] of Object.entries(related)) { for (let [colName, { isMany, rows, tableId }] of Object.entries(related)) {
const table: Table | undefined = this.getTable(tableId) const table: Table | undefined = this.getTable(tableId)
// if its not the foreign key skip it, nothing to do // if its not the foreign key skip it, nothing to do
if (!table || (table.primary && table.primary.indexOf(colName) !== -1)) { if (
!table ||
(!isMany && table.primary && table.primary.indexOf(colName) !== -1)
) {
continue continue
} }
for (let row of rows) { for (let row of rows) {

View File

@ -0,0 +1,134 @@
export interface MSSQLTablesResponse {
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
TABLE_TYPE: string
}
export interface MSSQLColumn {
IS_COMPUTED: number
IS_IDENTITY: number
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
COLUMN_NAME: string
ORDINAL_POSITION: number
COLUMN_DEFAULT: null | any
IS_NULLABLE: "NO" | "YES"
DATA_TYPE: string
CHARACTER_MAXIMUM_LENGTH: null | number
CHARACTER_OCTET_LENGTH: null | number
NUMERIC_PRECISION: null | number
NUMERIC_PRECISION_RADIX: null | number
NUMERIC_SCALE: null | number
DATETIME_PRECISION: null | string
CHARACTER_SET_CATALOG: null | string
CHARACTER_SET_SCHEMA: null | string
CHARACTER_SET_NAME: null | string
COLLATION_CATALOG: null | string
COLLATION_SCHEMA: null | string
COLLATION_NAME: null | string
DOMAIN_CATALOG: null | string
DOMAIN_SCHEMA: null | string
DOMAIN_NAME: null | string
}
export interface PostgresColumn {
table_catalog: string
table_schema: string
table_name: string
column_name: string
ordinal_position: number
column_default: null | any
is_nullable: "NO" | "YES"
data_type: string
character_maximum_length: null | number
character_octet_length: null | number
numeric_precision: null | number
numeric_precision_radix: null | number
numeric_scale: null | number
datetime_precision: null | string
interval_type: null | string
interval_precision: null | string
character_set_catalog: null | string
character_set_schema: null | string
character_set_name: null | string
collation_catalog: null | string
collation_schema: null | string
collation_name: null | string
domain_catalog: null | string
domain_schema: null | string
domain_name: null | string
udt_catalog: string
udt_schema: string
udt_name: string
scope_catalog: null | string
scope_schema: null | string
scope_name: null | string
maximum_cardinality: null | string
dtd_identifier: string
is_self_referencing: "NO" | "YES"
is_identity: "NO" | "YES"
identity_generation: null | number
identity_start: null | number
identity_increment: null | number
identity_maximum: null | number
identity_minimum: null | number
identity_cycle: "NO" | "YES"
is_generated: "NEVER"
generation_expression: null | string
is_updatable: "NO" | "YES"
}
export interface MySQLColumn {
Field: string
Type: string
Null: "NO" | "YES"
Key: "PRI" | "MUL" | ""
Default: null | any
Extra: null | string
}
/**
* Raw query response
*/
export interface OracleColumnsResponse {
TABLE_NAME: string
COLUMN_NAME: string
DATA_TYPE: string
DATA_DEFAULT: null | string
COLUMN_ID: number
CONSTRAINT_NAME: null | string
CONSTRAINT_TYPE: null | string
R_CONSTRAINT_NAME: null | string
SEARCH_CONDITION: null | string
}
/**
* An oracle constraint
*/
export interface OracleConstraint {
name: string
type: string
relatedConstraintName: null | string
searchCondition: null | string
}
/**
* An oracle column and it's related constraints
*/
export interface OracleColumn {
name: string
type: string
default: null | string
id: number
constraints: { [key: string]: OracleConstraint }
}
/**
* An oracle table and it's related columns
*/
export interface OracleTable {
name: string
columns: { [key: string]: OracleColumn }
}

View File

@ -17,6 +17,7 @@ import {
SqlClient, SqlClient,
} from "./utils" } from "./utils"
import Sql from "./base/sql" import Sql from "./base/sql"
import { MSSQLTablesResponse, MSSQLColumn } from "./base/types"
const sqlServer = require("mssql") const sqlServer = require("mssql")
const DEFAULT_SCHEMA = "dbo" const DEFAULT_SCHEMA = "dbo"
@ -31,41 +32,6 @@ interface MSSQLConfig {
encrypt?: boolean encrypt?: boolean
} }
interface TablesResponse {
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
TABLE_TYPE: string
}
type MSSQLColumn = {
IS_COMPUTED: number
IS_IDENTITY: number
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
COLUMN_NAME: string
ORDINAL_POSITION: number
COLUMN_DEFAULT: null | any
IS_NULLABLE: "NO" | "YES"
DATA_TYPE: string
CHARACTER_MAXIMUM_LENGTH: null | number
CHARACTER_OCTET_LENGTH: null | number
NUMERIC_PRECISION: null | string
NUMERIC_PRECISION_RADIX: null | string
NUMERIC_SCALE: null | string
DATETIME_PRECISION: null | string
CHARACTER_SET_CATALOG: null | string
CHARACTER_SET_SCHEMA: null | string
CHARACTER_SET_NAME: null | string
COLLATION_CATALOG: null | string
COLLATION_SCHEMA: null | string
COLLATION_NAME: null | string
DOMAIN_CATALOG: null | string
DOMAIN_SCHEMA: null | string
DOMAIN_NAME: null | string
}
const SCHEMA: Integration = { const SCHEMA: Integration = {
docs: "https://github.com/tediousjs/node-mssql", docs: "https://github.com/tediousjs/node-mssql",
plus: true, plus: true,
@ -238,7 +204,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
*/ */
async buildSchema(datasourceId: string, entities: Record<string, Table>) { async buildSchema(datasourceId: string, entities: Record<string, Table>) {
await this.connect() await this.connect()
let tableInfo: TablesResponse[] = await this.runSQL(this.TABLES_SQL) let tableInfo: MSSQLTablesResponse[] = await this.runSQL(this.TABLES_SQL)
if (tableInfo == null || !Array.isArray(tableInfo)) { if (tableInfo == null || !Array.isArray(tableInfo)) {
throw "Unable to get list of tables in database" throw "Unable to get list of tables in database"
} }

View File

@ -18,6 +18,7 @@ import {
import dayjs from "dayjs" import dayjs from "dayjs"
const { NUMBER_REGEX } = require("../utilities") const { NUMBER_REGEX } = require("../utilities")
import Sql from "./base/sql" import Sql from "./base/sql"
import { MySQLColumn } from "./base/types"
const mysql = require("mysql2/promise") const mysql = require("mysql2/promise")
@ -203,11 +204,11 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
try { try {
// get the tables first // get the tables first
const tablesResp = await this.internalQuery( const tablesResp: Record<string, string>[] = await this.internalQuery(
{ sql: "SHOW TABLES;" }, { sql: "SHOW TABLES;" },
{ connect: false } { connect: false }
) )
const tableNames = tablesResp.map( const tableNames: string[] = tablesResp.map(
(obj: any) => (obj: any) =>
obj[`Tables_in_${database}`] || obj[`Tables_in_${database}`] ||
obj[`Tables_in_${database.toLowerCase()}`] obj[`Tables_in_${database.toLowerCase()}`]
@ -215,7 +216,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
for (let tableName of tableNames) { for (let tableName of tableNames) {
const primaryKeys = [] const primaryKeys = []
const schema: TableSchema = {} const schema: TableSchema = {}
const descResp = await this.internalQuery( const descResp: MySQLColumn[] = await this.internalQuery(
{ sql: `DESCRIBE \`${tableName}\`;` }, { sql: `DESCRIBE \`${tableName}\`;` },
{ connect: false } { connect: false }
) )

View File

@ -24,6 +24,12 @@ import {
ExecuteOptions, ExecuteOptions,
Result, Result,
} from "oracledb" } from "oracledb"
import {
OracleTable,
OracleColumn,
OracleColumnsResponse,
OracleConstraint,
} from "./base/types"
let oracledb: any let oracledb: any
try { try {
oracledb = require("oracledb") oracledb = require("oracledb")
@ -89,50 +95,6 @@ const SCHEMA: Integration = {
const UNSUPPORTED_TYPES = ["BLOB", "CLOB", "NCLOB"] const UNSUPPORTED_TYPES = ["BLOB", "CLOB", "NCLOB"]
/**
* Raw query response
*/
interface ColumnsResponse {
TABLE_NAME: string
COLUMN_NAME: string
DATA_TYPE: string
DATA_DEFAULT: string | null
COLUMN_ID: number
CONSTRAINT_NAME: string | null
CONSTRAINT_TYPE: string | null
R_CONSTRAINT_NAME: string | null
SEARCH_CONDITION: string | null
}
/**
* An oracle constraint
*/
interface OracleConstraint {
name: string
type: string
relatedConstraintName: string | null
searchCondition: string | null
}
/**
* An oracle column and it's related constraints
*/
interface OracleColumn {
name: string
type: string
default: string | null
id: number
constraints: { [key: string]: OracleConstraint }
}
/**
* An oracle table and it's related columns
*/
interface OracleTable {
name: string
columns: { [key: string]: OracleColumn }
}
const OracleContraintTypes = { const OracleContraintTypes = {
PRIMARY: "P", PRIMARY: "P",
NOT_NULL_OR_CHECK: "C", NOT_NULL_OR_CHECK: "C",
@ -195,7 +157,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
/** /**
* Map the flat tabular columns and constraints data into a nested object * Map the flat tabular columns and constraints data into a nested object
*/ */
private mapColumns(result: Result<ColumnsResponse>): { private mapColumns(result: Result<OracleColumnsResponse>): {
[key: string]: OracleTable [key: string]: OracleTable
} { } {
const oracleTables: { [key: string]: OracleTable } = {} const oracleTables: { [key: string]: OracleTable } = {}
@ -299,7 +261,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
* @param entities - the tables that are to be built * @param entities - the tables that are to be built
*/ */
async buildSchema(datasourceId: string, entities: Record<string, Table>) { async buildSchema(datasourceId: string, entities: Record<string, Table>) {
const columnsResponse = await this.internalQuery<ColumnsResponse>({ const columnsResponse = await this.internalQuery<OracleColumnsResponse>({
sql: this.COLUMNS_SQL, sql: this.COLUMNS_SQL,
}) })
const oracleTables = this.mapColumns(columnsResponse) const oracleTables = this.mapColumns(columnsResponse)
@ -334,7 +296,9 @@ class OracleIntegration extends Sql implements DatasourcePlus {
fieldSchema = { fieldSchema = {
autocolumn: OracleIntegration.isAutoColumn(oracleColumn), autocolumn: OracleIntegration.isAutoColumn(oracleColumn),
name: columnName, name: columnName,
// TODO: add required constraint constraints: {
presence: false,
},
...this.internalConvertType(oracleColumn), ...this.internalConvertType(oracleColumn),
} }
table.schema[columnName] = fieldSchema table.schema[columnName] = fieldSchema
@ -344,6 +308,12 @@ class OracleIntegration extends Sql implements DatasourcePlus {
Object.values(oracleColumn.constraints).forEach(oracleConstraint => { Object.values(oracleColumn.constraints).forEach(oracleConstraint => {
if (oracleConstraint.type === OracleContraintTypes.PRIMARY) { if (oracleConstraint.type === OracleContraintTypes.PRIMARY) {
table.primary!.push(columnName) table.primary!.push(columnName)
} else if (
oracleConstraint.type === OracleContraintTypes.NOT_NULL_OR_CHECK
) {
table.schema[columnName].constraints = {
presence: true,
}
} }
}) })
}) })

View File

@ -15,9 +15,10 @@ import {
SqlClient, SqlClient,
} from "./utils" } from "./utils"
import Sql from "./base/sql" import Sql from "./base/sql"
import { PostgresColumn } from "./base/types"
import { escapeDangerousCharacters } from "../utilities"
const { Client, types } = require("pg") const { Client, types } = require("pg")
const { escapeDangerousCharacters } = require("../utilities")
// Return "date" and "timestamp" types as plain strings. // Return "date" and "timestamp" types as plain strings.
// This lets us reference the original stored timezone. // This lets us reference the original stored timezone.
@ -237,7 +238,8 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
} }
try { try {
const columnsResponse = await this.client.query(this.COLUMNS_SQL) const columnsResponse: { rows: PostgresColumn[] } =
await this.client.query(this.COLUMNS_SQL)
const tables: { [key: string]: Table } = {} const tables: { [key: string]: Table } = {}