Reviewing Oracle To Postgres Migration
GitHubOfficialFirst committed by PrimedPaulDistributed by GitHub
- Skill
- Workflow
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a systematic approach to managing database migrations from Oracle to PostgreSQL. It centers on a reference-based workflow that helps developers identify behavioral differences—such as empty string handling, type coercion, and pagination logic—during the planning phase and verify that these risks are addressed during the validation phase. By cross-referencing migration artifacts against known technical discrepancies, it ensures that application logic and database schemas are correctly adapted for the PostgreSQL environment.
What it does
- Provides a risk assessment workflow for planning database migrations.
- Offers a validation workflow to confirm migration work meets PostgreSQL requirements.
- Maintains a library of known behavioral differences between Oracle and PostgreSQL.
- Guides users through documenting risks and recommended fix patterns.
- Ensures integration tests cover critical Oracle-to-PostgreSQL behavioral gaps.
- Supports decision-making for handling specific database object migrations.
When to use it
- When planning the migration of database objects like procedures, triggers, or views.
- When reviewing completed migration artifacts to ensure all behavioral risks are mitigated.
- When validating that integration tests adequately cover Oracle-to-PostgreSQL differences.
- When you need a structured checklist to gate the success of a database migration task.
Advantages
- Reduces the risk of runtime errors caused by subtle database behavioral differences.
- Standardizes the migration process across different database objects.
- Provides clear, actionable guidance for common migration pitfalls.
- Facilitates thorough testing by highlighting specific scenarios that require validation.
Usage examples
- “I am planning to migrate a set of Oracle stored procedures to PostgreSQL. How should I start?”
- The agent will guide you through the Risk Assessment workflow, helping you list affected objects and screen them against the reference index to document necessary changes.
- “I have finished migrating a query that uses ROWNUM. How do I validate this?”
- The agent will initiate the Validation workflow, prompting you to map the artifact, check it against the ROWNUM-specific reference, and verify that your integration tests cover the new LIMIT/OFFSET logic.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/github/reviewing-oracle-to-postgres-migration/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip reviewing-oracle-to-postgres-migration.zip -d .agents/skills
Documentation by PrimedPaul
Oracle-to-PostgreSQL Database Migration
Surfaces migration risks and validates migration work against known Oracle/PostgreSQL behavioral differences documented in the references/ folder.
When to use
Planning — Before starting migration work on a procedure, trigger, query, or refcursor client. Identify which reference insights apply so risks are addressed up front.
Validating — After migration work is done, confirm every applicable insight was addressed and integration tests cover the new PostgreSQL semantics.
Workflow
Determine the task type:
Planning a migration? Follow the risk assessment workflow. Validating completed work? Follow the validation workflow.
Risk assessment workflow (planning)
Risk Assessment:
- [ ] Step 1: Identify the migration scope
- [ ] Step 2: Screen each insight for applicability
- [ ] Step 3: Document risks and recommended actionsStep 1: Identify the migration scope
List the affected database objects (procedures, triggers, queries, views) and the application code that calls them.
Step 2: Screen each insight for applicability
Review the reference index in references/REFERENCE.md. For each entry, determine whether the migration scope contains patterns affected by that insight. Read the full reference file only when the insight is potentially relevant.
Step 3: Document risks and recommended actions
For each applicable insight, note the specific risk and the recommended fix pattern from the reference file. Flag any insight that requires a design decision (e.g., whether to preserve Oracle empty-string-as-NULL semantics or adopt PostgreSQL behavior).
Validation workflow (post-migration)
Validation:
- [ ] Step 1: Map the migration artifact
- [ ] Step 2: Cross-check applicable insights
- [ ] Step 3: Verify integration test coverage
- [ ] Step 4: Gate the resultStep 1: Map the migration artifact
Identify the migrated object and summarize the change set.
Step 2: Cross-check applicable insights
For each reference in references/REFERENCE.md, confirm the behavior or test requirement is acknowledged and addressed in the migration work.
Step 3: Verify integration test coverage
Confirm tests exercise both the happy path and the failure scenarios highlighted in applicable insights (exceptions, sorting, UNION ALL behavior/performance risks, refcursor consumption, concurrent transactions, timestamps, materialized-view freshness, etc.).
Step 4: Gate the result
Return a checklist asserting each applicable insight was addressed, migration scripts run, and integration tests pass.
Files
- ATTRIBUTION.mdAdded by AutoGineer913 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md3.1 KiB
- references/REFERENCE.md2.8 KiB
- references/empty-strings-handling.md2.0 KiB
- references/no-data-found-exceptions.md2.6 KiB
- references/oracle-nvl-decode-functions.md2.4 KiB
- references/oracle-parentheses-from-clause.md4.8 KiB
- references/oracle-rownum-pagination.md2.1 KiB
- references/oracle-sysdate-sequences-dual.md3.2 KiB
- references/oracle-to-postgres-sorting.md2.7 KiB
- references/oracle-to-postgres-timestamp-timezone.md8.4 KiB
- references/oracle-to-postgres-to-char-numeric.md4.2 KiB
- references/oracle-to-postgres-type-coercion.md4.9 KiB
- references/postgres-concurrent-transactions.md9.4 KiB
- references/postgres-materialized-view-refresh.md1.4 KiB
- references/postgres-refcursor-handling.md5.7 KiB
- references/postgres-union-all-planner.md1.6 KiB
references/oracle-to-postgres-timestamp-timezone.md
Oracle to PostgreSQL: CURRENT_TIMESTAMP and NOW() Timezone Handling
Contents
Problem
Behavior Comparison
PostgreSQL Timezone Precedence
Common Error Symptoms
Migration Actions — Npgsql config, DateTime normalization, stored procedures, session timezone, application code
Integration Test Patterns
Checklist
Problem
Oracle's CURRENT_TIMESTAMP returns a value in the session timezone and stores it in the column's declared precision. When .NET reads this value back via ODP.NET, it is surfaced as a DateTime with Kind=Local, reflecting the OS timezone of the client.
PostgreSQL's CURRENT_TIMESTAMP and NOW() both return a timestamptz (timestamp with time zone) anchored to UTC, regardless of the session timezone setting. How Npgsql surfaces this value depends on the driver version and configuration:
Npgsql < 6 / legacy mode (
EnableLegacyTimestampBehavior = true):timestamptzcolumns are returned asDateTimewithKind=Unspecified. This is the source of silent timezone bugs when migrating from Oracle.Npgsql 6+ with legacy mode disabled (the new default):
timestamptzcolumns are returned asDateTimewithKind=Utc, and writing aKind=Unspecifiedvalue throws an exception at insertion time.
Projects that have not yet upgraded to Npgsql 6+, or that explicitly opt back into legacy mode, remain vulnerable to the Kind=Unspecified issue. This mismatch — and the ease of accidentally re-enabling legacy mode — causes silent data corruption, incorrect comparisons, and off-by-N-hours bugs that are extremely difficult to trace.
Behavior Comparison
| Aspect | Oracle | PostgreSQL |
|---|---|---|
CURRENT_TIMESTAMP type | TIMESTAMP WITH LOCAL TIME ZONE | timestamptz (UTC-normalised) |
Client DateTime.Kind via driver | Local | Unspecified (Npgsql < 6 / legacy mode); Utc (Npgsql 6+ default) |
| Session timezone influence | Yes — affects stored/returned value | Affects display only; UTC stored internally |
| NOW() equivalent | SYSDATE / CURRENT_TIMESTAMP | NOW() = CURRENT_TIMESTAMP (both return timestamptz) |
| Implicit conversion on comparison | Oracle applies session TZ offset | PostgreSQL compares UTC; session TZ is display-only |
PostgreSQL Timezone Precedence
PostgreSQL resolves the effective session timezone using the following hierarchy (highest priority wins):
| Level | How it is set |
|---|---|
| Session | SET TimeZone = 'UTC' sent at connection open |
| Role | ALTER ROLE app_user SET TimeZone = 'UTC' |
| Database | ALTER DATABASE mydb SET TimeZone = 'UTC' |
| Server | postgresql.conf → TimeZone = 'America/New_York' |
The session timezone does not affect the stored UTC value of a timestamptz column — it only controls how SHOW timezone and ::text casts format a value for display. Application code that relies on DateTime.Kind or compares timestamps without an explicit timezone can produce incorrect results if the server's default timezone is not UTC.
Common Error Symptoms
Timestamps read from PostgreSQL have
Kind=Unspecified; comparisons withDateTime.UtcNoworDateTime.Nowproduce incorrect results.Date-range queries return too few or too many rows because the WHERE clause comparison is evaluated in a timezone that differs from the stored UTC value.
Integration tests pass on a developer machine (UTC OS timezone) but fail in CI or production (non-UTC timezone).
Stored procedure output parameters carrying timestamps arrive with a session-offset applied by the server but are then compared to UTC values in the application.
Migration Actions
1. Configure Npgsql for UTC via Connection String or AppContext
Npgsql 6+ ships with EnableLegacyTimestampBehavior set to false by default, which causes timestamptz values to be returned as DateTime with Kind=Utc. Explicitly setting the switch at startup is still recommended to guard against accidental opt-in to legacy mode (e.g., via a config file or a transitive dependency) and to make the intent visible to future maintainers:
// Program.cs / Startup.cs — apply once at application start
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", false);With this switch disabled, Npgsql throws if you try to write a DateTime with Kind=Unspecified to a timestamptz column, making timezone bugs loud and detectable at insertion time rather than silently at query time.
2. Normalise DateTime Values Before Persistence
Replace any DateTime.Now with DateTime.UtcNow throughout the migrated codebase. For values that originate from external input (e.g., user-provided dates deserialized from JSON), ensure they are converted to UTC before being saved:
// Before (Oracle-era code — relied on session/OS timezone)
var timestamp = DateTime.Now;
// After (PostgreSQL-compatible)
var timestamp = DateTime.UtcNow;
// For externally-supplied values
var utcTimestamp = dateTimeInput.Kind == DateTimeKind.Utc
? dateTimeInput
: dateTimeInput.ToUniversalTime();3. Fix Stored Procedures Using CURRENT_TIMESTAMP / NOW()
Stored procedures that assign CURRENT_TIMESTAMP or NOW() to a timestamp without time zone (timestamp) column must be reviewed. Prefer timestamptz columns or cast explicitly:
-- Ambiguous: server timezone influences interpretation
INSERT INTO audit_log (created_at) VALUES (NOW()::timestamp);
-- Safe: always UTC
INSERT INTO audit_log (created_at) VALUES (NOW() AT TIME ZONE 'UTC');
-- Or: use timestamptz column type and let PostgreSQL store UTC natively
INSERT INTO audit_log (created_at) VALUES (CURRENT_TIMESTAMP);4. Force Session Timezone on Connection Open (Defence-in-Depth)
Regardless of role or database defaults, set the session timezone explicitly when opening a connection. This guarantees consistent behavior independent of server configuration:
// Npgsql connection string approach
var connString = "Host=localhost;Database=mydb;Username=app;Password=...;Timezone=UTC";
// Or: apply via NpgsqlDataSourceBuilder
var dataSource = new NpgsqlDataSourceBuilder(connString)
.Build();
// Or: execute on every new connection
await using var conn = new NpgsqlConnection(connString);
await conn.OpenAsync();
await using var cmd = new NpgsqlCommand("SET TimeZone = 'UTC'", conn);
await cmd.ExecuteNonQueryAsync();5. Application Code — Avoid DateTime.Kind=Unspecified
Audit all repository and data-access code that reads timestamp columns. Where Npgsql returns Unspecified, either configure the data source globally (option 1 above) or wrap the read:
// Safe reader helper — convert Unspecified to Utc at the boundary
DateTime ReadUtcDateTime(NpgsqlDataReader reader, int ordinal)
{
var dt = reader.GetDateTime(ordinal);
return dt.Kind == DateTimeKind.Unspecified
? DateTime.SpecifyKind(dt, DateTimeKind.Utc)
: dt.ToUniversalTime();
}Integration Test Patterns
Test: Verify timestamps persist and return as UTC
[Fact]
public async Task InsertedTimestamp_ShouldRoundTripAsUtc()
{
var before = DateTime.UtcNow;
await repository.InsertAuditEntryAsync(/* ... */);
var retrieved = await repository.GetLatestAuditEntryAsync();
Assert.Equal(DateTimeKind.Utc, retrieved.CreatedAt.Kind);
Assert.True(retrieved.CreatedAt >= before,
"Persisted CreatedAt should not be earlier than the pre-insert UTC timestamp.");
}Test: Verify timestamp comparisons across Oracle and PostgreSQL baselines
[Fact]
public async Task TimestampComparison_ShouldReturnSameRowsAsOracle()
{
var cutoff = DateTime.UtcNow.AddDays(-1);
var oracleResults = await oracleRepository.GetEntriesAfter(cutoff);
var postgresResults = await postgresRepository.GetEntriesAfter(cutoff);
Assert.Equal(oracleResults.Count, postgresResults.Count);
}Checklist
[ ]
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", false)applied at application startup.[ ] All
DateTime.Nowusages in data-access code replaced withDateTime.UtcNow.[ ] Connection string or connection-open hook sets
Timezone=UTC/SET TimeZone = 'UTC'.[ ] Stored procedures that use
CURRENT_TIMESTAMPorNOW()reviewed;timestamp without time zonecolumns explicitly cast or replaced withtimestamptz.[ ] Integration tests assert
DateTime.Kind == Utcon retrieved timestamp values.[ ] Tests cover date-range queries to confirm row counts match Oracle baseline.
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
PrimedPaul
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/reviewing-oracle-to-postgres-migration · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- PrimedPaul
AutoGineer is not affiliated with or endorsed by PrimedPaul or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download reviewing-oracle-to-postgres-migration.zip