diff --git a/packages/server/scripts/integrations/mssql/data/setup.sql b/packages/server/scripts/integrations/mssql/data/setup.sql index b6ab4f5274..5d2eeb8bb9 100644 --- a/packages/server/scripts/integrations/mssql/data/setup.sql +++ b/packages/server/scripts/integrations/mssql/data/setup.sql @@ -1,5 +1,10 @@ USE master; - + +IF NOT EXISTS(SELECT 1 FROM sys.schemas WHERE name = 'Chains') +BEGIN + EXEC sys.sp_executesql N'CREATE SCHEMA Chains;' +END + IF OBJECT_ID ('dbo.products', 'U') IS NOT NULL DROP TABLE products; GO @@ -61,3 +66,15 @@ VALUES ('Bob', '30'), ('Bobert', '99'), ('Jan', '22'), ('Megan', '11'); + + +IF OBJECT_ID ('Chains.sizes', 'U') IS NOT NULL + DROP TABLE Chains.sizes; +GO +CREATE TABLE Chains.sizes +( + sizeid int IDENTITY(1, 1), + name varchar(30), + CONSTRAINT pk_size PRIMARY KEY NONCLUSTERED (sizeid) +); + diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index efac92334e..6c8c8dc07d 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -153,6 +153,7 @@ export interface QueryJson { datasourceId: string entityId: string operation: Operation + schema?: string } resource: { fields: string[] diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index ce06624107..ffa405f016 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -249,6 +249,9 @@ class InternalBuilder { create(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery { const { endpoint, body } = json let query: KnexQuery = knex(endpoint.entityId) + if (endpoint.schema) { + query = query.withSchema(endpoint.schema) + } const parsedBody = parseBody(body) // make sure no null values in body for creation for (let [key, value] of Object.entries(parsedBody)) { @@ -267,6 +270,9 @@ class InternalBuilder { bulkCreate(knex: Knex, json: QueryJson): KnexQuery { const { endpoint, body } = json let query: KnexQuery = knex(endpoint.entityId) + if (endpoint.schema) { + query = query.withSchema(endpoint.schema) + } if (!Array.isArray(body)) { return query } @@ -275,7 +281,7 @@ class InternalBuilder { } read(knex: Knex, json: QueryJson, limit: number): KnexQuery { - let { endpoint, resource, filters, sort, paginate, relationships } = json + let { endpoint, resource, filters, paginate, relationships } = json const tableName = endpoint.entityId // select all if not specified if (!resource) { @@ -302,6 +308,9 @@ class InternalBuilder { } // start building the query let query: KnexQuery = knex(tableName).limit(foundLimit) + if (endpoint.schema) { + query = query.withSchema(endpoint.schema) + } if (foundOffset) { query = query.offset(foundOffset) } @@ -331,6 +340,9 @@ class InternalBuilder { update(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery { const { endpoint, body, filters } = json let query: KnexQuery = knex(endpoint.entityId) + if (endpoint.schema) { + query = query.withSchema(endpoint.schema) + } const parsedBody = parseBody(body) query = this.addFilters(query, filters, { tableName: endpoint.entityId }) // mysql can't use returning @@ -344,6 +356,9 @@ class InternalBuilder { delete(knex: Knex, json: QueryJson, opts: QueryOptions): KnexQuery { const { endpoint, filters } = json let query: KnexQuery = knex(endpoint.entityId) + if (endpoint.schema) { + query = query.withSchema(endpoint.schema) + } query = this.addFilters(query, filters, { tableName: endpoint.entityId }) // mysql can't use returning if (opts.disableReturning) { diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index d6331ef25a..c557b0e796 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -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, @@ -96,11 +109,41 @@ 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 schema = this.config.schema const request = client.request() this.index = 0 try { @@ -151,35 +194,6 @@ 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) - } - } - - getBindingIdentifier(): string { - return `(@p${this.index++})` - } - - async connect() { - try { - this.client = await this.pool.connect() - } catch (err) { - // @ts-ignore - throw new Error(err) - } - } - async runSQL(sql: string) { return (await this.internalQuery(getSqlQuery(sql))).recordset } @@ -191,11 +205,14 @@ module MSSQLModule { */ async buildSchema(datasourceId: string, entities: Record) { 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) @@ -267,7 +284,11 @@ module MSSQLModule { } 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) => this.internalQuery(query, op) const processFn = (result: any) =>