Light scripting in BroadSQL: the JS command family

September 4, 2026

#scripting #javascript #automation #multidatabase #devtools

A new lightweight JavaScript layer lets a BroadSQL script hold two or more database connections open at once and drive them with ordinary loops and conditionals, something the interactive shell cannot do at all.

The problem: one connection at a time

BroadSQL's interactive shell holds exactly one live connection at a time: CONNECT always closes whatever was open before opening the new one. That keeps day-to-day work simple, but it makes an entire category of task structurally impossible without leaving BroadSQL: anything that needs two databases open at once (comparing, migrating, reconciling), or a loop with conditional logic driving several statements. Until now, the only options were typing every statement by hand, one at a time, or writing a full compiled Java extension for what is really a five-line automation.

The BroadSQL approach: a script that can hold several connections at once

The new JS command family runs a plain JavaScript file, or a one-line snippet typed directly into the shell, against a small set of BroadSQL-provided functions: connect(name) to open one or more independent database connections, db to reuse whichever connection is already active interactively, .execute(sql)/.executeUpdate(sql) to run queries, and print/println for output. It runs on standalone Nashorn (org.openjdk.nashorn:nashorn-core), the JavaScript engine that used to ship inside the JDK, wired in through the standard Java scripting API. No compilation, no plugin JAR to build or drop into a folder, no restart: save a text file, type one command, see the result.

Two commands cover how you run one: JS RUN <file> [args...] runs a saved .js file, resolved the same way SCRIPT RUN resolves a .sql file today (though, unlike SQL scripts, the .js extension is not optional to type). Any extra tokens after the file name are passed into the script as args[0], args[1], and so on. JS EVAL <code> is the no-file equivalent, for a snippet you want to try immediately.

A first script

The smallest useful script is a handful of lines:

-- @description: Prints today's row count from the CUSTOMER table

if (db === null) {
    println("No active connection. Connect first, then run this script.");
} else {
    var rows = db.execute("SELECT COUNT(*) AS CNT FROM CUSTOMER");
    println("CUSTOMER table currently has " + rows.get(0)["CNT"] + " rows.");
}

Connect to a database as you normally would, then run it: JS RUN hello_db.js;. On its own, a script this small doesn't buy you much over typing the equivalent SELECT directly. Its point is the mechanics: the file, the JS RUN command, the db binding, println. Once those are familiar, the step from here to a script that opens two connections, loops over rows, and branches on a condition is small, and that's exactly where the real value shows up.

Two connections at once

This is the example that demonstrates the actual gap described above: two live database connections open at the same time.

-- migrate_active_customers.js
-- Copies every CLOSED order with a positive amount from PROD_ORDERS into ARCHIVE_DB.

var source = connect('PROD_ORDERS');
var target = connect('ARCHIVE_DB');

var rows = source.execute(
    "SELECT id, customer, amount, status FROM orders WHERE status = 'CLOSED'"
);

var migrated = 0;
for (var i = 0; i < rows.size(); i++) {
    var row = rows.get(i);
    if (row["amount"] > 0) {
        target.executeUpdate(
            "INSERT INTO orders_archive (id, customer, amount, status) VALUES (" +
            row["id"] + ", '" + row["customer"] + "', " + row["amount"] + ", '" + row["status"] + "')"
        );
        migrated++;
    }
}

println("Migrated " + migrated + " of " + rows.size() + " closed orders.");

source.close();
target.close();

Run it with JS RUN migrate_active_customers.js;, or pass the two connection names as arguments instead of hard-coding them (JS RUN migrate_active_customers.js PROD_ORDERS ARCHIVE_DB;), reading them inside the script as args[0]/args[1], so the same file works against any pair of environments.

Walking through it: connect('PROD_ORDERS') and connect('ARCHIVE_DB') each open a separate, independent connection, looked up from the same connection registry CONNECT/SHOW ALL CONNECTIONS already use. Neither touches the other, and neither touches whatever is connected interactively, if anything. source.execute(...) runs the SELECT and reads every matching row into memory up front, so rows becomes an ordinary collection you can index with rows.get(i) and read by column name with row["amount"]. The for loop is the part no single SQL statement, and no sequence of statements typed one at a time at an interactive prompt, could do across two databases at once: for every row pulled from the source, it checks a condition in ordinary JavaScript, and if it holds, builds and runs an INSERT against the target connection, a database entirely separate from where the row came from. If either connection fails to open, or a statement hits a SQL error, the script stops with an error message and BroadSQL's prompt is ready for the next command right after, exactly as if any other command had failed: a script never crashes the shell.

Honest scope

There is no sandbox: a script runs at the same trust level as an extension JAR or a .bat file you already run, with full access to the JVM through JavaScript's Java interop. This is not a safe way to run a script you did not write yourself. CTRL+C cannot interrupt a script stuck in a pure JavaScript loop that never calls into a database, though it still cancels a script blocked on a slow query, same as any other command. And execute(sql) reads a whole result set into memory before returning it: fine for ordinary sized results, not a fit for a query that returns millions of rows. None of this is accidental; each limit is a deliberate first-version boundary, not an oversight.

Who this is for

Anyone already comfortable in BroadSQL who has hit the "I need two databases open at once" wall: data stewards migrating or reconciling data between environments, and ops engineers automating a multi-step check that a single SQL statement can't express. It sits below a full custom command JAR (still the right tool for a command shipped to every user of that JAR) and above hand-typing statements one at a time.

Get your automation scripted properly

I write and review JS scripts for teams moving a recurring, multi-database job (a migration, a reconciliation, a sync check) out of manual steps and into a version-controlled file that runs the same way every time. Get in touch to talk about the job you're still doing by hand.