Skip to content

Commit d27cc9e

Browse files
authored
Merge pull request #5 from commandprompt/doc/migration-positioning
Add a migration-led positioning page
2 parents 82d1175 + 795903e commit d27cc9e

3 files changed

Lines changed: 152 additions & 0 deletions

File tree

doc/MIGRATION.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Migrating procedural code to PostgreSQL
2+
3+
Most teams meet plx in the middle of a migration. There is procedural code that
4+
works, there is a deadline, and PostgreSQL speaks plpgsql. This page is about
5+
that situation: what the realistic options are, where plx fits among them, and
6+
where it does not.
7+
8+
## The situation plx is for
9+
10+
You are moving logic into PostgreSQL and the logic already exists somewhere
11+
else. Four common shapes:
12+
13+
- **Oracle to PostgreSQL.** Packages and procedures written in PL/SQL, often
14+
large and often the part of the migration nobody wants to own. See
15+
[plxplsql](plxplsql.md).
16+
- **SQL Server to PostgreSQL.** Stored procedures written in Transact-SQL, with
17+
`DECLARE @x`, `SET`, and `IIF` throughout. See [plxtsql](plxtsql.md).
18+
- **Application logic moving into the database.** Validation, scoring, or
19+
reporting that lives in a Ruby, PHP, JavaScript, TypeScript, Python, or Go
20+
codebase and needs to run next to the data instead of a round trip away.
21+
- **Mainframe modernisation.** Batch logic in COBOL, where the rules are the
22+
asset and rewriting them is the risk. See [plxcobol](plxcobol.md).
23+
24+
In all four the hard part is the same. The code is understood by the people who
25+
wrote it, and a rewrite into an unfamiliar language is where the defects get
26+
introduced.
27+
28+
## What plx actually does
29+
30+
`CREATE FUNCTION ... LANGUAGE plx*` transpiles the body to plpgsql at DDL time
31+
and stores that plpgsql in `pg_proc.prosrc`. At run time PostgreSQL's own
32+
plpgsql interpreter executes it. No language runtime is loaded into the
33+
backend, and the generated plpgsql is ordinary catalog content you can read.
34+
35+
Two consequences matter for a migration. A body the dialect cannot parse is
36+
rejected at `CREATE FUNCTION`, so that class of error is found at deploy time
37+
rather than the first time a row reaches the function. Treat that as a filter
38+
and not a guarantee: the generated plpgsql can still fail at execution the way
39+
any plpgsql can, and a construct that transpiles cleanly can still mean
40+
something different in SQL than it meant in the source language. Reading the
41+
generated plpgsql, step 3 below, is what catches that. The second consequence
42+
is that what you end up with is plpgsql, which is what you would have written
43+
by hand.
44+
45+
## The alternatives
46+
47+
| Approach | What you write | Runtime in the backend | Result in the catalog |
48+
| --- | --- | --- | --- |
49+
| Rewrite by hand | plpgsql | none | plpgsql |
50+
| plx | your dialect | none | plpgsql |
51+
| An embedded PL (plv8, plpython3u, plperl, PL/Ruby, PL/PHP) | that language | that language's engine | that language |
52+
| Leave the logic in the application | your dialect | none | nothing |
53+
54+
**Rewriting by hand** is the baseline, and for a handful of small functions it
55+
is the right answer. It stops being the right answer at volume, because the
56+
cost is linear in the number of functions and the risk sits with whoever is
57+
least familiar with the original code.
58+
59+
**An embedded PL** gives you the real language, with its standard library and
60+
its semantics. That is a genuine advantage plx does not offer: plx gives you a
61+
dialect's syntax over SQL semantics, not the language itself. The cost is a
62+
language runtime in every backend process, an operational dependency, and, for
63+
`plpython3u` and `plperlu`, an untrusted language that only a superuser can
64+
use. If you need actual Python or actual V8, use them; plx is not a substitute.
65+
66+
**Leaving the logic in the application** is a real option and often the correct
67+
one. Move logic into the database when it needs to be transactional with the
68+
data, when several applications must share it, or when the round trip is the
69+
bottleneck. If none of those apply, the migration may not be necessary.
70+
71+
For Oracle specifically, `ora2pg` converts schema and PL/SQL to plpgsql, and
72+
commercial Oracle-compatible distributions exist. These are complementary
73+
rather than competing: `ora2pg` produces plpgsql you then own, while plxplsql
74+
keeps the body in PL/SQL form in the catalog. Which you want depends on whether
75+
your team would rather maintain plpgsql or PL/SQL from here on.
76+
77+
## What a migration looks like
78+
79+
1. **Install the extension and pick the dialect.** One dialect per source
80+
language. Nothing else changes about the database.
81+
2. **Move functions across in their original form.** The body stays in the
82+
language it was written in, so review is done by the people who know the
83+
code, reading the code they know.
84+
3. **Read the generated plpgsql.** It is in `pg_proc.prosrc`, one statement per
85+
source statement. This is the review step that catches a dialect construct
86+
meaning something different in SQL than it did at home, particularly around
87+
NULL.
88+
4. **Test against the old system.** The generated plpgsql is ordinary
89+
PostgreSQL, so every tool you already use applies.
90+
5. **Decide what to keep.** Some teams keep the dialect bodies because that is
91+
what their developers read. Others treat plx as the conversion step and move
92+
to plpgsql once the migration is done. Both are supported, and the next
93+
section is why.
94+
95+
## Leaving plx
96+
97+
Because the catalog holds plpgsql, plx is removable. The generated body of any
98+
plx function can be recreated as a plain plpgsql function:
99+
100+
```sql
101+
DO $do$
102+
DECLARE
103+
body text;
104+
BEGIN
105+
SELECT prosrc INTO body
106+
FROM pg_proc WHERE oid = 'grade(int)'::regprocedure;
107+
EXECUTE format(
108+
'CREATE FUNCTION grade_pg(score int) RETURNS text LANGUAGE plpgsql AS %L',
109+
body);
110+
END;
111+
$do$;
112+
```
113+
114+
`grade_pg` is a normal plpgsql function with no dependency on the extension.
115+
Applied across your functions, this is an exit: you keep the plpgsql and drop
116+
plx. The original dialect source is preserved in a trailing comment inside the
117+
generated body, so recreating it as plpgsql does not throw away the source
118+
either. See [debugging](DEBUGGING.md) for the helper that decodes it.
119+
120+
An adoption decision you can reverse is a smaller decision. That is the main
121+
argument for starting with plx during a migration rather than after one.
122+
123+
## When not to use plx
124+
125+
- **You need the language, not the syntax.** A dialect body is transpiled to
126+
plpgsql, so the language's standard library, object model, and runtime
127+
semantics are not available. [Gaps and limitations](LIMITATIONS.md) is the
128+
specific list per dialect, and it is worth reading before committing.
129+
- **The expressions are the hard part.** Expressions are largely passed through
130+
to SQL. Where a dialect's operator means something different in SQL, notably
131+
`==` and `+` on strings, and NULL rather than falsy comparison, the
132+
translation is documented rather than emulated.
133+
- **You have three functions to move.** Rewrite them.
134+
- **The logic does not belong in the database.** plx makes the move cheaper,
135+
which is not the same as making it correct.
136+
137+
## Where to go next
138+
139+
- [User guide](USERGUIDE.md): the same worked examples across dialects.
140+
- [Feature parity](PARITY.md): the construct-by-construct matrix against
141+
plpgsql.
142+
- [Gaps and limitations](LIMITATIONS.md): what each dialect does not support.
143+
- The cookbook for your dialect: [overview](cookbook/index.md).

doc/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ and [feature parity](PARITY.md) is the construct-by-construct matrix.
5353
- **Familiar syntax, plpgsql performance.** Write in the dialect your team
5454
knows; run with plpgsql's execution and trust model.
5555

56+
## Migrating
57+
58+
If you are moving procedural code into PostgreSQL from Oracle, SQL Server, a
59+
mainframe, or an application codebase, [migrating to
60+
PostgreSQL](MIGRATION.md) covers how plx compares to the alternatives, what the
61+
migration looks like, and how to leave plx afterwards while keeping the
62+
generated plpgsql.
63+
5664
## Install
5765

5866
Build from source against your PostgreSQL installation:

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ markdown_extensions:
5454
nav:
5555
- Home: index.md
5656
- User guide: USERGUIDE.md
57+
- Migrating to PostgreSQL: MIGRATION.md
5758
- Dialects:
5859
- Ruby (plxruby): plxruby.md
5960
- PHP (plxphp): plxphp.md

0 commit comments

Comments
 (0)