> For the complete documentation index, see [llms.txt](https://docs.veza.com/4yItIzMvkpAvMVFAamTf/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.veza.com/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/actions/send-sql-command.md).

# Send SQL Command

Executes a single SQL statement against MySQL, Microsoft SQL Server, Oracle, PostgreSQL, or SAP IQ (Sybase IQ) as a workflow step. Use it to provision, update, or deprovision users directly in a database-backed application, without standing up a REST adapter.

**Example use cases:**

* Insert a row into an application's user table on provisioning
* Update a status column when upstream employment state changes
* Delete or disable rows on deprovisioning
* Set role or group membership in a database-backed application

## Supported targets

| Database             | Driver                             |
| -------------------- | ---------------------------------- |
| MySQL                | Native MySQL protocol              |
| Microsoft SQL Server | Native TDS protocol                |
| Oracle               | Native Oracle protocol             |
| PostgreSQL           | Native PostgreSQL protocol         |
| SAP IQ (Sybase IQ)   | Sybase TDS protocol (SQL Anywhere) |

The target host must be reachable from the Veza control plane, or from an Insight Point when one is configured for the action (see [Insight Point execution](#insight-point-execution)).

## Settings

| Setting                           | Description                                                                                                                                                                                                            |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Credential                        | Stored connection credential. Database type, host, port, database, username, and password (or certificate) are configured once at **Lifecycle Management** > **Settings** > **Credentials** and reused across actions. |
| Database Type                     | MySQL, MS SQL Server, Oracle, PostgreSQL, or SAP IQ. Inherited from the credential; can be overridden inline.                                                                                                          |
| Host                              | Database host or DNS name. Inherited from the credential; inline value overrides.                                                                                                                                      |
| Port                              | Database port. Inherited from the credential; inline value overrides.                                                                                                                                                  |
| Database                          | Database (catalog) name. Inherited from the credential; inline value overrides.                                                                                                                                        |
| SSL Mode                          | One of `disable`, `require`, `verify-ca`, `verify-full`. See [TLS](#tls).                                                                                                                                              |
| CA Certificate                    | (Optional) PEM-encoded CA certificate, used with `verify-ca` and `verify-full`. See [TLS](#tls).                                                                                                                       |
| SQL Statement                     | SQL to execute. **Body placeholders are not supported**: use bound parameters. See [Parameter binding](#parameter-binding).                                                                                            |
| Parameters                        | Ordered values bound to driver placeholders (`?` for MySQL/MSSQL/SAP IQ, `$1`, `$2`, … for PostgreSQL, `:1`, `:2`, … for Oracle). Each value accepts `{attribute_name}` substitution and transformer expressions.      |
| Timeout                           | Statement timeout in seconds. Range: 0–3600. `0` uses the driver default.                                                                                                                                              |
| Only Send if Any Upstream Changes | When enabled, the statement runs only if a prior action in the workflow modified or created resources. Cannot be combined with [Insight Point execution](#insight-point-execution).                                    |

A non-empty inline value overrides the corresponding credential field. One credential can drive many actions while individual actions retarget host or database without a new credential entry.

### Centrally-managed credentials

Credentials live under **Lifecycle Management** > **Settings** > **Credentials**. Passwords are encrypted at rest and write-only on read, matching the REST authentication credential model.

To create a credential:

1. Go to **Lifecycle Management** > **Settings** > **Credentials**.
2. Click **Add Credential** and select the database type.
3. Enter host, port, database, username, and password (or, where supported, a certificate).
4. Save. The credential appears in the **Credential** picker for any **Send SQL Command** action.

## Parameter binding

Unlike most LCM actions, the **SQL Statement** field does **not** accept `{attribute_name}` placeholders: any `{...}` in the body is rejected at save time. All identity substitution flows through the **Parameters** list, bound by the driver using its native placeholder syntax:

| Database             | Placeholder syntax |
| -------------------- | ------------------ |
| MySQL                | `?`                |
| Microsoft SQL Server | `?`                |
| PostgreSQL           | `$1`, `$2`, …      |
| Oracle               | `:1`, `:2`, …      |
| SAP IQ (Sybase IQ)   | `?`                |

Each Parameters entry accepts `{attribute_name}` substitution and transformer expressions. Available attribute sources:

* **Source-of-identity (SOI) attributes**: fields on the identity record that triggered the workflow.
* **Sync Identities output entity attributes**: when a preceding [Sync Identities](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/actions/sync-identities.md) action provisioned or updated a target user in the same workflow.

**Example, PostgreSQL insert with bound parameters:**

```sql
INSERT INTO app_users (email, employee_id, department, status)
VALUES ($1, $2, $3, 'active')
```

Parameters list:

| Position | Value           |
| -------- | --------------- |
| `$1`     | `{email}`       |
| `$2`     | `{employee_id}` |
| `$3`     | `{department}`  |

Driver-side binding means quotes, comments, and statement terminators in attribute values pass through without breaking the statement. Identifiers (table or column names) cannot be parameterized: they are part of the validated statement body.

Parameter values support transformer expressions (`UPPER`, `LOWER`, `TRIM`, dot notation for nested attributes). See [Transformers](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/transformers.md).

## Form field substitution (Access Requests only)

When a Send SQL Command action is configured as part of an Access Request [catalog definition](/4yItIzMvkpAvMVFAamTf/features/access-hub/configuration.md), Parameters entries can reference values submitted on the request form. The requester provides values (such as a username or role identifier) at request time, and those values are bound to the SQL statement at execution.

Use the `{$form_field.field_name}` syntax inside a Parameters value, where `field_name` matches a field defined on the catalog definition.

To configure form field substitution:

1. Build the SQL statement with driver-native placeholders for every requester-supplied value, and add a Parameters entry for each:

   ```sql
   INSERT INTO app_users (email, role_name)
   VALUES ($1, $2)
   ```

   | Position | Value                     |
   | -------- | ------------------------- |
   | `$1`     | `{$form_field.email}`     |
   | `$2`     | `{$form_field.role_name}` |
2. On the matching catalog definition, add form fields whose names match the placeholders (`email` and `role_name` in the example above).
3. When a user submits an access request, the value entered for each form field replaces the corresponding `{$form_field.*}` placeholder in the Parameters list. The substituted value is then bound by the driver. The `{$form_field.*}` syntax is not substituted inside the **SQL Statement** body; use it only inside Parameters.

Field names must contain only letters, numbers, and underscores (`role_name` is valid, `role-name` is not). A field whose name contains an unsupported character might be accepted at catalog-definition save time but is not substituted at execution time, and the literal `{$form_field.*}` placeholder is bound as the parameter value. If a placeholder references a field that was not provided on the access request, the placeholder is likewise left unchanged.

Because substituted values populate Parameters rather than the SQL Statement, they are bound by the driver and cannot alter statement structure.

## Statement validation

At save time, the statement is rejected if it contains:

* Statement terminators inside the body (`;`)
* SQL line comments (`--`)
* SQL block comments (`/* ... */`)
* MySQL line comments (`#`, MySQL only)

These rules enforce single-statement execution and block the common patterns that smuggle multi-statement payloads past parameter binding. To run multiple statements, split them into separate actions or wrap them in a stored procedure call.

## TLS

The **SSL Mode** setting controls TLS behavior:

| Mode          | Behavior                                                                      |
| ------------- | ----------------------------------------------------------------------------- |
| `disable`     | Plain TCP, no TLS.                                                            |
| `require`     | TLS without server-certificate validation.                                    |
| `verify-ca`   | TLS with CA validation. Requires a CA certificate.                            |
| `verify-full` | TLS with CA validation plus hostname verification. Requires a CA certificate. |

Caveats:

* **MySQL and SAP IQ** require a non-empty **CA Certificate** for both `verify-ca` and `verify-full`; an empty CA is rejected because the server certificate would otherwise not be verified.
* **Custom CA certificates are not yet honored for MySQL or PostgreSQL.** Validation rejects a custom CA for those drivers; Microsoft SQL Server, Oracle, and SAP IQ accept custom CAs today.

## Insight Point execution

Set **Datasource** to route execution through an Insight Point instead of the control plane. Use this when the target database is unreachable from the Veza control plane (for example, a Postgres instance behind a firewall).

| Setting    | Description                                                                                                                                                           |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Datasource | (Optional) Custom Provider data source to route through. Must be configured as an Insight Point Custom Application. When empty, the action runs on the control plane. |

{% hint style="warning" %}
Insight Point execution cannot be combined with **Only Send if Any Upstream Changes**. Setting both `datasource_id` and the upstream-changes flag is rejected at validation.
{% endhint %}

## Output

| Field           | Description                                                                                                                     |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `rows_affected` | Rows affected by the statement, as reported by the driver. Use it to assert that an UPDATE or DELETE matched the intended rows. |

`rows_affected` is exposed to downstream conditions and actions in the workflow context.

## Configuration

Add the action through the Policy Editor or the policy update API:

* **Policy Editor**: open the policy, choose **Add Action**, then select **Send SQL Command**.
* **API**: include the action in the policy's `actions` array. The action type identifier is `SEND_SQL_COMMAND`. See [Update Policy Configuration](/4yItIzMvkpAvMVFAamTf/developers/api/lifecycle-management/policies/updatepolicyconfiguration.md).

## Limitations

* Only MySQL, MS SQL Server, Oracle, PostgreSQL, and SAP IQ are supported.
* Statements must be valid SQL for the selected dialect. Veza does not translate SQL between dialects.
* One statement per invocation. Split multi-statement scripts into multiple actions or wrap them in a stored procedure.
* Result-set rows are not exposed: only `rows_affected`. To branch on query results, use [Get Node From Graph](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/actions/get-node-from-graph.md) when the data is already in the Access Graph, or [Send REST Request](/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/actions/send-rest-request.md) when a JSON return value is required.
* Custom CA certificates are not yet honored for MySQL or PostgreSQL.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.veza.com/4yItIzMvkpAvMVFAamTf/features/lifecycle-management/policies-workflows/actions/send-sql-command.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
