Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/spanner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Flowable Spanner Build

on:
push:
branches:
- main
- spanner-migration
- 'flowable-release-*'
pull_request:
branches:
- main
- spanner-migration

env:
MAVEN_ARGS: >-
-Dmaven.javadoc.skip=true
-B -V --no-transfer-progress
-Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120

jobs:
test_spanner:
name: Cloud Spanner Emulator
runs-on: ubuntu-latest
services:
spanner-emulator:
image: gcr.io/cloud-spanner-emulator/emulator:latest
ports:
- 9010:9010
- 9020:9020
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: 17
- name: Restore Maven Repository Cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Test
id: test
run: >-
./mvnw test
${MAVEN_ARGS}
-Pspanner
"-Djdbc.url=jdbc:cloudspanner://localhost:9010/projects/test-project/instances/test-instance/databases/test-db;usePVGeneration=true;lenient=true;autoConfigEmulator=true"
-Djdbc.driver=com.google.cloud.spanner.jdbc.JdbcDriver
-pl modules/flowable-engine-common,modules/flowable-engine
-am
-Dtest=*Spanner*
-Dsurefire.failIfNoSpecifiedTests=false
-Dmaven.test.redirectTestOutputToFile=false
- name: Upload test artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: ${{ failure() && steps.test.conclusion == 'failure' }}
with:
name: surefire-test-reports-spanner
path: '**/target/surefire-reports/*'
31 changes: 31 additions & 0 deletions .sam/changes/unit-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Unit 1: Root Spanner Configuration - Change Summary

## Overview
Introduced the Cloud Spanner JDBC driver dependency management and build profile to Flowable Engine without modifying default build behavior or existing database drivers.

## Modified Files
- [`modules/flowable-dependencies/pom.xml`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-dependencies/pom.xml)
- [`modules/flowable-parent/pom.xml`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-parent/pom.xml)

## Details of Changes
1. **`modules/flowable-dependencies/pom.xml`**:
- Added `<google-cloud-spanner-jdbc.version>2.27.0</google-cloud-spanner-jdbc.version>` under `<!-- JDBC Driver dependencies -->`.
- Added `com.google.cloud:google-cloud-spanner-jdbc` to the `<dependencyManagement><dependencies>` section under `<!-- JDBC Drivers -->`.
2. **`modules/flowable-parent/pom.xml`**:
- Added `<profile><id>spanner</id>` declaring the dependency `com.google.cloud:google-cloud-spanner-jdbc` alongside existing database profiles (`postgresql`, `mssql`, `mysql`, `mariadb`, `db2`, `oracle`).
3. **Compatibility Preserved**:
- Existing database driver dependencies, versions, and configurations were left untouched.
- The default Maven build profile remains standard H2 in-memory.

## Decisions & Defaults
- **Driver Version**: Used confirmed version `2.27.0` for `com.google.cloud:google-cloud-spanner-jdbc` per plan specification.
- **Dependency Management Location**: Defined in `flowable-dependencies` alongside other JDBC drivers (`db2-jcc`, `h2`, `mssql-jdbc`, `mysql-connector-j`, `ojdbc17`, `postgresql`) ensuring uniform version management across all Flowable engine modules.
- **Profile Configuration**: Followed existing convention in `flowable-parent` where database profiles include only their corresponding driver artifact without version (inheriting from `dependencyManagement`).

## Verification & Test Status
- Ran default Maven build:
`./mvnw test-compile -pl modules/flowable-dependencies,modules/flowable-parent -DskipTests` -> **BUILD SUCCESS**
- Ran Spanner profile dependency resolution:
`./mvnw dependency:resolve -pl modules/flowable-parent -Pspanner` -> **BUILD SUCCESS** (successfully resolved `google-cloud-spanner-jdbc:2.27.0` and transitive Google Cloud/gRPC/protobuf libraries)
- Ran Spanner profile compile:
`./mvnw test-compile -pl modules/flowable-dependencies,modules/flowable-parent -Pspanner -DskipTests` -> **BUILD SUCCESS**
51 changes: 51 additions & 0 deletions .sam/changes/unit-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Unit 2: Spanner Engine Dialect & Session Configuration - Change Summary

## Overview
Configured Flowable Engine dialect introspection, dialect properties, metadata filtering, and null-ordering handling for Google Cloud Spanner (GoogleSQL dialect) across `flowable-engine-common`.

