MySQL

Execute

17 samples, from the least code that works to complete workflows.

What an Execute does

The call new ArxProcedureExpression("sp_place_order"), in the pattern of ArxQueryExpression: AddParameter, AddOutParameter<T>, AddInOutParameter<T>; ProcedureType = ArxProcedureType.Function for a function. Never SQL text. AddCursorParameter, AddVariadicParameter and ReturnsCursors are ARX8001: MySQL has no cursor parameters and no VARIADIC
Procedure or function The caller says which: a procedure runs as CALL sp(…), a function as SELECT fn(…). MySQL functions return one value, never rows: ordering or paging a function's result is ARX8001. Called as the wrong kind, MySQL finds no routine by that name (1305): ARX5001, and the message says which ProcedureType to set
Arguments A procedure's by name or by position (positional first; a call with OUT or INOUT parameters names every argument, since MySQL's CALL has no named arguments and ARX passes them in the procedure's own order); a function's by position only. MySQL procedures have no default values, so every parameter is given (null for no value). By name, ARX checks each name against the procedure's own (information_schema.PARAMETERS, read once per procedure) and refuses one it doesn't have, rather than let it be ignored. Each value is a typed parameter through the same value path as Create (dates, JSON, selection sets, lookups)
What comes back ArxExecuteResult: Outputs (OUT and INOUT values as their declared types), ResultSets (each SELECT the procedure runs, by position), Messages (the notes and warnings the call left, read with SHOW WARNINGS in the same round trip). Value<T>() reads a function's value. MySQL procedures have no RETURN value
OUT and INOUT An OUT starts as NULL inside the procedure; an INOUT carries the value given. Each is declared with its .NET type and read after the call's last result set
Tables from a procedure Every SELECT the procedure runs is a result set, read by position (ResultSets[0], [1] …) in one round trip. OUT values arrive after the last one
Paging PageInfo, as on a query, two ways: the procedure pages itself (PagingParameters: ARX fills its page arguments and reads its total from an OUT parameter), or ARX pages a procedure's result set (on the client: earlier rows read and skipped, the rest counted; every row crosses the network). The total is opt-in
Transactions ARX's own call runs outside any transaction, so a procedure may begin and commit its own. MySQL also lets a procedure COMMIT (or run DDL) inside the caller's transaction, and that commits everything the transaction did before it, with no error ARX could see: call such procedures outside ARX transactions and batch scopes (RealCase5)
Errors the procedure raises SIGNAL SQLSTATE '45000' is ARX3005 RaisedByDatabase, with the procedure's message and its error number as NativeCode (MYSQL_ERRNO = 50101 gives 50101; 1644 when it sets none), so the application can branch on its own numbers. A SIGNAL of class 01 is a warning: it comes back in Messages and the procedure goes on
Security A procedure, function or parameter name carrying SQL is ARX1017, refused before the database; values are parameters
Names in the samples Procedures sp_, functions fn_ (views vw_)
Without a catalog Needs no catalog and reads nothing first: a service created with only the connection string calls procedures and functions as any other

Reference

Error codes an Execute can raise

Code When
ARX1001 InvalidArgument A positional argument after a named one; OUT or INOUT parameters on a function; an OUT without a type; AddOrder on a procedure; PageInfo with nothing to page or below 1; PagingParameters without PageInfo; PagedResultSet naming no result set
ARX1002, ARX1003, ARX1004 No routine name; an invalid name (three parts, a typo); a parameter given twice
ARX1007, ARX1013 A value type ARX can't write; default(DateTime)
ARX1008 InvalidConnection A connection with an open transaction, given without that transaction
ARX1017 SuspectedInjection A procedure, function, parameter or paging name carrying SQL
ARX2003 MultipleRecordsMatched Value<T>() on several rows
ARX3001–3004 A constraint the procedure broke (kept from the database)
ARX3005 RaisedByDatabase The procedure refused: SIGNAL SQLSTATE '45000' (1644, or the MYSQL_ERRNO it sets)
ARX4003 InvalidValueFormat An argument that doesn't convert to the parameter's type ('abc' for an INT)
ARX4004 DataTypeMismatch The wrong number of arguments (1318), a parameter left out, a named argument the procedure doesn't have
ARX5001 UndefinedEntity No procedure or function by that name (1305), or one called as the other kind
ARX5003 SchemaMismatch An OUT value of another type than declared, that can't be widened
ARX6001 PermissionDenied No EXECUTE permission
ARX7003 Timeout CommandTimeout exceeded (MySqlConnector stops the statement with KILL QUERY)
ARX8001 NotSupported Cursor parameters, VARIADIC, ReturnsCursors; ordering or paging a function's result (MySQL functions return one value); a function as a Retrieve row source

The samples

Basics

The least code that works.

1Call a stored procedureProgressive

Calling a stored procedure, in three scenarios: named arguments; positional arguments; a parameter left out, which MySQL refuses (its procedures have no default values). The call is described with ArxProcedureExpression, the way a query is described with ArxQueryExpression; ARX sends CALL with every value as a parameter, and checks named arguments against the procedure's own names.

2Read OUT and INOUT valuesProgressive

OUT and INOUT values, in two scenarios: an INOUT goes in and comes back (here filled in by the procedure when it arrives as NULL), and OUT values come back typed in Outputs. An OUT starts as NULL inside the procedure; an INOUT carries the value given. Each is declared with its .NET type, and MySqlConnector reads them after the call's last result set, in the same round trip.

3Call a function: a value, rows, an effectProgressive

