"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const datasource_1 = require("../definitions/datasource");
const utils_1 = require("./utils");
var MSSQLModule;
(function (MSSQLModule) {
    const sqlServer = require("mssql");
    const Sql = require("./base/sql");
    const DEFAULT_SCHEMA = "dbo";
    const SCHEMA = {
        docs: "https://github.com/tediousjs/node-mssql",
        plus: true,
        description: "Microsoft SQL Server is a relational database management system developed by Microsoft. ",
        friendlyName: "MS SQL Server",
        type: "Relational",
        datasource: {
            user: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                required: true,
                default: "localhost",
            },
            password: {
                type: datasource_1.DatasourceFieldTypes.PASSWORD,
                required: true,
            },
            server: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                default: "localhost",
            },
            port: {
                type: datasource_1.DatasourceFieldTypes.NUMBER,
                required: false,
                default: 1433,
            },
            database: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                default: "root",
            },
            schema: {
                type: datasource_1.DatasourceFieldTypes.STRING,
                default: DEFAULT_SCHEMA,
            },
            encrypt: {
                type: datasource_1.DatasourceFieldTypes.BOOLEAN,
                default: true,
            },
        },
        query: {
            create: {
                type: datasource_1.QueryTypes.SQL,
            },
            read: {
                type: datasource_1.QueryTypes.SQL,
            },
            update: {
                type: datasource_1.QueryTypes.SQL,
            },
            delete: {
                type: datasource_1.QueryTypes.SQL,
            },
        },
    };
    class SqlServerIntegration extends Sql {
        constructor(config) {
            super(utils_1.SqlClients.MS_SQL);
            this.index = 0;
            this.tables = {};
            this.schemaErrors = {};
            this.MASTER_TABLES = [
                "spt_fallback_db",
                "spt_fallback_dev",
                "spt_fallback_usg",
                "spt_monitor",
                "MSreplication_options",
            ];
            this.TABLES_SQL = "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'";
            this.config = config;
            const clientCfg = Object.assign(Object.assign({}, this.config), { options: {
                    encrypt: this.config.encrypt,
                    enableArithAbort: true,
                } });
            delete clientCfg.encrypt;
            if (!this.pool) {
                this.pool = new sqlServer.ConnectionPool(clientCfg);
            }
        }
        getBindingIdentifier() {
            return `@p${this.index++}`;
        }
        getStringConcat(parts) {
            return `concat(${parts.join(", ")})`;
        }
        connect() {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    this.client = yield this.pool.connect();
                }
                catch (err) {
                    // @ts-ignore
                    throw new Error(err);
                }
            });
        }
        internalQuery(query, operation = undefined) {
            return __awaiter(this, void 0, void 0, function* () {
                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 === datasource_1.Operation.CREATE
                        ? `${query.sql}; SELECT SCOPE_IDENTITY() AS id;`
                        : query.sql;
                    return yield request.query(sql);
                }
                catch (err) {
                    // @ts-ignore
                    throw new Error(err);
                }
            });
        }
        getDefinitionSQL(tableName) {
            return `select *
              from INFORMATION_SCHEMA.COLUMNS
              where TABLE_NAME='${tableName}'`;
        }
        getConstraintsSQL(tableName) {
            return `SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC 
              INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KU
                ON TC.CONSTRAINT_TYPE = 'PRIMARY KEY' 
                AND TC.CONSTRAINT_NAME = KU.CONSTRAINT_NAME 
                AND KU.table_name='${tableName}'
              ORDER BY 
                KU.TABLE_NAME,
                KU.ORDINAL_POSITION;`;
        }
        getAutoColumnsSQL(tableName) {
            return `SELECT 
              COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA+'.'+TABLE_NAME),COLUMN_NAME,'IsComputed') 
                AS IS_COMPUTED,
              COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity')
                AS IS_IDENTITY,
              *
              FROM INFORMATION_SCHEMA.COLUMNS
              WHERE TABLE_NAME='${tableName}'`;
        }
        runSQL(sql) {
            return __awaiter(this, void 0, void 0, function* () {
                return (yield this.internalQuery((0, utils_1.getSqlQuery)(sql))).recordset;
            });
        }
        /**
         * Fetches the tables from the sql server database and assigns them to the datasource.
         * @param {*} datasourceId - datasourceId to fetch
         * @param entities - the tables that are to be built
         */
        buildSchema(datasourceId, entities) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                let tableInfo = yield this.runSQL(this.TABLES_SQL);
                if (tableInfo == null || !Array.isArray(tableInfo)) {
                    throw "Unable to get list of tables in database";
                }
                const schema = this.config.schema || DEFAULT_SCHEMA;
                const tableNames = tableInfo
                    .filter((record) => record.TABLE_SCHEMA === schema)
                    .map((record) => record.TABLE_NAME)
                    .filter((name) => this.MASTER_TABLES.indexOf(name) === -1);
                const tables = {};
                for (let tableName of tableNames) {
                    // get the column definition (type)
                    const definition = yield this.runSQL(this.getDefinitionSQL(tableName));
                    // find primary key constraints
                    const constraints = yield this.runSQL(this.getConstraintsSQL(tableName));
                    // find the computed and identity columns (auto columns)
                    const columns = yield this.runSQL(this.getAutoColumnsSQL(tableName));
                    const primaryKeys = constraints
                        .filter((constraint) => constraint.CONSTRAINT_TYPE === "PRIMARY KEY")
                        .map((constraint) => constraint.COLUMN_NAME);
                    const autoColumns = columns
                        .filter((col) => col.IS_COMPUTED || col.IS_IDENTITY)
                        .map((col) => col.COLUMN_NAME);
                    let schema = {};
                    for (let def of definition) {
                        const name = def.COLUMN_NAME;
                        if (typeof name !== "string") {
                            continue;
                        }
                        schema[name] = Object.assign(Object.assign({ autocolumn: !!autoColumns.find((col) => col === name), name: name }, (0, utils_1.convertSqlType)(def.DATA_TYPE)), { externalType: def.DATA_TYPE });
                    }
                    tables[tableName] = {
                        _id: (0, utils_1.buildExternalTableId)(datasourceId, tableName),
                        primary: primaryKeys,
                        name: tableName,
                        schema,
                    };
                }
                const final = (0, utils_1.finaliseExternalTables)(tables, entities);
                this.tables = final.tables;
                this.schemaErrors = final.errors;
            });
        }
        read(query) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                const response = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return response.recordset;
            });
        }
        create(query) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                const response = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return response.recordset || [{ created: true }];
            });
        }
        update(query) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                const response = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return response.recordset || [{ updated: true }];
            });
        }
        delete(query) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                const response = yield this.internalQuery((0, utils_1.getSqlQuery)(query));
                return response.recordset || [{ deleted: true }];
            });
        }
        query(json) {
            return __awaiter(this, void 0, void 0, function* () {
                const schema = this.config.schema;
                yield this.connect();
                if (schema && schema !== DEFAULT_SCHEMA && (json === null || json === void 0 ? void 0 : json.endpoint)) {
                    json.endpoint.schema = schema;
                }
                const operation = this._operation(json);
                const queryFn = (query, op) => this.internalQuery(query, op);
                const processFn = (result) => result.recordset ? result.recordset : [{ [operation]: true }];
                return this.queryWithReturning(json, queryFn, processFn);
            });
        }
    }
    module.exports = {
        schema: SCHEMA,
        integration: SqlServerIntegration,
    };
})(MSSQLModule || (MSSQLModule = {}));