## Modified and Created Files
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/DbUtil.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/DbUtil.java)
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java)
- [`modules/flowable-engine-common/src/main/resources/org/flowable/common/db/properties/spanner.properties`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/resources/org/flowable/common/db/properties/spanner.properties) *(New File)*
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/ListQueryParameterObject.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/ListQueryParameterObject.java)
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/persistence/entity/TableDataManagerImpl.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/persistence/entity/TableDataManagerImpl.java)
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/AbstractSqlScriptBasedDbSchemaManager.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/AbstractSqlScriptBasedDbSchemaManager.java)
- [`modules/flowable-engine-common/src/test/java/org/flowable/common/engine/impl/util/DbUtilTest.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/test/java/org/flowable/common/engine/impl/util/DbUtilTest.java) *(New File)*
- [`modules/flowable-engine-common/src/test/java/org/flowable/common/engine/impl/db/SpannerEngineConfigurationTest.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/test/java/org/flowable/common/engine/impl/db/SpannerEngineConfigurationTest.java) *(New File)*

## Details of Changes
1. **`DbUtil.java`**:
- Added constant `public static final String DATABASE_TYPE_SPANNER = "spanner";`.
- In `getDefaultDatabaseTypeMappings()`, mapped product name `"Google Cloud Spanner"` to `DATABASE_TYPE_SPANNER`.
- In `determineDatabaseType()`, added fallback recognition checking if database product name contains `"spanner"` or connection URL starts with `jdbc:cloudspanner:` (`JDBC-C8`).
2. **`AbstractEngineConfiguration.java`**:
- Added constant `public static final String DATABASE_TYPE_SPANNER = "spanner";`.
3. **`spanner.properties`**:
- Defined pagination limit clause: `limitAfter=LIMIT #{maxResults} OFFSET #{firstResult}`.
- Defined binary blob type mapping: `blobType=BLOB` (`GSQL-C4`).
- Defined boolean literal value: `boolValue=TRUE`.
4. **`ListQueryParameterObject.java`**:
- Added `DATABASE_TYPE_SPANNER` to native `NULLS FIRST` and `NULLS LAST` order-by branches, ensuring GoogleSQL compliant `order by <col> [asc|desc] NULLS FIRST|LAST` generation without CASE expressions.
5. **`TableDataManagerImpl.java`**:
- In `getTableNameFilter()`, ensured table name filter formats as uppercase `databaseTablePrefix + flowableTablePrefix.toUpperCase(Locale.ROOT) + escape + "_%"` for Spanner.
- In `getDatabaseSchema()`, defaulted schema to empty string `""` when `databaseType` is `spanner` and schema is not configured (`GSQL-C13`).
- In `getTableMetaData()`, normalized table name to uppercase and set on `TableMetaData` before column introspection queries.
6. **`AbstractSqlScriptBasedDbSchemaManager.java`**:
- In `isTablePresent()`, normalized table name to uppercase and defaulted schema to empty string `""` when `databaseType` is `spanner`.

## Decisions & Defaults
- **Schema Name Handling**: Per `GSQL-C13`, Cloud Spanner's default schema in `INFORMATION_SCHEMA` is empty string (`TABLE_SCHEMA = ''`). Schema checks in `TableDataManagerImpl` and `AbstractSqlScriptBasedDbSchemaManager` default to `""` rather than assuming `public` or `null`.
- **Table Name Casing**: GoogleSQL identifiers are case-insensitive in queries but conventionally stored in uppercase metadata in Cloud Spanner; table name filtering and metadata lookups normalize to uppercase.
- **Null Ordering**: GoogleSQL supports standard SQL `NULLS FIRST` and `NULLS LAST`; enabled directly in `ListQueryParameterObject` instead of falling back to synthetic `CASE WHEN ... IS NULL` expressions.

## Verification & Test Status
- Authored unit test suite:
- `DbUtilTest`: verifies `DATABASE_TYPE_SPANNER` constant, default mappings, product name matching, and fallback recognition via URL and product name patterns.
- `SpannerEngineConfigurationTest`: verifies `AbstractEngineConfiguration.DATABASE_TYPE_SPANNER`, loading of `spanner.properties`, `ListQueryParameterObject` nulls-first / nulls-last order-by clauses, `AbstractSqlScriptBasedDbSchemaManager.isTablePresent()`, and `TableDataManagerImpl` metadata queries.
- Executed TDD cycle:
- Red phase: tests failed with missing symbols and unhandled Spanner dialect branches.
- Green phase: all unit tests passed cleanly (`10/10` passed).
- Executed full module test run:
- `./mvnw test -pl modules/flowable-engine-common`: 987 tests run, 0 failures, 0 errors, 65 skipped.
- Executed Checkstyle audit:
- `./mvnw checkstyle:check -pl modules/flowable-engine-common`: 0 Checkstyle violations.
44 changes: 44 additions & 0 deletions .sam/changes/unit-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Unit 3: Transaction Abort Handling & Spanner Retry Interceptor - Change Summary

## Overview
Implemented Cloud Spanner transaction abort retry handling via `SpannerRetryInterceptor` and configured `StrongUuidGenerator` as the default identifier generator for `ProcessEngineConfigurationImpl` when using Cloud Spanner, avoiding primary key write hotspotting on Spanner root splits.

