Zoeken op website

node.js - Azure Devops Build-taak die SQL-script uitvoert, stopt na een aantal sp_rename-aanroepen


Ik wil een Azure Devops-buildtaak bouwen die een lijst met SQL-scripts uitvoert tegen een SQL Server 2017-database. Ik heb deze handleiding gevolgd om de taak te bouwen: https://learn.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=azure-devops

De taak loopt over het algemeen succesvol en ik heb al verschillende scripts uitgevoerd op mijn lokale database (in SQL Server 2017 Express). Ik gebruik het npm-pakket "mssql/msnodesqlv8" (native SQL Server-driver voor mssql) om verbinding te maken en de scripts direct in node.js uit te voeren.

async function executeBatches(script: string, pool: sql.ConnectionPool) {
    const batches = script.split("\r\nGO");
    for (const batch of batches) {
        console.log("Executing script:", batch);
        await pool.batch(batch);
    }
}

Nu heb ik een script gevonden dat op een heel vreemde manier faalt. Het betreffende script voert een aantal hernoemingen uit over 4 tabellen in een transactie zoals deze:

BEGIN TRANSACTION
EXEC sp_rename 'table' , 'newTable'
EXEC sp_rename 'newTable.column', 'newColumn', 'COLUMN'
(repeat for several columns)
EXEC sp_rename 'dbo.PK_table', 'PK_newTable'

(repeat for 3 more tables)
COMMIT

In SQL Server Management Studio wordt het script correct uitgevoerd. Maar in de Devops-taak stopt dit script na ongeveer 18 sp_rename-aanroepen. Er wordt geen foutmelding weergegeven en de transactie blijft open. De client blijft doorgaan (omdat er geen fout is ontvangen) en na het uitvoeren van nog enkele queries start SQL Server een rollback en zal natuurlijk alle wijzigingen sinds het uitvoeren van dit script terugdraaien.

Ik heb de statements in het script omgewisseld en geprobeerd enkele regels uit te commentariëren, maar het stopt altijd na ongeveer 18 sp_rename-aanroepen. Wanneer ik genoeg regels verwijder zodat er 18 of minder sp_rename-aanroepen overblijven, kan de taak het script volledig uitvoeren en de wijzigingen doorvoeren (het maakt niet uit welke regels).

Wanneer ik de transactie verwijder, zal het alle hernoemingen uitvoeren tot dat magische nummer en vervolgens het script nog steeds afbreken en de impliciete transactie van de laatste verklaring open laten, zodat het nog steeds alle wijzigingen terugdraait na nog enkele queries.

Ik heb de SQL Profiler uitgevoerd en het toont StmtStarting voor een hernoeming en daarna BatchCompleted met de fout "2 - Abort", maar er is geen andere fout of reden aangegeven waarom de batch is afgebroken.

De system_health-sessie toont 2 fouten wanneer het script wordt uitgevoerd:

Fout #1

Een verbindingsfout met tds_flags "DisconnectDueToReadError, NetworkErrorFoundInInputStream, NormalDisconnect" en tds_input_buffer_error 109.

Fout #2

Een beveiligingsfout met foutcode 5023 (wat betekent "De groep of bron bevindt zich niet in de juiste staat om de gevraagde bewerking uit te voeren.")

Het online zoeken naar deze fouten gaf geen bruikbare resultaten, aangezien ze ofwel geen oplossing hebben of gerelateerd zijn aan inlogproblemen, waarvan ik geloof dat dit niet het geval is aangezien ik andere scripts prima kan uitvoeren.

Ik heb ook al de codering gecontroleerd en dat het script correct wordt gelezen via de 'fs'-bibliotheek van Node.

Alle hulp of aanwijzingen over waar ik een oorzaak voor dit probleem zou kunnen vinden, zou zeer op prijs worden gesteld.

BEWERKEN: Ik ben toegekomen aan het maken van een kleiner voorbeeld met alleen msnodesqlv8.

import tl = require("azure-pipelines-task-lib/task");
import fs = require("fs");
import path = require("path");
import util = require("util");

import { SqlClient } from "msnodesqlv8";
// tslint:disable-next-line: no-var-requires
const sqlClient: SqlClient = require("msnodesqlv8");
const open = util.promisify(sqlClient.open);
const query = util.promisify(sqlClient.query);

async function run() {
    try {
        const scriptDirectory = tl.getInput("ScriptDirectory", true) ?? "";
        const connectionString = "Driver={ODBC Driver 13 for SQL Server};Server={.\\SQLEXPRESS};Uid={sa};Pwd={start};Database={MyDatabase};Encrypt={yes};TrustServerCertificate={yes}";
        const scriptsDir = fs.readdirSync(scriptDirectory);
        const con = await open(connectionString);
        const close = util.promisify(con.close);
        try {
            const conQuery = util.promisify(con.query);
            for (const file of scriptsDir) {
                console.log("Executing:", file);
                const script = readFileWithoutBom(path.join(scriptDirectory, file));
                console.log("Executing script:", script);
                // await query(connectionString, { query_str: script, query_timeout: 120 });
                await conQuery({ query_str: script, query_timeout: 120 });

                const insert = `INSERT INTO AppliedDatabaseScript (ScriptFile, DateApplied) VALUES ('${file}', GETDATE())`
                console.log("Executing script:", insert);
                // await query(connectionString, { query_str: insert });
                await conQuery({ query_str: insert });
            }
        } finally {
            await close();
        }
    }
    catch (err) {
        console.error(err);
        tl.setResult(tl.TaskResult.Failed, err.message);
    }
}

// Strip BOM. SQL Server won't execute certain scripts with BOM.
function readFileWithoutBom(filePath: string) {
   return fs.readFileSync(filePath, "utf-8").replace(/^\uFEFF/, "");
}

Het gedrag is nog steeds hetzelfde. Ik heb geprobeerd met een gewone verbinding en een aparte verbinding voor elke query. Het zal alles in dezelfde verbinding terugdraaien en doorgaan alsof er geen fout is opgetreden. Ik heb ook gespeeld met de query-timeout, maar er is helemaal geen verband met de fout.

Antwoord #1

Het is me gelukt om het werkend te krijgen door over te schakelen naar de niet-native driver tedious (ook ondersteund door mssql). Het lijkt erop dat de native SQL Server-driver voor node.js niet correct werkt.

Voor het geval iemand problemen heeft met het verbinden met tedious (aangezien ik geen goede documentatie online kon vinden), zijn dit de basisconfiguratie-opties:

{
    user: "username",
    password: "password",
    server: "hostname\\instancename",
    database: "database",
    port: 1433
}

U moet ervoor zorgen dat de SQL Server Browser wordt uitgevoerd en dat de instantie is geconfigureerd voor externe toegang (zelfs bij verbinding via localhost) door TCP/IP te activeren in de SQL Server Configuration Manager.