@ -19,6 +19,7 @@ import { Table, TableSchema } from "../definitions/common"
module MSSQLModule {
const sqlServer = require ( "mssql" )
const Sql = require ( "./base/sql" )
const DEFAULT_SCHEMA = "dbo"
interface MSSQLConfig {
user : string
@ -26,9 +27,17 @@ module MSSQLModule {
server : string
port : number
database : string
schema : string
encrypt? : boolean
}
interface TablesResponse {
TABLE_CATALOG : string
TABLE_SCHEMA : string
TABLE_NAME : string
TABLE_TYPE : string
}
const SCHEMA : Integration = {
docs : "https://github.com/tediousjs/node-mssql" ,
plus : true ,
@ -58,6 +67,10 @@ module MSSQLModule {
type : DatasourceFieldTypes . STRING ,
default : "root" ,
} ,
schema : {
type : DatasourceFieldTypes . STRING ,
default : DEFAULT_SCHEMA ,
} ,
encrypt : {
type : DatasourceFieldTypes . BOOLEAN ,
default : true ,
@ -79,34 +92,9 @@ module MSSQLModule {
} ,
}
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 ) {
request . input ( ` p ${ count ++ } ` , binding )
}
}
// 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 )
}
}
class SqlServerIntegration extends Sql implements DatasourcePlus {
private readonly config : MSSQLConfig
private index : number = 0
static pool : any
public tables : Record < string , Table > = { }
public schemaErrors : Record < string , string > = { }
@ -121,6 +109,62 @@ module MSSQLModule {
TABLES_SQL =
"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'"
constructor ( config : MSSQLConfig ) {
super ( SqlClients . MS_SQL )
this . config = config
const clientCfg = {
. . . this . config ,
options : {
encrypt : this.config.encrypt ,
enableArithAbort : true ,
} ,
}
delete clientCfg . encrypt
if ( ! this . pool ) {
this . pool = new sqlServer . ConnectionPool ( clientCfg )
}
}
getBindingIdentifier ( ) : string {
return ` (@p ${ this . index ++ } ) `
}
async connect() {
try {
this . client = await this . pool . connect ( )
} catch ( err ) {
// @ts-ignore
throw new Error ( err )
}
}
async internalQuery (
query : SqlQuery ,
operation : string | undefined = undefined
) {
const client = this . client
const request = client . request ( )
this . index = 0
try {
if ( Array . isArray ( query . bindings ) ) {
let count = 0
for ( let binding of query . bindings ) {
request . input ( ` p ${ count ++ } ` , binding )
}
}
// 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 )
}
}
getDefinitionSQL ( tableName : string ) {
return ` select *
from INFORMATION_SCHEMA . COLUMNS
@ -149,33 +193,8 @@ module MSSQLModule {
WHERE TABLE_NAME = '${tableName}' `
}
constructor ( config : MSSQLConfig ) {
super ( SqlClients . MS_SQL )
this . config = config
const clientCfg = {
. . . this . config ,
options : {
encrypt : this.config.encrypt ,
enableArithAbort : true ,
} ,
}
delete clientCfg . encrypt
if ( ! this . pool ) {
this . pool = new sqlServer . ConnectionPool ( clientCfg )
}
}
async connect() {
try {
this . client = await this . pool . connect ( )
} catch ( err ) {
// @ts-ignore
throw new Error ( err )
}
}
async runSQL ( sql : string ) {
return ( await internalQuery ( this . client , getSqlQuery ( sql ) ) ) . recordset
return ( await this . internalQuery ( getSqlQuery ( sql ) ) ) . recordset
}
/ * *
@ -185,11 +204,14 @@ module MSSQLModule {
* /
async buildSchema ( datasourceId : string , entities : Record < string , Table > ) {
await this . connect ( )
let tableNames = await this . runSQL ( this . TABLES_SQL )
if ( tableNames == null || ! Array . isArray ( tableNames ) ) {
let tableInfo : TablesResponse [ ] = await this . runSQL ( this . TABLES_SQL )
if ( tableInfo == null || ! Array . isArray ( tableInfo ) ) {
throw "Unable to get list of tables in database"
}
tableNames = tableNames
const schema = this . config . schema || DEFAULT_SCHEMA
const tableNames = tableInfo
. filter ( ( record : any ) = > record . TABLE_SCHEMA === schema )
. map ( ( record : any ) = > record . TABLE_NAME )
. filter ( ( name : string ) = > this . MASTER_TABLES . indexOf ( name ) === - 1 )
@ -238,33 +260,36 @@ module MSSQLModule {
async read ( query : SqlQuery | string ) {
await this . connect ( )
const response = await internalQuery ( this . client , getSqlQuery ( query ) )
const response = await this . internalQuery ( getSqlQuery ( query ) )
return response . recordset
}
async create ( query : SqlQuery | string ) {
await this . connect ( )
const response = await internalQuery ( this . client , getSqlQuery ( query ) )
const response = await this . internalQuery ( getSqlQuery ( query ) )
return response . recordset || [ { created : true } ]
}
async update ( query : SqlQuery | string ) {
await this . connect ( )
const response = await internalQuery ( this . client , getSqlQuery ( query ) )
const response = await this . internalQuery ( getSqlQuery ( query ) )
return response . recordset || [ { updated : true } ]
}
async delete ( query : SqlQuery | string ) {
await this . connect ( )
const response = await internalQuery ( this . client , getSqlQuery ( query ) )
const response = await this . internalQuery ( getSqlQuery ( query ) )
return response . recordset || [ { deleted : true } ]
}
async query ( json : QueryJson ) {
const schema = this . config . schema
await this . connect ( )
if ( schema && schema !== DEFAULT_SCHEMA && json ? . endpoint ) {
json . endpoint . schema = schema
}
const operation = this . _operation ( json )
const queryFn = ( query : any , op : string ) = >
internalQuery ( this . client , query , op )
const queryFn = ( query : any , op : string ) = > this . internalQuery ( query , op )
const processFn = ( result : any ) = >
result . recordset ? result . recordset : [ { [ operation ] : true } ]
return this . queryWithReturning ( json , queryFn , processFn )