## Modified and Created Files
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/interceptor/SpannerRetryInterceptor.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/interceptor/SpannerRetryInterceptor.java) *(New File)*
- [`modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java)
- [`modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java)
- [`modules/flowable-engine-common/src/test/java/org/flowable/common/engine/impl/interceptor/SpannerRetryInterceptorTest.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine-common/src/test/java/org/flowable/common/engine/impl/interceptor/SpannerRetryInterceptorTest.java) *(New File)*
- [`modules/flowable-engine/src/test/java/org/flowable/engine/test/cfg/SpannerProcessEngineConfigurationTest.java`](file:///usr/local/google/home/vaibhavch/sam-teamfood/flowable-engine/modules/flowable-engine/src/test/java/org/flowable/engine/test/cfg/SpannerProcessEngineConfigurationTest.java) *(New File)*

## Details of Changes
1. **`SpannerRetryInterceptor.java`**:
- Modeled after `CrDbRetryInterceptor` with configurable retry count (`nrRetries = 3`), initial wait time (`waitTime = 50ms`), exponential multiplier (`waitTimeIncrease = 2`), and corresponding getters and setters.
- Implemented `isTransactionRetryException(Throwable exception)` with recursive cause-chain inspection, `SQLException.getNextException()` traversal, and cycle protection:
- Identifies Spanner abort indicators via exception class names (`AbortedException`, `AbortedDueToConcurrentModificationException`).
- Detects gRPC status code 10 (`ABORTED`) on `SQLException.getErrorCode()`.
- Detects message content containing `"ABORTED"`.
- Intentionally avoids `SQLState` matching because Cloud Spanner JDBC driver reports `null` for `SQLState` (`JDBC-C2`).
- Re-throws non-retryable exceptions immediately on the first attempt without unnecessary retries.
2. **`AbstractEngineConfiguration.java`**:
- In `getDefaultCommandInterceptors()`, registered `SpannerRetryInterceptor` into the command interceptor chain when `DATABASE_TYPE_SPANNER.equals(databaseType)`.
3. **`ProcessEngineConfigurationImpl.java`**:
- In `initIdGenerator()`, defaulted to `idGenerator = new StrongUuidGenerator()` when `DATABASE_TYPE_SPANNER.equals(databaseType)`.
- Preserves custom user-provided `IdGenerator` instances when configured.
- Prevents sequential integer block allocation (`DbIdGenerator`) from creating write hotspots on Spanner's split boundary root ranges (`GSQL-C1`, `SPN-D7`).

## Decisions & Defaults
- **Exception Classification by gRPC Ordinal and Status Name**: In accordance with `JDBC-C2` and `JDBC-C4`, Cloud Spanner errors surface as `JdbcSqlException` wrapping `SpannerException` with null `SQLState` and gRPC status ordinal (10 for `ABORTED`). `isTransactionRetryException` matches error code 10, `"ABORTED"` message text, and Spanner exception class names rather than SQLState 40001.
- **Backoff Configuration**: Defaulted to `nrRetries = 3`, `waitTime = 50ms`, `waitTimeIncrease = 2` with full programmatic access via setters/getters.
- **UUID Primary Key Generation**: `StrongUuidGenerator` was adopted as the default ID generator for `ProcessEngine` on Spanner, bringing it into parity with all other Flowable engines (CMMN, DMN, Form, IDM, App, Event Registry) which already use `StrongUuidGenerator` by default via `AbstractEngineConfiguration`.

## Verification & Test Status
- Authored unit test suites:
- `SpannerRetryInterceptorTest` in `modules/flowable-engine-common`: 14 tests verifying retry on simulated Spanner abort exceptions, exponential backoff, retry exhaustion, immediate re-throw of non-retryable exceptions, full cause chain and `SQLException.getNextException()` traversal, property getters/setters, and interceptor chain inclusion.
- `SpannerProcessEngineConfigurationTest` in `modules/flowable-engine`: 3 tests verifying `StrongUuidGenerator` default on Spanner, `DbIdGenerator` default on H2, and preservation of custom `IdGenerator` on Spanner.
- TDD cycle:
- Red phase: test compilation failed due to missing `SpannerRetryInterceptor` class in `flowable-engine-common`, and runtime error confirmed `DbIdGenerator` failure on Spanner in `flowable-engine`.
- Green phase: all tests passed cleanly following implementation.
- Executed tests:
- `./mvnw test -pl modules/flowable-engine-common,modules/flowable-engine -am -Dtest=*Spanner*,*Retry* -Dsurefire.failIfNoSpecifiedTests=false`: All 10 tests passed (0 failures, 0 errors).
- Executed Checkstyle:
- `./mvnw checkstyle:check -pl modules/flowable-engine-common,modules/flowable-engine`: 0 Checkstyle violations in both modules.
Loading