OriginChainDB docs
examples · tds · 3 / 5

3. Prepared statements and batches

← SQL Server wire examples
shared-credential listeners only

The write on this page runs only against a listener on the shared credential model. A per-user TDS session is read-only and refuses every write, so if your tenant has any database RBAC grant or any row-level security policy — the condition that forces per-user login — this will not run against it. Check which model you are on under credential models first, and write through the HTTP API or the PostgreSQL wire if you are not.

what this does

Prepares one statement, executes it three times with different values, frees it, and then runs a batch insert inside a transaction. Nothing here is OriginChainDB-specific — it is ordinary JDBC — which is the point.

the code
// One PreparedStatement, executed many times, then a batch insert.
// The driver turns this into the RPC procedure family on its own -
// you never name those procedures yourself.
try (Connection conn = DriverManager.getConnection(url, user, password)) {

    try (PreparedStatement ps = conn.prepareStatement(
            "SELECT id, customer_ref FROM invoices"
          + " WHERE status = ? AND amount_cents > ?")) {

        for (String status : List.of("open", "settled", "void")) {
            ps.setString(1, status);
            ps.setInt(2, 50000);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    System.out.println(status + " " + rs.getInt("id"));
                }
            }
        }
    }   // close() frees the server-side handle

    conn.setAutoCommit(false);
    try (PreparedStatement ins = conn.prepareStatement(
            "INSERT INTO invoices (id, customer_ref, amount_cents, status)"
          + " VALUES (?, ?, ?, ?)")) {
        for (Object[] row : rows) {
            ins.setInt(1, (Integer) row[0]);
            ins.setString(2, (String) row[1]);
            ins.setInt(3, (Integer) row[2]);
            ins.setString(4, (String) row[3]);
            ins.addBatch();
        }
        int[] counts = ins.executeBatch();   // every statement runs
        System.out.println("inserted " + counts.length + " rows");
    }
    conn.commit();
}
what goes over the wire

A SQL Server driver does not send that statement the same way twice. It switches to a prepared handle, and which call it uses depends on the driver and on how many times you have executed:

execution 1   sp_executesql   statement text + parameter declarations
execution 2   sp_prepexec     prepare and execute in one round trip
                              -> returns a handle
execution 3   sp_execute      the handle, with fresh values
close         sp_unprepare    free the handle

All of it is served. That matters more than it sounds: a listener that answered only the first form would make "the driver works" true for exactly one execution of any prepared statement and false from the second onward — and false from the first for ODBC clients, which reach for the handle immediately. The interop battery asserts the whole family through the driver's own handle, not through our encoder.

what this is not

Those procedure names belong to the protocol, not to your schema. Serving them is what makes a prepared statement work; it is not stored-procedure support. Calling a procedure you wrote is refused, as is every other procedure by name or by numeric id.

-- Calling a procedure YOU wrote is a different thing, and is refused.
EXEC dbo.settle_invoice @id = 4101;
-- error: that procedure surface does not exist on this engine

OriginChainDB's own procedures are written in a PL/pgSQL-shaped subset and called over the SQL endpoint or the PostgreSQL wire. The TDS reference shows the same procedure written both ways.

notes
  • Handles are per connection. They are freed on every teardown path, they are never recycled after a close, and one connection can never see another's.
  • executeBatch runs every statement in the batch. Earlier builds parsed only the first request in a message and dropped the rest, reporting success for statements that never ran; that is fixed and pinned.
  • OUTPUT parameters are refused, because there is no variable-assignment surface that could produce one. Return values through a result set instead.
  • Integer, floating point, bit, string, numeric and unique-identifier parameters decode exactly. Date and time parameters are best-effort in this preview — their epoch base differs from the engine's stored form.

Microsoft, SQL Server and T-SQL are trademarks of the Microsoft group of companies. Named here only to describe protocol and dialect compatibility. OriginChainDB is not affiliated with, endorsed by, or sponsored by Microsoft, and does not distribute Microsoft software.