chore: publish from main

This commit is contained in:
github-actions[bot]
2026-08-11 02:48:20 +00:00
parent dab2d38097
commit be857f7808
28 changed files with 1057 additions and 136 deletions
@@ -45,7 +45,7 @@ Present the classified list. Let the user adjust classifications or migration or
**Step 4: Write the plan file**
Save to: `.github/oracle-to-postgres-migration/Reports/Master Migration Plan.md`
Save to: `.github/oracle-to-postgres-migration/Reports/MasterMigrationPlan.md`
Use this exact template — downstream consumers depend on the structure:
@@ -57,6 +57,11 @@ Use this exact template — downstream consumers depend on the structure:
**Created:** {timestamp}
**Last Updated:** {timestamp}
## DDL Artifacts
**Location:** {path to DDL artifacts, e.g., `.github/oracle-to-postgres-migration/DDL/`}
**External tool used:** {Yes / No} — {If Yes, name the tool (e.g., `ora2pg`) and note that Phase 4 (Schema & DDL Migration) can be skipped; PostgreSQL DDL artifacts already exist.}
## Solution Summary
| Metric | Count |
@@ -40,4 +40,6 @@ Use the template in [references/BUG-REPORT-TEMPLATE.md](references/BUG-REPORT-TE
## Filename Convention
Save bug reports as `BUG_REPORT_<DescriptiveSlug>.md` where `<DescriptiveSlug>` is a short PascalCase identifier (e.g., `EmptyStringNullHandling`, `RefCursorUnwrapFailure`).
Save bug reports to `.github/oracle-to-postgres-migration/Reports/{ProjectName}/BUG_REPORT_<DescriptiveSlug>.md` where:
- `{ProjectName}` is the project's assembly/folder name with spaces normalized to `-` (e.g. `MyApp.DataAccess`)
- `<DescriptiveSlug>` is a short PascalCase identifier describing the defect (e.g., `EmptyStringNullHandling`, `RefCursorUnwrapFailure`)
@@ -1,11 +1,11 @@
---
name: creating-oracle-to-postgres-migration-integration-tests
description: 'Creates integration test cases for .NET data access artifacts during Oracle-to-PostgreSQL database migrations. Generates DB-agnostic xUnit tests with deterministic seed data that validate behavior consistency across both database systems. Use when creating integration tests for a migrated project, generating test coverage for data access layers, or writing Oracle-to-PostgreSQL migration validation tests.'
description: 'Creates integration test cases targeting Oracle for .NET data access artifacts. Tests capture Oracle expected behavior as the authoritative baseline; they are written once and later ported to PostgreSQL by migrating the test project in Phase 6. Use only during Phase 3, before any PostgreSQL migration work has begun. Do not invoke during Phase 6 or against a project that has already been migrated.'
---
# Creating Integration Tests for Oracle-to-PostgreSQL Migration
Generates integration test cases for data access artifacts in a single target project. Tests validate behavior consistency when running against Oracle or PostgreSQL.
Generates integration test cases for data access artifacts in a single target project. Tests target Oracle and capture its behavior as the authoritative baseline. They are written to be logically portable — so they can survive Phase 6 migration without rewriting — but they do not run against PostgreSQL at this stage.
## Prerequisites
@@ -34,7 +34,6 @@ Scope to the target project only. List data access methods that interact with th
**Step 3: Create seed data**
- Follow seed file location and naming conventions from the existing project.
- Reuse existing seed files when possible.
- Avoid `TRUNCATE TABLE` — keep existing database data intact.
- Assume existing business rows and lookup rows are already present; add only minimal, collision-safe seed records needed for the scenario.
- Do not commit seed data; tests run in transactions that roll back.
@@ -51,7 +50,7 @@ Scope to the target project only. List data access methods that interact with th
- Avoid testing code paths that do not exist or asserting behavior that cannot occur.
- Avoid redundant assertions across tests targeting the same method.
- For text parameters, include both empty-string and `NULL`/missing input coverage where applicable.
- For datetime behavior, include explicit timezone-sensitive assertions when methods write/read `timestamp without time zone` or `timestamp(0)` targets.
- For datetime behavior, include assertions that validate the value written and read back matches — use the Oracle column's precision (e.g., seconds-only for a date/time column with no fractional seconds) rather than assuming any particular database type syntax.
**Step 5: Review determinism**
@@ -59,8 +58,9 @@ Re-examine every assertion against non-null values. Confirm each is deterministi
## Key Constraints
- **Phase 3 only** — these tests target Oracle. Do not invoke this skill during Phase 6 or against a PostgreSQL-targeting project.
- **Oracle is the golden source** — tests capture Oracle's expected behavior.
- **DB-agnostic assertions** — no platform-specific error messages or syntax in assertions.
- **Seed only against Oracle** — test project will be migrated to PostgreSQL later.
- **Assertion portability** — avoid platform-specific error messages or syntax in assertions so that when the test project is migrated to PostgreSQL in Phase 6, assertions require no changes.
- **Seed only against Oracle** — the test project will be migrated to PostgreSQL in Phase 6; seed data and infrastructure stay Oracle-targeted until then.
- **Scoped to one project** — do not create tests for artifacts outside the target project.
- **Preserve existing data** — never rewrite or wipe pre-existing business or lookup rows.
@@ -0,0 +1,137 @@
---
name: migrating-oracle-to-postgres-data-access-code
description: 'Migrates .NET/C# data access code from Oracle to PostgreSQL (Npgsql). Replaces Oracle NuGet packages, rewrites OracleConnection/OracleCommand/OracleDataReader usage, fixes DbType mappings, updates stored procedure invocation patterns, and adapts connection string configuration. Use when migrating the application code layer of a .NET project during an Oracle-to-PostgreSQL database migration.'
---
# Migrating .NET Data Access Code from Oracle to PostgreSQL
Migrate the C# data access layer of a single `.Postgres`-copy project from Oracle (Oracle.ManagedDataAccess) to PostgreSQL (Npgsql). Work item by item through `Reports/{ProjectName}/MigrationChecklist.md`.
## Prerequisites
- The `.Postgres` project copy exists (created in Phase 5 setup).
- `Reports/{ProjectName}/MigrationChecklist.md` exists and is the source of truth for what to change.
- `Reports/{ProjectName}/OracleRiskAnalysis.md` exists for cross-referencing behavioral differences.
## Workflow
```
Progress:
- [ ] Step 1: Replace NuGet packages
- [ ] Step 2: Update connection string configuration
- [ ] Step 3: Rewrite ADO.NET type references
- [ ] Step 4: Fix DbType mappings
- [ ] Step 5: Migrate stored procedure invocation
- [ ] Step 6: Address Oracle-specific SQL and syntax
- [ ] Step 7: Build and verify
```
**Step 1: Replace NuGet packages**
In the `.csproj` of the `.Postgres` project:
- Remove: `Oracle.ManagedDataAccess.Core`, `Oracle.EntityFrameworkCore` (and any other `Oracle.*` packages)
- Add: `Npgsql` (for ADO.NET) and/or `Npgsql.EntityFrameworkCore.PostgreSQL` (for EF Core)
- Keep version pinning consistent with the target .NET version; do not introduce newer package versions than what the solution already uses for similar packages.
- If `System.Data` abstractions (`IDbConnection`, `IDbCommand`) are used project-wide, the surface-level code may need fewer changes — identify them first.
**Step 2: Update connection string configuration**
- Locate the Oracle connection string in `appsettings.json`, `appsettings.{env}.json`, `web.config`, `app.config`, or environment variable configuration.
- Replace with a Npgsql-compatible connection string: `Host=localhost;Port=5432;Database=mydb;Username=myuser;Password=mypassword`
- Do not hardcode credentials — use the same configuration mechanism already in use (e.g., environment variables, secrets manager, `IConfiguration`).
- Update any named connection string keys only if they were Oracle-specific (e.g., `OracleConnection`). Prefer keeping the same key name to minimize application config changes.
**Step 3: Rewrite ADO.NET type references**
Replace Oracle-specific ADO.NET types with Npgsql equivalents:
| Oracle type | Npgsql replacement |
|---|---|
| `OracleConnection` | `NpgsqlConnection` |
| `OracleCommand` | `NpgsqlCommand` |
| `OracleDataReader` | `NpgsqlDataReader` |
| `OracleDataAdapter` | `NpgsqlDataAdapter` |
| `OracleParameter` | `NpgsqlParameter` |
| `OracleTransaction` | `NpgsqlTransaction` |
| `OracleException` | `NpgsqlException` |
| `OracleDbType` | `NpgsqlDbType` (from `NpgsqlTypes` namespace) |
Update `using` directives accordingly (`using Oracle.ManagedDataAccess.Client``using Npgsql`).
If the codebase uses `IDbConnection`/`IDbCommand` abstractions registered via DI, update only the DI registration and connection string — the consuming code may not need changes.
**Step 4: Fix DbType and NpgsqlDbType mappings**
Oracle parameter types do not map 1:1 to Npgsql. Review every `OracleParameter` (now `NpgsqlParameter`) that sets an explicit type:
| Oracle type | Notes |
|---|---|
| `OracleDbType.Varchar2` | Use `NpgsqlDbType.Varchar` or omit (Npgsql infers from value) |
| `OracleDbType.Clob` | Use `NpgsqlDbType.Text` |
| `OracleDbType.Number` | Use `NpgsqlDbType.Numeric` or `NpgsqlDbType.Integer` depending on precision |
| `OracleDbType.Date` | Use `NpgsqlDbType.Date` (date only) or `NpgsqlDbType.Timestamp` (if time component used) |
| `OracleDbType.TimeStamp` | Use `NpgsqlDbType.Timestamp` |
| `OracleDbType.RefCursor` | Use `NpgsqlDbType.Refcursor` — see Step 5 |
| `OracleDbType.Char` | Use `NpgsqlDbType.Char` |
For parameters where Oracle inferred the type from the value, Npgsql also infers — explicit type setting is often unnecessary and can be removed.
**Step 5: Migrate stored procedure invocation**
Oracle and PostgreSQL stored procedure invocation differ significantly:
- **Command type**: Retain `CommandType.StoredProcedure` for function calls. For procedures that use `OUT` parameters, PostgreSQL requires `CommandType.Text` with `CALL proc_name(...)` syntax in some versions of Npgsql — verify against the target Npgsql version.
- **RefCursor handling**: Oracle returns ref cursors as output parameters; PostgreSQL returns them differently:
- For `RETURNS TABLE` / `RETURNS SETOF`, use `ExecuteReader()` directly — no cursor parameter needed.
- For `RETURNS refcursor`, call within a transaction, read the cursor name from the output parameter, then issue `FETCH ALL IN "<cursor_name>"`.
- Remove any Oracle-specific cursor-wrapping code (e.g., `OracleRefCursor`).
- **OUT parameters**: PostgreSQL stored procedures use `INOUT` or function return values. Verify parameter direction matches the migrated procedure signature.
- **Sequence `NEXTVAL`**: Replace `SELECT {SEQUENCE}.NEXTVAL FROM DUAL` with `SELECT nextval('{sequence_name}')`.
- **Named parameters**: Npgsql uses `@param_name`; Oracle used `:param_name`. Update all parameter name prefixes.
**Step 6: Address Oracle-specific SQL and C# patterns**
Review inline SQL strings and query builders for Oracle-specific constructs and replace:
| Oracle construct | PostgreSQL replacement |
|---|---|
| `ROWNUM <= n` | `LIMIT n` |
| `ROWNUM = 1` | `LIMIT 1` |
| `NVL(x, y)` | `COALESCE(x, y)` |
| `DECODE(expr, v1, r1, ...)` | `CASE WHEN expr = v1 THEN r1 ... END` |
| `SYSDATE` / `SYSTIMESTAMP` | `NOW()` or `CURRENT_TIMESTAMP` |
| `TO_CHAR(date, fmt)` | `TO_CHAR(date, fmt)` (mostly compatible; verify format strings) |
| `TO_DATE(str, fmt)` | `TO_DATE(str, fmt)` (verify format strings) |
| `TO_NUMBER(str)` | `CAST(str AS NUMERIC)` or `str::NUMERIC` |
| `||` string concat | `||` (compatible) |
| `DUAL` table | Remove `FROM DUAL`; PostgreSQL evaluates `SELECT expr` without a table |
| `CONNECT BY` hierarchy | Rewrite using recursive CTEs (`WITH RECURSIVE`) |
| `MERGE INTO` | Rewrite as `INSERT ... ON CONFLICT DO UPDATE` |
| Empty string `''` as NULL | Oracle treats `''` as NULL; PostgreSQL does not — check comparisons and `IS NULL` guards |
| `VARCHAR2` | `VARCHAR` or `TEXT` |
**Step 7: Build and verify**
After addressing all checklist items:
1. Run `dotnet build` on the `.Postgres` project. Fix any remaining compilation errors.
2. Verify no Oracle-specific namespaces remain: search for `Oracle.ManagedDataAccess`, `OracleConnection`, `OracleCommand`, `:param` patterns.
3. Mark completed items in `Reports/{ProjectName}/MigrationChecklist.md`.
## EF Core projects
If the project uses `Oracle.EntityFrameworkCore`:
- Replace the provider registration in `DbContext` configuration: `.UseOracle(...)``.UseNpgsql(...)`
- Replace `OracleDbContextOptionsBuilder` references.
- Review `OnModelCreating` for Oracle-specific configurations (e.g., `HasColumnType("NUMBER")``HasColumnType("numeric")`).
- Sequence configuration: `modelBuilder.HasSequence<int>("seq_name").StartsAt(1).IncrementsBy(1)` syntax is compatible; verify column defaults referencing sequences.
- Do not run EF Core migrations — schema is managed externally via DDL scripts (Phase 4).
## Key Constraints
- Work only within the `.Postgres` copy — never modify the original Oracle-targeting project.
- Keep to existing .NET and C# versions; do not introduce newer language or runtime features.
- Preserve comments and application logic; change only what is necessary for PostgreSQL compatibility.
- Oracle is the source of truth — behavioral differences must be documented as bug reports, not silently altered.
@@ -39,8 +39,10 @@ Apply these translation rules:
- Treat `UNION ALL` as a review checkpoint. Validate plan quality per branch and restructure if combined-branch planning causes regressions (for example, unexpected sequential scans on large tables).
- Leverage the `orafce` extension when it improves clarity or fidelity.
Consult the PostgreSQL table/view definitions at `.github/oracle-to-postgres-migration/DDL/Postgres/Tables and Views/` for target schema details.
Consult the PostgreSQL table/view definitions at `.github/oracle-to-postgres-migration/DDL/Postgres/{ProjectName}/Tables and Views/` for target schema details.
**Step 3: Write the migrated procedure to Postgres output directory**
Place each migrated procedure in its own file under `.github/oracle-to-postgres-migration/DDL/Postgres/Procedures and Functions/{PACKAGE_NAME_IF_APPLICABLE}/`. One procedure per file.
Place each migrated procedure in its own file under `.github/oracle-to-postgres-migration/DDL/Postgres/{ProjectName}/Procedures and Functions/{PACKAGE_NAME_IF_APPLICABLE}/`. One procedure per file.
> `{ProjectName}` is the project's assembly/folder name with spaces normalized to `-` (e.g. `MyApp.DataAccess`). This matches the path used by the agent and other migration skills.
@@ -4,7 +4,10 @@
| --- | --- |
| [empty-strings-handling.md](empty-strings-handling.md) | Oracle treats '' as NULL; PostgreSQL keeps empty strings distinct—patterns to align behavior in code, tests, and migrations. |
| [no-data-found-exceptions.md](no-data-found-exceptions.md) | Oracle SELECT INTO raises "no data found"; PostgreSQL doesnt—add explicit NOT FOUND handling to mirror Oracle behavior. |
| [oracle-nvl-decode-functions.md](oracle-nvl-decode-functions.md) | Oracle NVL/NVL2/DECODE have no direct equivalents—replace with COALESCE, CASE WHEN, and NULL-safe comparisons. |
| [oracle-parentheses-from-clause.md](oracle-parentheses-from-clause.md) | Oracle allows `FROM(TABLE_NAME)` syntax; PostgreSQL requires `FROM TABLE_NAME`—remove unnecessary parentheses around table names. |
| [oracle-rownum-pagination.md](oracle-rownum-pagination.md) | Oracle ROWNUM is assigned before ORDER BY; replace top-N and pagination patterns with LIMIT/OFFSET. |
| [oracle-sysdate-sequences-dual.md](oracle-sysdate-sequences-dual.md) | Replace SYSDATE/SYSTIMESTAMP with NOW(), sequence .NEXTVAL with nextval('seq'), and remove FROM DUAL. |
| [oracle-to-postgres-sorting.md](oracle-to-postgres-sorting.md) | How to preserve Oracle-like ordering in PostgreSQL using COLLATE "C" and DISTINCT wrapper patterns. |
| [oracle-to-postgres-to-char-numeric.md](oracle-to-postgres-to-char-numeric.md) | Oracle allows TO_CHAR(numeric) without format; PostgreSQL requires format string—use CAST(numeric AS TEXT) instead. |
| [oracle-to-postgres-type-coercion.md](oracle-to-postgres-type-coercion.md) | PostgreSQL strict type checks vs. Oracle implicit coercion—fix comparison errors by quoting or casting literals. |
@@ -0,0 +1,79 @@
# Oracle to PostgreSQL: NVL, DECODE, and Null-Handling Functions
## Problem
Oracle provides several functions — `NVL`, `NVL2`, `DECODE` — that have no direct equivalents in standard SQL but are commonly used in Oracle stored procedures and inline SQL. PostgreSQL uses standard SQL alternatives: `COALESCE`, `NULLIF`, and `CASE` expressions.
## Behavior Comparison
### NVL
**Oracle:** `NVL(expr, replacement)` — returns `replacement` if `expr` is `NULL`, otherwise `expr`.
**PostgreSQL:** Use `COALESCE(expr, replacement)` — semantically identical for two arguments.
```sql
-- Oracle
NVL(column_name, 'default')
-- PostgreSQL
COALESCE(column_name, 'default')
```
### NVL2
**Oracle:** `NVL2(expr, not_null_val, null_val)` — returns `not_null_val` if `expr` IS NOT NULL, `null_val` if NULL.
**PostgreSQL:** No direct equivalent — use `CASE`:
```sql
-- Oracle
NVL2(column_name, 'has value', 'no value')
-- PostgreSQL
CASE WHEN column_name IS NOT NULL THEN 'has value' ELSE 'no value' END
```
### DECODE
**Oracle:** `DECODE(expr, search1, result1, search2, result2, ..., default)` — equality-based switch.
**PostgreSQL:** No `DECODE` function — use `CASE WHEN`:
```sql
-- Oracle
DECODE(status, 1, 'Active', 2, 'Inactive', 'Unknown')
-- PostgreSQL
CASE status
WHEN 1 THEN 'Active'
WHEN 2 THEN 'Inactive'
ELSE 'Unknown'
END
```
Note: `DECODE` in Oracle treats two `NULL` values as equal (unlike `=`). If any search value is `NULL`, use `IS NULL` in the `CASE` equivalent:
```sql
-- Oracle: DECODE treats NULL = NULL
DECODE(col, NULL, 'empty', col)
-- PostgreSQL
CASE WHEN col IS NULL THEN 'empty' ELSE col END
```
## Migration Actions
### 1. Stored Procedures
Apply the direct substitutions above. Pay special attention to:
- `NVL` on numeric expressions — `COALESCE` is type-sensitive in PostgreSQL; ensure both arguments are the same type or cast explicitly.
- `DECODE` with `NULL` search values — replace with `IS NULL` guard in the `CASE` expression.
### 2. Application Code (inline SQL strings)
Search C# string literals and query builders for `NVL(`, `NVL2(`, and `DECODE(`. Apply the same substitutions.
### 3. Tests
Write test cases that exercise `NULL` inputs specifically — the Oracle → PostgreSQL translation of `NVL`/`COALESCE` is straightforward, but edge cases around NULL equality in `DECODE``CASE` are a common source of silent behavioral differences.
@@ -0,0 +1,75 @@
# Oracle to PostgreSQL: ROWNUM Pagination vs LIMIT/OFFSET
## Problem
Oracle uses `ROWNUM` pseudo-column for pagination and row-limiting. PostgreSQL uses standard `LIMIT` / `OFFSET` syntax. `ROWNUM` is also fundamentally different in *when* it is assigned, which affects filtering behavior.
## Behavior Comparison
**Oracle:**
- `ROWNUM` is assigned before `ORDER BY` — filtering with `ROWNUM` on an unordered result set is non-deterministic
- Common pattern to get "top N ordered rows" requires a subquery:
```sql
SELECT * FROM (SELECT * FROM t ORDER BY col) WHERE ROWNUM <= 10
```
- `ROWNUM BETWEEN n AND m` requires a double-wrapped subquery
**PostgreSQL:**
- `LIMIT n` restricts result rows after `ORDER BY` is applied — straightforward and deterministic
- `OFFSET n` skips rows; combine with `LIMIT` for pagination
- No `ROWNUM` pseudo-column exists
## Code Example
```sql
-- Oracle: top 10 rows by date
SELECT * FROM (
SELECT * FROM orders ORDER BY created_at DESC
) WHERE ROWNUM <= 10;
-- PostgreSQL equivalent
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
-- Oracle: rows 1120 (keyset pagination via ROWNUM)
SELECT * FROM (
SELECT t.*, ROWNUM rn FROM (
SELECT * FROM orders ORDER BY created_at DESC
) t WHERE ROWNUM <= 20
) WHERE rn > 10;
-- PostgreSQL equivalent
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10 OFFSET 10;
```
## Migration Actions
### 1. Stored Procedures
Replace all `ROWNUM`-based limiting patterns with `LIMIT`/`OFFSET`:
```sql
-- Oracle
WHERE ROWNUM = 1
WHERE ROWNUM <= :n
-- PostgreSQL
LIMIT 1
LIMIT :n -- note: use $n parameter style in PL/pgSQL
```
For subquery wrapping patterns:
```sql
-- Oracle
SELECT * FROM (SELECT ... ORDER BY col) WHERE ROWNUM <= :n
-- PostgreSQL
SELECT ... ORDER BY col LIMIT :n
```
### 2. Application Code (inline SQL strings)
Search for `ROWNUM` in C# string literals, `StringBuilder`, and query-builder methods. Apply the same replacement patterns above.
### 3. Tests
Ensure integration tests validate that result set sizes are correct and that ordering is preserved (i.e., the correct *n* rows are returned, not just any *n* rows).
@@ -0,0 +1,86 @@
# Oracle to PostgreSQL: Date Functions, Sequences, and DUAL
## Problem
Oracle relies on several built-in constructs — `SYSDATE`, `SYSTIMESTAMP`, sequence `NEXTVAL` syntax, and the `DUAL` dummy table — that do not exist in PostgreSQL. Each requires a direct substitution.
## SYSDATE and SYSTIMESTAMP
**Oracle:**
- `SYSDATE` — returns the current date and time (no time zone) as an Oracle `DATE` type
- `SYSTIMESTAMP` — returns the current timestamp with time zone
**PostgreSQL:**
- Use `NOW()` or `CURRENT_TIMESTAMP` for timestamp with time zone
- Use `CURRENT_DATE` for date only
- Use `LOCALTIMESTAMP` for timestamp without time zone (closer to Oracle's `SYSDATE` semantics)
```sql
-- Oracle
SELECT SYSDATE FROM DUAL;
INSERT INTO t (created_at) VALUES (SYSDATE);
-- PostgreSQL
SELECT NOW();
INSERT INTO t (created_at) VALUES (NOW());
-- or, if the column is DATE-only:
INSERT INTO t (created_at) VALUES (CURRENT_DATE);
```
> **Warning:** Oracle `DATE` stores date *and* time; PostgreSQL `DATE` stores date only. If Oracle columns typed as `DATE` carry a time component, the PostgreSQL target column should be `TIMESTAMP`, not `DATE`.
## Sequence NEXTVAL Syntax
**Oracle:**
```sql
SELECT my_sequence.NEXTVAL FROM DUAL;
INSERT INTO t (id) VALUES (my_sequence.NEXTVAL);
```
**PostgreSQL:**
```sql
SELECT nextval('my_sequence');
INSERT INTO t (id) VALUES (nextval('my_sequence'));
```
Key differences:
- PostgreSQL `nextval()` is a function call with the sequence name as a quoted string argument
- Oracle uses dot notation: `sequence_name.NEXTVAL`
- Oracle also has `CURRVAL` → PostgreSQL `currval('sequence_name')`
- If the column uses a `DEFAULT nextval(...)` constraint (set during Phase 4 DDL migration), application code can omit the sequence call entirely and omit the column from the `INSERT`
## DUAL Table
Oracle requires a `FROM DUAL` clause in `SELECT` statements that evaluate expressions without a real table. PostgreSQL does not have `DUAL` — expressions can be selected without a `FROM` clause.
```sql
-- Oracle
SELECT 1 + 1 FROM DUAL;
SELECT SYSDATE FROM DUAL;
SELECT my_sequence.NEXTVAL FROM DUAL;
-- PostgreSQL
SELECT 1 + 1;
SELECT NOW();
SELECT nextval('my_sequence');
```
> **orafce extension:** If `orafce` is installed, it provides a `DUAL` view that makes Oracle-style `FROM DUAL` queries work without changes. This is a useful transitional aid but should not be relied on permanently.
## Migration Actions
### 1. Stored Procedures
- Replace all `SYSDATE` / `SYSTIMESTAMP` references with `NOW()` or `CURRENT_TIMESTAMP` (verify column type — use `LOCALTIMESTAMP` if the target is `TIMESTAMP WITHOUT TIME ZONE`)
- Replace `sequence_name.NEXTVAL` with `nextval('sequence_name')`
- Replace `sequence_name.CURRVAL` with `currval('sequence_name')`
- Remove `FROM DUAL` from all expression-only `SELECT` statements
### 2. Application Code (inline SQL strings)
Search C# string literals for `SYSDATE`, `SYSTIMESTAMP`, `.NEXTVAL`, `.CURRVAL`, and `FROM DUAL`. Apply the same substitutions.
### 3. Tests
- Verify datetime assertions use timezone-safe comparisons (see `oracle-to-postgres-timestamp-timezone.md` for Npgsql-specific behavior)
- Verify sequence-dependent IDs are correctly populated in assertions
@@ -1,6 +1,6 @@
---
name: scaffolding-oracle-to-postgres-migration-test-project
description: 'Scaffolds an xUnit integration test project for validating Oracle-to-PostgreSQL database migration behavior in .NET solutions. Creates the test project, transaction-rollback base class, and seed data manager. Use when setting up test infrastructure before writing migration integration tests, or when a test project is needed for Oracle-to-PostgreSQL validation.'
description: 'Scaffolds an xUnit integration test project targeting Oracle in .NET solutions. Creates the test project, transaction-rollback base class, and seed data manager. Use only during Phase 3, before writing Oracle baseline integration tests. Do not invoke during Phase 6 — the PostgreSQL test project is produced by migrating this project, not by running this skill again.'
---
# Scaffolding an Integration Test Project for Oracle-to-PostgreSQL Migration
@@ -25,7 +25,7 @@ Read the target project's `.csproj` to determine the .NET version and existing p
**Step 2: Create the xUnit test project**
- Target the same .NET version as the application under test.
- Add NuGet packages for Oracle database connectivity and xUnit.
- Add NuGet packages for Oracle database connectivity (`Oracle.ManagedDataAccess.Core`) and xUnit.
- Add a project reference to the target project only — no other application projects.
- Add an `appsettings.json` configured for Oracle database connectivity.
@@ -40,7 +40,6 @@ Read the target project's `.csproj` to determine the .NET version and existing p
- Create a global seed manager for loading test data within the transaction scope.
- Do not commit seed data — transactions roll back after each test.
- Do not use `TRUNCATE TABLE` — preserve existing database data.
- Reuse existing seed files if available.
- Establish a naming convention for seed file location that downstream test creation will follow.
**Step 5: Verify the project compiles**
@@ -49,6 +48,7 @@ Build the test project and confirm it compiles with zero errors before finishing
## Key Constraints
- Oracle is the golden behavior source — scaffold for Oracle first.
- **Phase 3 only** — this skill scaffolds the Oracle-targeting test project. The PostgreSQL test project (Phase 6) is created by copying and migrating this project; do not run this skill again at that point.
- Oracle is the golden behavior source — scaffold for Oracle only, not PostgreSQL.
- Keep to existing .NET and C# versions; do not introduce newer language or runtime features.
- Output is an empty test project with infrastructure only — no test cases.