> For the complete documentation index, see [llms.txt](https://developer.harness.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.harness.io/database-devops/3.0/setup-db-devops/jdbc-connection-string/get-started-bigtable.md).

# Get Started with Google Cloud BigTable

Configure Harness Database DevOps to manage Google Cloud BigTable schema changes using Liquibase-based change types.

This guide explains how to connect Harness Database DevOps to Google Cloud BigTable and manage schema changes using declarative YAML changelogs. You will create tables, column families, and garbage collection rules through a Liquibase-based extension purpose-built for BigTable's native gRPC API.

## Before you begin <a href="#before-you-begin" id="before-you-begin"></a>

* Active Harness account with Database DevOps module enabled.
* A Google Cloud project with BigTable API enabled and at least one BigTable instance provisioned.
* Permissions to manage BigTable tables and column families (`bigtable.tables.create`, `bigtable.tables.update`, `bigtable.tables.delete`).
* Permissions to create connectors and Database DevOps pipelines in Harness. Go to [RBAC in Harness](/harness-platform/3.0/harness-platform-resources/platform-access-control.md) to configure roles.

## BigTable schema concepts <a href="#bigtable-schema-concepts" id="bigtable-schema-concepts"></a>

BigTable is a wide-column NoSQL database. Its schema model differs from relational databases:

* **Table:** The top-level container, identified by a table ID within a BigTable instance.
* **Column family:** A logical grouping of columns within a table. All columns within a family share the same garbage collection policy. You define column families at schema design time.
* **Garbage collection (GC) rules:** Policies that automatically delete stale cell versions from a column family. BigTable stores multiple timestamped versions of each cell value. GC rules control how many versions or how long versions are retained.

Unlike SQL databases, BigTable has no `CREATE TABLE` DDL. Harness Database DevOps handles this through custom Liquibase change types that call the BigTable Admin API directly via gRPC.

## OIDC / Workload Identity <a href="#oidc-workload-identity" id="oidc-workload-identity"></a>

When the Harness runner operates on a GCP-managed environment (GKE, Cloud Run, Compute Engine), it can use Application Default Credentials via Workload Identity without a key file. No additional credential configuration is required when Workload Identity is properly configured for the runner's service account.

## Connect to BigTable <a href="#connect-to-bigtable" id="connect-to-bigtable"></a>

The BigTable connection URL uses the following format:

| Scenario            | URL                                                          |
| ------------------- | ------------------------------------------------------------ |
| Default app profile | `bigtable:projects/<project_name>/instances/<instance_name>` |

To create a BigTable connector in Harness:

1. In your Harness project, go to **Connectors** under **Project Setup**.
2. Select **New Connector**, then select **Google Cloud BigTable** under **Cloud Providers**.
3. Enter a **Name** for the connector (for example, `gcp-bigtable-prod`).
4. Enter the **Connection URL** in the format above.
5. Under **Credentials**, select **OIDC / Workload Identity**. The runner uses Application Default Credentials from the GCP-managed environment.
6. Select **Save and Continue** to test the connection.

## Supported change types <a href="#supported-change-types" id="supported-change-types"></a>

Harness Database DevOps supports thirteen change types for BigTable. Each maps directly to a BigTable Admin API operation.

### createBigtableTable <a href="#createbigtabletable" id="createbigtabletable"></a>

Creates a new BigTable table. You can define one or more column families and their GC rules in the same changeset.

**Properties:**

| Property                  | Required | Description                                                                                                                |
| ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `tableName`               | Yes      | The ID of the table to create.                                                                                             |
| `columnFamilies`          | Yes      | List of column family definitions. BigTable requires at least one column family per table.                                 |
| `columnFamilies[].name`   | Yes      | Column family name.                                                                                                        |
| `columnFamilies[].gcRule` | No       | GC rule object. Supports `maxNumVersions`, `maxAge`, `union`, and `intersection`. Omit to store all versions indefinitely. |

**Example - Create a table with two column families and a rollback:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-users-table
      author: harness
      changes:
        - createBigtableTable:
            tableName: users
            columnFamilies:
              - name: profile
                gcRule:
                  maxAge: 90d
              - name: activity
                gcRule:
                  maxNumVersions: 10
      rollback:
        - deleteBigtableTable:
            tableName: users
```

**Example - Create a table with composite GC rules:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-metrics-table
      author: harness
      changes:
        - createBigtableTable:
            tableName: metrics
            columnFamilies:
              - name: raw
                gcRule:
                  intersection:
                    - gcRule:
                        maxAge: 7d
                    - gcRule:
                        maxNumVersions: 5
              - name: aggregated
                gcRule:
                  union:
                    - gcRule:
                        maxAge: 365d
                    - gcRule:
                        maxNumVersions: 1
              - name: labels
```

### deleteBigtableTable <a href="#deletebigtabletable" id="deletebigtabletable"></a>

Deletes a BigTable table and all its data. This operation is irreversible. It is most commonly used as the `rollback` target for a `createBigtableTable` changeset.

**Properties:**

| Property    | Required | Description                    |
| ----------- | -------- | ------------------------------ |
| `tableName` | Yes      | The ID of the table to delete. |

**Example - Delete a table:**

```yaml
databaseChangeLog:
  - changeSet:
      id: drop-legacy-table
      author: harness
      changes:
        - deleteBigtableTable:
            tableName: legacy_events
```

### createColumnFamily <a href="#createcolumnfamily" id="createcolumnfamily"></a>

Adds a new column family to an existing table. Use this change type when you need to extend a table's schema after initial creation.

**Properties:**

| Property     | Required | Description                                                                                                                 |
| ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `tableName`  | Yes      | The ID of the existing table.                                                                                               |
| `familyName` | Yes      | The column family name to create.                                                                                           |
| `gcRule`     | No       | GC rule object. Supports `maxNumVersions`, `maxAge`, `union`, and `intersection`. Omit to retain all versions indefinitely. |

**Example - Add a column family with a max-age rule and a rollback:**

```yaml
databaseChangeLog:
  - changeSet:
      id: cf-maxage
      author: harness
      changes:
        - createColumnFamily:
            tableName: users
            familyName: sessions
            gcRule:
              maxAge: 7d
      rollback:
        - deleteColumnFamily:
            tableName: users
            familyName: sessions
```

**Example - Add a column family with a version-based rule:**

```yaml
databaseChangeLog:
  - changeSet:
      id: cf-basic
      author: harness
      changes:
        - createColumnFamily:
            tableName: users
            familyName: profile
            gcRule:
              maxNumVersions: 3
```

**Example - Add a column family with a union rule:**

```yaml
databaseChangeLog:
  - changeSet:
      id: cf-union
      author: harness
      changes:
        - createColumnFamily:
            tableName: orders
            familyName: audit_log
            gcRule:
              union:
                - gcRule:
                    maxNumVersions: 5
                - gcRule:
                    maxAge: 90d
```

**Example - Add a column family with an intersection rule:**

```yaml
databaseChangeLog:
  - changeSet:
      id: cf-intersection
      author: harness
      changes:
        - createColumnFamily:
            tableName: products
            familyName: metadata
            gcRule:
              intersection:
                - gcRule:
                    maxNumVersions: 2
                - gcRule:
                    maxAge: 30d
```

**Example - Add a column family with no GC rule:**

```yaml
databaseChangeLog:
  - changeSet:
      id: cf-no-gc-rule
      author: harness
      changes:
        - createColumnFamily:
            tableName: events
            familyName: raw_data
```

### deleteColumnFamily <a href="#deletecolumnfamily" id="deletecolumnfamily"></a>

Removes a column family and all its data from a table. This operation is irreversible. Harness Database DevOps tracks this changeset in the `DATABASECHANGELOG` table so it does not re-execute on subsequent runs.

**Properties:**

| Property     | Required | Description                                       |
| ------------ | -------- | ------------------------------------------------- |
| `tableName`  | Yes      | The ID of the table containing the column family. |
| `familyName` | Yes      | The name of the column family to delete.          |

{% hint style="info" %}
Run `deleteColumnFamily` in a separate changeset from `createColumnFamily` operations on the same table. This ensures Harness Database DevOps can track each operation independently and roll back selectively if needed.
{% endhint %}

**Example - Remove a deprecated column family:**

```yaml
databaseChangeLog:
  - changeSet:
      id: 7
      author: harness
      changes:
        - deleteColumnFamily:
            tableName: users
            familyName: legacy_data
```

**Example - Remove multiple column families in sequence:**

```yaml
databaseChangeLog:
  - changeSet:
      id: 8
      author: harness
      changes:
        - deleteColumnFamily:
            tableName: events
            familyName: raw_v1

  - changeSet:
      id: 9
      author: harness
      changes:
        - deleteColumnFamily:
            tableName: events
            familyName: raw_v2
```

### modifyColumnFamilyGCRule <a href="#modifycolumnfamilygcrule" id="modifycolumnfamilygcrule"></a>

Updates the garbage collection rule on an existing column family. Use this change type to tighten or relax data retention policies without recreating the column family.

**Properties:**

| Property     | Required | Description                                                                                        |
| ------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `tableName`  | Yes      | The ID of the table containing the column family.                                                  |
| `familyName` | Yes      | The name of the column family to modify.                                                           |
| `gcRule`     | Yes      | The new GC rule object to apply. Supports `maxNumVersions`, `maxAge`, `union`, and `intersection`. |

**Example - Reduce retention to one version per cell:**

```yaml
databaseChangeLog:
  - changeSet:
      id: tighten-profile-gc
      author: harness
      changes:
        - modifyColumnFamilyGCRule:
            tableName: users
            familyName: profile
            gcRule:
              maxNumVersions: 1
```

**Example - Extend retention to 1 year:**

```yaml
databaseChangeLog:
  - changeSet:
      id: extend-raw-retention
      author: harness
      changes:
        - modifyColumnFamilyGCRule:
            tableName: metrics
            familyName: raw
            gcRule:
              maxAge: 365d
```

**Example - Apply an intersection rule to reduce storage costs:**

```yaml
databaseChangeLog:
  - changeSet:
      id: tighten-events-gc
      author: harness
      changes:
        - modifyColumnFamilyGCRule:
            tableName: activity
            familyName: events
            gcRule:
              intersection:
                - gcRule:
                    maxAge: 30d
                - gcRule:
                    maxNumVersions: 3
```

### dropBigTableRows <a href="#dropbigtablerows" id="dropbigtablerows"></a>

Deletes rows from a BigTable table that match a row key prefix or range. Use this change type to purge data as part of a schema migration, for example when retiring a row key scheme before switching to a new one.

**Properties:**

| Property       | Required | Description                                                                                                      |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `tableName`    | Yes      | The ID of the table to delete rows from.                                                                         |
| `rowKeyPrefix` | No       | Delete all rows whose row key starts with this string.                                                           |
| `startRowKey`  | No       | Delete rows with row keys greater than or equal to this value. Used together with `endRowKey` to define a range. |
| `endRowKey`    | No       | Delete rows with row keys less than this value (exclusive). Used together with `startRowKey`.                    |

{% hint style="info" %}
Use either `rowKeyPrefix` for prefix-based deletion or `startRowKey`/`endRowKey` for range-based deletion - not both in the same changeset.
{% endhint %}

**Example - Delete all rows with a given prefix:**

```yaml
databaseChangeLog:
  - changeSet:
      id: drop-test-rows
      author: harness
      changes:
        - dropBigTableRows:
            tableName: users
            rowKeyPrefix: "test#"
```

**Example - Delete rows in a key range:**

```yaml
databaseChangeLog:
  - changeSet:
      id: drop-archived-rows
      author: harness
      changes:
        - dropBigTableRows:
            tableName: events
            startRowKey: "2023-01-01"
            endRowKey: "2024-01-01"
```

### modifyChangeStreamConfig <a href="#modifychangestreamconfig" id="modifychangestreamconfig"></a>

Enables or updates the change stream on a BigTable table. A change stream captures row mutations in real time and makes them available to consumers such as Dataflow pipelines. Use this change type to set the retention period for captured changes.

**Properties:**

| Property                             | Required | Description                                                                                                                                                    |
| ------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tableName`                          | Yes      | The ID of the table to configure the change stream on.                                                                                                         |
| `changeStreamConfig.retentionPeriod` | Yes      | How long change stream data is retained. Accepts a duration string with a unit suffix: `d` (days), `h` (hours), `m` (minutes), `s` (seconds). Maximum is `7d`. |

**Example - Enable a change stream with 3-day retention:**

```yaml
databaseChangeLog:
  - changeSet:
      id: enable-change-stream
      author: harness
      changes:
        - modifyChangeStreamConfig:
            tableName: user_events
            changeStreamConfig:
              retentionPeriod: 3d
```

**Example - Extend change stream retention:**

```yaml
databaseChangeLog:
  - changeSet:
      id: extend-change-stream-retention
      author: harness
      changes:
        - modifyChangeStreamConfig:
            tableName: user_events
            changeStreamConfig:
              retentionPeriod: 7d
```

### createBigtableMaterializedView <a href="#createbigtablematerializedview" id="createbigtablematerializedview"></a>

Creates a materialized view on a BigTable table. A materialized view pre-computes and stores the results of a SQL query, enabling fast reads against aggregated or projected data without scanning the full table on every request.

**Properties:**

| Property             | Required | Description                                                             |
| -------------------- | -------- | ----------------------------------------------------------------------- |
| `name`               | Yes      | A unique name for the materialized view.                                |
| `query`              | Yes      | A SQL query string that defines the view's contents.                    |
| `deletionProtection` | No       | When `true`, prevents the view from being deleted. Defaults to `false`. |

**Example - Create a materialized view with a rollback:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-activity-summary-view
      author: harness
      changes:
        - createBigtableMaterializedView:
            name: user_activity_summary
            query: >
              SELECT _key, profile['user_id'] AS user_id
              FROM user_events
              ORDER BY _key ASC
      rollback:
        - deleteBigtableMaterializedView:
            name: user_activity_summary
```

**Example - Create a materialized view with deletion protection:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-protected-summary-view
      author: harness
      changes:
        - createBigtableMaterializedView:
            name: orders_summary
            query: >
              SELECT _key, audit_log['action'] AS action
              FROM orders
            deletionProtection: true
```

***

### deleteBigtableMaterializedView <a href="#deletebigtablematerializedview" id="deletebigtablematerializedview"></a>

Removes a materialized view. This does not affect the underlying table or its data.

**Properties:**

| Property | Required | Description                                  |
| -------- | -------- | -------------------------------------------- |
| `name`   | Yes      | The name of the materialized view to delete. |

{% hint style="info" %}
If `deletionProtection` is set to `true` on the view, use `modifyBigtableMaterializedView` to disable it before running `deleteBigtableMaterializedView`.
{% endhint %}

**Example - Delete a materialized view:**

```yaml
databaseChangeLog:
  - changeSet:
      id: drop-activity-summary-view
      author: harness
      changes:
        - deleteBigtableMaterializedView:
            name: user_activity_summary
```

***

### modifyBigtableMaterializedView <a href="#modifybigtablematerializedview" id="modifybigtablematerializedview"></a>

Updates an existing materialized view. Use this change type to replace the SQL query or toggle deletion protection.

**Properties:**

| Property             | Required | Description                                                        |
| -------------------- | -------- | ------------------------------------------------------------------ |
| `name`               | Yes      | The name of the materialized view to modify.                       |
| `query`              | Yes      | The SQL query string that defines the view.                        |
| `deletionProtection` | No       | Set to `true` to enable or `false` to disable deletion protection. |

**Example - Update the query on an existing materialized view:**

```yaml
databaseChangeLog:
  - changeSet:
      id: extend-activity-summary-view
      author: harness
      changes:
        - modifyBigtableMaterializedView:
            name: user_activity_summary
            query: >
              SELECT _key, profile['user_id'] AS user_id,
              activity['event_type'] AS event_type
              FROM user_events
              ORDER BY _key ASC
```

**Example - Disable deletion protection before removing a materialized view:**

```yaml
databaseChangeLog:
  - changeSet:
      id: unprotect-orders-summary
      author: harness
      changes:
        - modifyBigtableMaterializedView:
            name: orders_summary
            query: >
              SELECT _key, audit_log['action'] AS action
              FROM orders
            deletionProtection: false

  - changeSet:
      id: drop-orders-summary
      author: harness
      changes:
        - deleteBigtableMaterializedView:
            name: orders_summary
```

***

### createBigtableLogicalView <a href="#createbigtablelogicalview" id="createbigtablelogicalview"></a>

Creates a logical view over a BigTable table. A logical view defines a SQL query that selects specific columns visible to applications, enabling column-level access control and query isolation without duplicating data.

**Properties:**

| Property             | Required | Description                                                             |
| -------------------- | -------- | ----------------------------------------------------------------------- |
| `name`               | Yes      | A unique name for the logical view.                                     |
| `query`              | Yes      | A SQL query string that defines the view.                               |
| `deletionProtection` | No       | When `true`, prevents the view from being deleted. Defaults to `false`. |

**Example - Create a view over selected columns and a rollback:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-activity-view
      author: harness
      changes:
        - createBigtableLogicalView:
            name: recent_activity
            query: >
              SELECT _key, activity['event_type'] AS event_type
              FROM user_events
      rollback:
        - deleteBigtableLogicalView:
            name: recent_activity
```

**Example - Create a view with deletion protection enabled:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-audit-view
      author: harness
      changes:
        - createBigtableLogicalView:
            name: orders-audit-view
            query: >
              SELECT _key, audit_log['action'] AS action
              FROM orders
            deletionProtection: true
```

### deleteBigtableLogicalView <a href="#deletebigtablelogicalview" id="deletebigtablelogicalview"></a>

Removes a logical view. This does not affect the underlying table or its data.

**Properties:**

| Property | Required | Description                             |
| -------- | -------- | --------------------------------------- |
| `name`   | Yes      | The name of the logical view to delete. |

{% hint style="info" %}
If `deletionProtection` is set to `true` on the view, use `modifyBigtableLogicalView` to disable it before running `deleteBigtableLogicalView`.
{% endhint %}

**Example - Delete a logical view:**

```yaml
databaseChangeLog:
  - changeSet:
      id: drop-activity-view
      author: harness
      changes:
        - deleteBigtableLogicalView:
            name: recent_activity
```

### modifyBigtableLogicalView <a href="#modifybigtablelogicalview" id="modifybigtablelogicalview"></a>

Updates an existing logical view. Use this change type to replace the SQL query or toggle deletion protection.

**Properties:**

| Property             | Required | Description                                                        |
| -------------------- | -------- | ------------------------------------------------------------------ |
| `name`               | Yes      | The name of the logical view to modify.                            |
| `query`              | Yes      | The SQL query string that defines the view.                        |
| `deletionProtection` | No       | Set to `true` to enable or `false` to disable deletion protection. |

**Example - Update the query on an existing view:**

```yaml
databaseChangeLog:
  - changeSet:
      id: extend-activity-view
      author: harness
      changes:
        - modifyBigtableLogicalView:
            name: recent_activity
            query: >
              SELECT _key, activity['event_type'] AS event_type,
              activity['timestamp'] AS timestamp
              FROM user_events
```

**Example - Disable deletion protection before removing a view:**

```yaml
databaseChangeLog:
  - changeSet:
      id: unprotect-audit-view
      author: harness
      changes:
        - modifyBigtableLogicalView:
            name: orders-audit-view
            query: >
              SELECT _key, audit_log['action'] AS action
              FROM orders
            deletionProtection: false

  - changeSet:
      id: drop-audit-view
      author: harness
      changes:
        - deleteBigtableLogicalView:
            name: orders-audit-view
```

## Create a changelog file <a href="#create-a-changelog-file" id="create-a-changelog-file"></a>

Create a YAML changelog file in your repository. A single changelog can contain multiple changesets targeting the same or different tables.

**Example - Full schema setup for a new instance:**

```yaml
databaseChangeLog:
  - changeSet:
      id: create-users
      author: platform-team
      changes:
        - createBigtableTable:
            tableName: users
            columnFamilies:
              - name: profile
                gcRule:
                  maxAge: 90d
              - name: activity
                gcRule:
                  maxNumVersions: 20
              - name: preferences
                gcRule:
                  maxAge: 180d

  - changeSet:
      id: create-session-events
      author: harness
      changes:
        - createBigtableTable:
            tableName: session_events
            columnFamilies:
              - name: events
                gcRule:
                  intersection:
                    - gcRule:
                        maxAge: 30d
                    - gcRule:
                        maxNumVersions: 5
              - name: metadata
                gcRule:
                  maxNumVersions: 1

  - changeSet:
      id: create-feature-flags
      author: platform-team
      changes:
        - createBigtableTable:
            tableName: feature_flags
            columnFamilies:
              - name: flags
```

## Change authoring with Harness AI

The **Author DB Change** workflow supports BigTable. Because BigTable has no SQL DDL, generated changesets use the BigTable change types documented above rather than the generic Liquibase change types used for relational databases. A request to add a column family produces `createColumnFamily`, not `addColumn`.

The following table shows how common authoring requests map to BigTable operations.

| Request                                  | Generated BigTable change type                                  | Generic change type not used |
| ---------------------------------------- | --------------------------------------------------------------- | ---------------------------- |
| Create a table with column families      | `createBigtableTable`                                           | `createTable`                |
| Add a column family to an existing table | `createColumnFamily`                                            | `addColumn`                  |
| Change retention on a column family      | `modifyColumnFamilyGCRule`                                      | `modifyDataType`             |
| Remove a column family                   | `deleteColumnFamily`                                            | `dropColumn`                 |
| Remove a table                           | `deleteBigtableTable`                                           | `dropTable`                  |
| Purge rows by key prefix or range        | `dropBigTableRows`                                              | `delete`                     |
| Set change stream retention              | `modifyChangeStreamConfig`                                      | Not applicable               |
| Create a materialized or logical view    | `createBigtableMaterializedView` or `createBigtableLogicalView` | `createView`                 |

{% hint style="warning" %}
Generic relational change types such as `createTable`, `addColumn`, and `dropTable` are not valid against a BigTable instance, because they have no equivalent in the BigTable Admin API. A changelog that contains them fails validation rather than applying a partial change.
{% endhint %}

Before the generated changeset is committed, the LLM authoring pipeline runs the **DB Test and Preview** step, which executes the Liquibase `validate` command against the selected BigTable instance. Validation confirms that the change types, table names, and column family properties resolve against the target instance.

Go to [Configure Author DB Change](/database-devops/3.0/use-db-devops/create-database-updates/configure-llm-for-database-devops.md) to set up the authoring pipeline, and to [DB Test and Preview step](/database-devops/3.0/use-db-devops/deployment-pipeline-configuration/step-types/test-and-preview.md) to configure the validation step.

## Governance for BigTable changes

BigTable has no SQL DDL, so the preview steps cannot emit SQL statements the way they do for a relational instance. For BigTable they surface the change itself instead: the changelog YAML, and the serialized details of each change, including the table, column families, GC rules, row key ranges, and view properties. Apply and rollback flows therefore remain reviewable without a SQL preview, and the same governance surfaces used for relational databases still apply.

### Apply and rollback previews

Two steps generate a preview without modifying data:

| Step             | What it previews                                                              |
| ---------------- | ----------------------------------------------------------------------------- |
| **Update SQL**   | The pending changesets that an apply would run against the BigTable instance. |
| **Rollback SQL** | The changes that a rollback would run, resolved from the rollback target.     |

For a BigTable instance, both steps output the changelog YAML and serialized change details rather than SQL text. Reviewers read the operation and its properties directly, for example a `deleteColumnFamily` naming the table and family it removes.

{% hint style="info" %}
Rollback coverage depends on the changeset. `deleteBigtableTable` and `deleteColumnFamily` are irreversible, so a rollback preview cannot restore the deleted data. Define an explicit `rollback` block on changesets that support one, as shown in the change type examples above.
{% endhint %}

Go to [Update SQL step](/database-devops/3.0/use-db-devops/deployment-pipeline-configuration/step-types/update-sql.md) and [Rollback SQL step](/database-devops/3.0/use-db-devops/deployment-pipeline-configuration/step-types/rollback-sql.md) to configure the previews and reference their output in downstream steps.

### Policy evaluation with OPA

Because the preview carries structured change data rather than SQL text, OPA policies for BigTable match on the change type and its properties instead of matching SQL with a regular expression. This supports policies such as the following:

* Deny `deleteBigtableTable` or `deleteColumnFamily` against a production instance.
* Require every new column family to declare a `gcRule`, so that data retention is always explicit.
* Restrict `dropBigTableRows` to changesets that specify a bounded row key range.
* Cap the number of changes in a single changeset.

Go to [Using Rego for Database DevOps Steps](/database-devops/3.0/use-db-devops/governance-and-compliance/using-opa-with-database-devops.md) to create a policy, attach it to a policy set, and evaluate it during a pipeline run.

### Manual approval gates

Place a **Manual Approval** step between the preview step and the apply step so that a reviewer confirms the change before it reaches the instance. The approval message can include the preview step output, which gives approvers the changelog YAML and serialized change details for the pending change.

Go to [Preview SQL with Manual Approval](/database-devops/3.0/use-db-devops/governance-and-compliance/using-approval-gates-with-harness-ui.md) to build the preview, approval, and apply sequence.

## Next steps

* Go to [Apply DB Schema step](/database-devops/3.0/use-db-devops/deployment-pipeline-configuration/step-types/apply-dbschema-step.md) to apply approved BigTable changesets to an instance.
* Go to [Configure Author DB Change](/database-devops/3.0/use-db-devops/create-database-updates/configure-llm-for-database-devops.md) to generate BigTable changesets with Harness AI.
* Go to [Using Rego for Database DevOps Steps](/database-devops/3.0/use-db-devops/governance-and-compliance/using-opa-with-database-devops.md) to enforce policies on BigTable changes.

{% @harness-feedback/feedback %}
