Install
$ agentstack add skill-wfukatsu-nexus-architect-migrate-oracle-aq-to-scalardb ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Migrate Oracle Stored Procedures & Triggers to AQ + ScalarDB Consumer Skill
Purpose
Convert Oracle triggers and stored procedures into an event-driven architecture using Oracle Advanced Queuing (AQ) as the messaging layer and ScalarDB Java Transaction API as the consumer layer.
Producer side (Oracle SQL): Triggers and stored procedures are replaced with AQ enqueue operations — triggers contain no business logic and simply call enqueue SPs.
Consumer side (Java): Message consumer classes dequeue from AQ using JMS and write to ScalarDB-managed tables using the Java Transaction API.
This skill produces:
- A complete SQL file (
aq_setup.sql) for the Oracle producer side - Java consumer files for the ScalarDB consumer side
- An AQ migration report documenting all conversions
Skill Responsibility
This skill is responsible for:
- Analyzing triggers and stored procedures from extracted schema JSON
- Determining which triggers/SPs should be converted to AQ enqueue patterns
- Generating SQL for payload types, queue tables, queues, modified triggers, and enqueue SPs
- Generating Java consumer service classes (dequeue + ScalarDB Transaction API)
- Generating message POJO classes and helper utilities
- Producing an AQ migration report
This skill is NOT responsible for:
- Orchestration or command handling (handled by
/oracle-to-scalardbcommand) - Schema extraction (handled by Subagent 1)
- Schema report generation (handled by Subagent 2)
- General migration analysis (handled by Subagent 3)
- Direct SP/trigger to Java conversion without AQ (handled by Subagent 5)
- Creating a full consumer application (build files, main class, deployment config)
Input Contract
| Input | Type | Required | Description | |-------|------|----------|-------------| | raw_schema_data.json | File | YES | Extracted Oracle schema data (specifically the plsql section) | | oracle_schema_report.md | File | YES | Schema report (for table/column context needed for accurate Key builders) | | aq-migration-strategy-guide.md | File | YES | Reference doc with AQ conversion patterns and code examples | | aq-exception-handling-strategy.md | File | YES | Exception classification and retry/commit strategy for consumer error handling | | output_directory | Directory | YES | Where to write generated files |
Output Contract
| Output | Location | Description | |--------|----------|-------------| | AQ Setup SQL | /aq_setup.sql | Complete SQL file: payload types, queues, triggers, enqueue SPs | | Java Consumer Classes | /generated-java/Consumer.java | One consumer per queue/message-type | | Java Message POJOs | /generated-java/Message.java | One POJO per payload type | | Java Helper Utility | /generated-java/AqStructHolder.java | Reusable Oracle STRUCT wrapper for ojdbc11 | | Exception Classifier | /generated-java/ExceptionClassifier.java | Classifies exceptions into RETRIABLE / NONRETRIABLE / UNKNOWNTX_STATE for AQ session handling | | AQ Migration Report | /scalardb_aq_migration_report.md | Report documenting all conversions |
How to Parse the JSON
Read raw_schema_data.json and extract these sections from plsql:
| JSON Path | Contains | |-----------|----------| | plsql.procedures | Procedure metadata (name, deterministic, parallel, authid) | | plsql.functions | Function metadata (name, return_type, deterministic) | | plsql.packages | Package metadata (name, authid, spec/body status) | | plsql.triggers | Trigger metadata (name, table, timing, event, status) | | plsql.arguments | Parameters for procedures/functions | | plsql.source | PL/SQL source code (grouped by NAME, TYPE, ordered by LINE) | | plsql.trigger_source | Trigger body source code | | plsql.procedure_ddl | Full DDL for procedures | | plsql.function_ddl | Full DDL for functions | | plsql.trigger_ddl | Full DDL for triggers |
Also read oracle_schema_report.md to extract:
- Table names and their columns with data types
- Primary key definitions (needed to build correct
Key.of*()calls) - Foreign key relationships (needed for multi-table operations)
Conversion Decision Logic
Which triggers/SPs get converted to AQ?
Analyze each trigger and stored procedure to determine if it should be converted to AQ:
| Object | Convert to AQ? | Rationale | |--------|---------------|-----------| | Trigger with DML (INSERT/UPDATE/DELETE on another table) | YES | The DML becomes a consumer-side ScalarDB operation | | Trigger that calls an SP doing DML | YES | Both trigger and SP are converted | | Trigger with only validation/defaults (no cross-table DML) | NO | Keep as application-layer validation | | SP that INSERTs/UPDATEs/DELETEs records | YES | The DML moves to the consumer | | SP with only SELECT/computation | NO | Convert to direct Java (Subagent 5 handles) | | SP called by a trigger | YES | Becomes the enqueue SP; trigger just calls it |
Trigger conversion rules
- If a trigger has NO business logic (just calls an SP): The trigger calls the enqueue SP with appropriate parameters. The business logic lives in the consumer.
- If a trigger HAS business logic: Extract the logic. The trigger calls an enqueue SP passing all needed data (OLD/NEW values). The consumer Java code implements the business logic.
- Original triggers are DISABLED — new triggers replace them.
- Preserve the original trigger structure — do NOT split a single trigger into multiple triggers. If the original trigger fires on multiple events (e.g.,
UPDATE OF job_id, department_id), the AQ replacement MUST be a single trigger with the same event specification. The replacement trigger should call the enqueue SP once, passing all relevant OLD/NEW values.
Stored procedure conversion rules
- SPs that do DML: Converted to enqueue SPs (
DBMS_AQ.ENQUEUEwithON_COMMITvisibility). The actual DML moves to the consumer. - SPs called by triggers: Become enqueue SPs. The trigger just calls the enqueue SP.
- SPs with only SELECT/computation: Not converted to AQ (handled by Subagent 5 as direct Java).
SQL Generation Rules
1. Payload Type
Create one Oracle Object Type per distinct message schema. Include all data the consumer needs:
CREATE OR REPLACE TYPE . AS OBJECT (
-- Include all columns the consumer needs to write
-- Include operation_type VARCHAR2(20) as a routing key
,
...
operation_type VARCHAR2(20) -- routing key: identifies what the consumer should do
);
Naming convention: _change_t (e.g., job_history_change_t)
2. Queue Table
BEGIN
DBMS_AQADM.CREATE_QUEUE_TABLE(
queue_table => '.',
queue_payload_type => '.'
);
END;
/
Naming convention: _qt (e.g., job_history_qt)
3. Queue
BEGIN
DBMS_AQADM.CREATE_QUEUE(
queue_name => '.',
queue_table => '.',
max_retries => 5,
retry_delay => 0
);
END;
/
BEGIN
DBMS_AQADM.START_QUEUE('.');
END;
/
BEGIN
DBMS_AQADM.GRANT_QUEUE_PRIVILEGE(
privilege => 'ALL',
queue_name => '.',
grantee => ''
);
END;
/
Naming convention: _queue (e.g., job_history_queue)
4. Enqueue Stored Procedures
CREATE OR REPLACE PROCEDURE .SP_ENQUEUE_ (
) AS
l_enq_opts DBMS_AQ.ENQUEUE_OPTIONS_T;
l_msg_props DBMS_AQ.MESSAGE_PROPERTIES_T;
l_payload ;
l_msgid RAW(16);
BEGIN
l_payload := (
=> ,
...
operation_type => '' -- routing key
);
l_enq_opts.visibility := DBMS_AQ.ON_COMMIT;
DBMS_AQ.ENQUEUE(
queue_name => '.',
enqueue_options => l_enq_opts,
message_properties => l_msg_props,
payload => l_payload,
msgid => l_msgid
);
END SP_ENQUEUE_;
/
5. Modified Triggers
IMPORTANT: Preserve the original trigger structure. Do NOT split a single trigger into multiple triggers. If the original trigger fires on UPDATE OF col1, col2, the replacement trigger MUST use the same event specification as a single trigger.
-- Disable original trigger
ALTER TRIGGER . DISABLE;
-- New trigger: no business logic, just calls the enqueue SP
-- Preserve the SAME event specification as the original trigger
CREATE OR REPLACE TRIGGER .TRG_AQ_
ON .
FOR EACH ROW
BEGIN
SP_ENQUEUE_(
p_col1 => :OLD.col1, -- or :NEW.col1 depending on timing
p_col2 => :NEW.col2,
...
);
END TRG_AQ_;
/
SQL File Structure
The generated aq_setup.sql MUST be organized in this order:
-- =============================================================================
-- Oracle AQ Setup for Schema
-- Generated:
-- =============================================================================
-- Section 1: Payload Type Definitions
-- Section 2: Queue Table Creation
-- Section 3: Queue Creation & Configuration
-- Section 4: Disable Original Triggers
-- Section 5: Enqueue Stored Procedures
-- Section 6: New AQ Triggers
-- Section 7: Verification Queries
Note: Keep the AQ setup SQL minimal — only create the necessary payload types, queues, triggers, and enqueue SPs. Avoid unnecessary idempotent cleanup blocks unless the script is designed to be re-runnable.
Java Consumer Generation Rules
Target Java Version: 17
All generated Java files MUST target Java 17. Use Java 17 features where appropriate: var, records, instanceof pattern matching, switch expressions, text blocks, List.of(), String.formatted().
Do NOT use preview features or anything requiring Java 21+.
Required Dependencies (document in report)
The following JAR files are required for AQ consumer functionality:
| Dependency | Source | Notes | |------------|--------|-------| | aqapi.jar | Oracle DB ($ORACLE_HOME/rdbms/jlib/aqapi.jar) | Must be extracted from Oracle DB installation or container | | javax.jms-api-2.0.1.jar | Maven Central or Oracle DB | JMS 2.0 API | | ojdbc11-23.x.jar | Maven Central (com.oracle.database.jdbc:ojdbc11) | Oracle JDBC driver | | scalardb-3.17.x.jar | Maven Central (com.scalar-labs:scalardb) | ScalarDB Core (Transaction API) |
Note: ScalarDB Core is the default component (open source/community edition). Only add ScalarDB Cluster dependencies if the SQL interface is needed. For the Java Transaction API used by these consumers, ScalarDB Core is sufficient.
Note: aqapi.jar is NOT available in Maven Central for most versions. It must be obtained from inside the Oracle DB installation directory and added to the project's libs/ folder.
File Naming
- Consumer classes:
Consumer.java(PascalCase) - Example:
job_history_queue→JobHistoryQueueConsumer.java - Message POJOs:
Message.java(PascalCase) - Example:
job_history_change_t→JobHistoryChangeMessage.java - Helper:
AqStructHolder.java(always this name)
IMPORTANT: ALL files listed in the Output Contract MUST actually be written to disk. The Generated File Index in the report MUST match the actual files in generated-java/. Do not reference files that were not written.
AqStructHolder (generate once)
package com.example.scalardb.aq;
import oracle.sql.Datum;
import oracle.sql.ORAData;
import oracle.sql.STRUCT;
/**
* Wraps Oracle STRUCT for ojdbc11 compatibility.
* Required because oracle.sql.STRUCT no longer implements ORAData directly in ojdbc11.
*
* Java version: 17
* Generated by ScalarDB AQ Migration Skill
*/
public class AqStructHolder implements ORAData {
private final Datum datum;
public AqStructHolder(Datum datum) {
this.datum = datum;
}
public STRUCT asStruct() {
return (STRUCT) datum;
}
@Override
public Datum toDatum(java.sql.Connection conn) {
return datum;
}
}
Message POJO (one per payload type)
package com.example.scalardb.aq;
/**
* Message POJO for Oracle AQ payload type:
* Maps to Oracle Object Type attributes by positional index.
*
* Java version: 17
* Generated by ScalarDB AQ Migration Skill
*/
public class Message {
private ; // [0]
...
private String operationType; // [N] operation_type (routing key)
// Getters and setters for all fields
}
Consumer Service Class (one per queue)
package com.example.scalardb.aq;
import com.scalar.db.api.*;
import com.scalar.db.io.Key;
import com.scalar.db.exception.transaction.*;
import oracle.jms.*;
import oracle.sql.STRUCT;
import javax.jms.*;
import java.math.BigDecimal;
import java.util.*;
/**
* AQ Consumer for queue:
* Dequeues messages and processes them using ScalarDB Java Transaction API.
*
* Original triggers/SPs converted:
* -
*
* Java version: 17
* Generated by ScalarDB AQ Migration Skill
*/
public class Consumer {
private static final String NAMESPACE = "";
private static final String QUEUE_OWNER = "";
private static final String QUEUE_NAME = "";
private static final long RECEIVE_TIMEOUT_MS = 10_000;
private final DistributedTransactionManager txManager;
public Consumer(DistributedTransactionManager txManager) {
this.txManager = txManager;
}
/**
* Processes a single dequeued message using ScalarDB Transaction API.
* Routes to the appropriate handler based on operation_type.
*
* @param message the parsed message POJO
* @throws TransactionException if ScalarDB transaction fails
*/
public void processMessage(Message message) throws TransactionException {
switch (message.getOperationType()) {
case "" -> handle(message);
case "" -> handle(message);
default -> throw new IllegalArgumentException(
"Unknown operation type: " + message.getOperationType());
}
}
// --- Handler methods (one per operation_type) ---
private void handle(Message msg) throws TransactionException {
DistributedTransaction tx = txManager.begin();
try {
// ScalarDB write logic — Upsert recommended for idempotency
var upsert = Upsert.newBuilder()
.namespace(NAMESPACE)
.table("")
.partitionKey(Key.of("", msg.get()))
// .clusteringKey(...) if applicable
// .xxxValue("", msg.get()) for each column
.build();
tx.upsert(upsert);
tx.commit();
} catch (Exception e) {
tx.abort();
throw e;
}
}
// --- AQ JMS message parsing ---
/**
* Parses an Oracle ADT message (STRUCT) into a message POJO.
* Attribute positions match the Oracle Object Type definition order.
*/
public static Message parseMessage(Message jmsMessage) throws Exception {
var adtMsg = (AQjmsAdtMessage) jmsMessage;
var holder = (AqStructHolder) adtMsg.getAdtPayload();
Object[] attrs = holder.asStruct().getAttributes();
var msg = new Message();
// Map each positional attribute to the POJO field
// msg.setField(() attrs[index]);
return msg;
}
}
ScalarDB Write Patterns
Upsert (recommended for idempotency):
var upsert = Upsert.newBuilder()
.namespace(NAMESPACE)
.table("")
.partitionKey(Key.of("", value))
.clusteringKey(Key.of("", value))
.Value("", value)
.build();
tx.upsert(upsert);
Using Upsert is recommended because it provides idempotency during message redelivery: if the consumer crashes after the ScalarDB commit but before the AQ session commit, AQ redelivers the message and the Upsert simply overwrites with identical data. However, Upsert is not mandatory — Insert can be used if duplicate handling is managed differently.
Insert (alternative):
var insert =
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [wfukatsu](https://github.com/wfukatsu)
- **Source:** [wfukatsu/nexus-architect](https://github.com/wfukatsu/nexus-architect)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.