Calling a function, in three scenarios: one value (Value<T>()); rows, which on MySQL come from a procedure (ResultSets[0]); and an effect, also a procedure's job. A function is the same ArxProcedureExpression with Function: ARX sends SELECT fn(…) instead of CALL. MySQL functions return one value, never rows (RetrieveMultiple's RealCase6 and Advanced8 show what to read instead).

4A procedure that returns tablesProgressive

A procedure that returns tables, in two scenarios. A MySQL procedure returns a table by selecting it: each SELECT it runs is a result set. ARX runs the call in one round trip and returns the result sets by position (ResultSets[0], ResultSets[1] …), in the order the procedure sends them.

Real cases

What an application does: a small CRM and product catalogue.

1Place an order through a procedureProgressive

Placing an order through a procedure, in two scenarios: sp_place_order writes the order and its lines (from the products' current prices) in one call and returns the new id and the total; the id is then used like any other (Retrieve reads the order back). The application doesn't repeat the pricing rule the procedure owns.

2A report with several result setsProgressive

An account page built from one call: sp_account_report returns three tables (the contacts, the orders, a summary row), read together in one round trip, each by its position: the order of the procedure's SELECTs.

3Page through a procedure's result: the procedure pages itself, ARX pages a result setProgressive

Paging what a procedure returns, in four scenarios. Each sets PageInfo, as a query does, and gets back a result set with CurrentPage, HasNextPage and, when asked, the total. The total is opt-in: ask for it once (page 1), as for RetrieveMultiple.

  1. The procedure pages itself (best for large data: the database produces only the page). ARX fills its page arguments from PageInfo and reads its total from an OUT parameter.
  2. A function's rows: MySQL functions return one value, never rows, so paging one is refused before anything is sent (ARX8001). Page the table with RetrieveMultiple instead: LIMIT/OFFSET and the total in one round trip.
  3. ARX pages a table the procedure returns (a result set): MySQL has no cursor to move, so ARX reads past the earlier rows, keeps one page and one row more, and counts the rest. Every row crosses the network.
  4. Which to choose.
4Business errors raised by a procedureProgressive

Business rules the database enforces, in three scenarios. sp_reserve_credit refuses an account on hold (SIGNAL SQLSTATE '45000' … MYSQL_ERRNO = 50101) and an amount over the credit limit (MYSQL_ERRNO = 50102). Both arrive as ARX3005 RaisedByDatabase with the procedure's message and its error number as NativeCode, so the application can tell "the database said no" from "something broke", and branch on the procedure's own number.

5A nightly job that commits in batchesProgressive

A nightly job that commits as it goes, in two scenarios. sp_recalculate_totals recalculates order totals in batches and commits after each, so a failure late in the run keeps the batches already done, and reports what it did as a note. ARX's own call runs outside any transaction, which a procedure that commits needs. MySQL lets a procedure COMMIT inside the caller's transaction too, and that COMMIT commits everything the transaction did before it, without an error ARX could see: call such procedures outside ARX transactions and batch scopes.

6Progress messages from a procedureProgressive

The messages a procedure sends, in two scenarios. A MySQL procedure reports with a warning: SIGNAL SQLSTATE '01000' (class 01 is a warning, not an error, and the procedure goes on). MySQL keeps only the warnings of the procedure's last statement, so a procedure reports one note when it ends rather than progress while it runs. It comes back in Messages, in order, for the application to show or log as it chooses (ARX never writes them to a log); CollectMessages = false leaves them out.

7Handle Execute errorsProgressive

The errors an Execute call can raise, turned into messages, in two scenarios: what the database refuses (a procedure that doesn't exist, arguments of the wrong type, a function called as a procedure, a timeout) and what ARX refuses before it (a positional argument after a named one, an OUT parameter without a type, a cursor parameter).

8Catch an SQL injection attempt on ExecuteProgressive

Catching an SQL injection attempt on Execute, in three scenarios. A routine name or a parameter name that carries SQL is refused with ARX1017 SuspectedInjection before the database. SQL in a value is a parameter: it's stored as text and runs nothing. Execute never takes SQL text.

Advanced cases

Dependency injection, transactions, batch scopes and setting the service up.

1Execute in an ARX transactionAdvanced

Execute inside an ARX transaction, in two scenarios: a Create and a procedure in one transaction, the procedure's writes read back inside it, and everything gone after a rollback; and a business error from the procedure aborting the transaction, so the next call is refused (ARX1015) until the application rolls back.

2Execute in a batch scope: a refused call undone aloneAdvanced

Execute in a batch scope, in one scenario: an import of three orders through the procedure, one transaction. Each call has its own savepoint (asked for with Savepoints = true), so the order the procedure refuses (a credit limit) is undone alone and the import continues; the others are committed together at the end.

3Export what a procedure returns, page by pageAdvanced

Exporting everything a procedure returns, page by page, in one scenario: sp_products_page pages itself (the first way of paging), the export asks for the total on the first page only, and reads pages until HasNextPage is false. Each page is one call, so the memory is one page's.

4A security check around Execute: probing, blocking, unblocking a false positiveAdvanced

A security check around Execute (SecurityGuard, in the project root), in three scenarios: a report run through an allowed list of procedures passes; an endpoint that runs whatever procedure the request names is probed, and the attacker is blocked after three attempts; and a false positive (an old client sending "account report (v1)") is triaged: support unblocks the user and the endpoint maps the old name.

5Execute on a preloaded, Strict schema: routines aren't covered, tables areAdvanced

Execute on a service whose schema is preloaded and Strict, in three scenarios: a procedure and a function called as usual, because Strict covers tables and views, not routines; a procedure's result set, with the routine's own columns; and the same service still refusing a table column that wasn't preloaded.