-
Notifications
You must be signed in to change notification settings - Fork 895
Schema Diff: complete the SERIAL/integer column conversion script #10318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dpage
wants to merge
7
commits into
pgadmin-org:master
Choose a base branch
from
dpage:fix/10292-integer-serial-conversion
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
6deffe2
Schema Diff: complete the SERIAL/integer column conversion script (#1…
dpage 9e6e535
fix: decode escaped quotes in sequence names and stop shadowing confl…
dpage 824d569
Create a SERIAL conversion's sequence with the column's own integer t…
dpage 6cde5c3
Move a converted SERIAL column's new sequence past its existing value…
dpage 95c51cf
Only move a converted SERIAL column's sequence forward from its START…
dpage f31ea7c
Keep a SERIAL column's sequence when only its type changes (#10292)
dpage 2f0d304
Test the whole Schema Diff script for a SERIAL conversion (#10292)
dpage File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
...groups/servers/databases/schemas/tables/columns/tests/test_parse_nextval_sequence_unit.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| ########################################################################## | ||
| # | ||
| # pgAdmin 4 - PostgreSQL Tools | ||
| # | ||
| # Copyright (C) 2013 - 2026, The pgAdmin Development Team | ||
| # This software is released under the PostgreSQL Licence | ||
| # | ||
| ########################################################################## | ||
|
|
||
| """Unit tests for parse_nextval_sequence(), covering the schema-qualified | ||
| identifier it extracts out of a column's ``nextval(...)`` default, and in | ||
| particular the SQL string-literal quote-doubling PostgreSQL applies when | ||
| the sequence name itself contains a single quote (#10318). | ||
| """ | ||
|
|
||
| from pgadmin.browser.server_groups.servers.databases.schemas.tables.\ | ||
| columns.utils import parse_nextval_sequence | ||
| from pgadmin.utils.route import BaseTestGenerator | ||
|
|
||
|
|
||
| class TestParseNextvalSequence(BaseTestGenerator): | ||
| """Unit tests for parse_nextval_sequence().""" | ||
|
|
||
| scenarios = [ | ||
| ('No default value returns None', | ||
| dict(test_method='test_none_defval')), | ||
| ('A non-nextval default returns None', | ||
| dict(test_method='test_non_nextval_defval')), | ||
| ('A plain schema-qualified sequence name is extracted verbatim', | ||
| dict(test_method='test_plain_sequence_name')), | ||
| ('A sequence name containing a single quote has the doubled ' | ||
| 'quote decoded back to one', | ||
| dict(test_method='test_quoted_sequence_name_with_embedded_quote')), | ||
| ] | ||
|
|
||
| def runTest(self): | ||
| getattr(self, self.test_method)() | ||
|
|
||
| def test_none_defval(self): | ||
| self.assertIsNone(parse_nextval_sequence(None)) | ||
|
|
||
| def test_non_nextval_defval(self): | ||
| self.assertIsNone(parse_nextval_sequence('1')) | ||
|
|
||
| def test_plain_sequence_name(self): | ||
| seq_name = parse_nextval_sequence( | ||
| "nextval('public.t_id_seq'::regclass)") | ||
| self.assertEqual(seq_name, 'public.t_id_seq') | ||
|
|
||
| def test_quoted_sequence_name_with_embedded_quote(self): | ||
| # PostgreSQL renders the sequence "id'seq" as the double-quoted | ||
| # identifier "id'seq", and then - because the whole thing is the | ||
| # argument of a string literal - doubles the embedded single | ||
| # quote: nextval('public."id''seq"'::regclass). The extracted | ||
| # identifier must have that doubling undone, since it is spliced | ||
| # verbatim into CREATE SEQUENCE / ALTER SEQUENCE DDL rather than | ||
| # back into a string literal. | ||
| seq_name = parse_nextval_sequence( | ||
| 'nextval(\'public."id\'\'seq"\'::regclass)') | ||
| self.assertEqual(seq_name, 'public."id\'seq"') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
...server_groups/servers/databases/schemas/tables/tests/test_normalise_serial_column_unit.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| ########################################################################## | ||
| # | ||
| # pgAdmin 4 - PostgreSQL Tools | ||
| # | ||
| # Copyright (C) 2013 - 2026, The pgAdmin Development Team | ||
| # This software is released under the PostgreSQL Licence | ||
| # | ||
| ########################################################################## | ||
|
|
||
| """Unit tests for BaseTableView._normalise_serial_column(), covering both | ||
| directions of converting a column between a plain integer type and | ||
| SERIAL/BIGSERIAL/SMALLSERIAL (#10292), and guarding against the ordinary | ||
| (non Schema Diff) column PUT being mistaken for one. | ||
| """ | ||
|
|
||
| from pgadmin.browser.server_groups.servers.databases.schemas.tables.utils \ | ||
| import BaseTableView | ||
| from pgadmin.utils.route import BaseTestGenerator | ||
|
|
||
|
|
||
| class TestNormaliseSerialColumn(BaseTestGenerator): | ||
| """Unit tests for BaseTableView._normalise_serial_column().""" | ||
|
|
||
| scenarios = [ | ||
| ('Converting a plain column to SERIAL creates the sequence and ' | ||
| 'restores the default', | ||
| dict(test_method='test_becoming_serial')), | ||
| ('Converting a SERIAL column to plain queues the sequence for ' | ||
| 'dropping', | ||
| dict(test_method='test_leaving_serial')), | ||
| ('A genuine difference on a column that is SERIAL on both sides ' | ||
| 'is unaffected', | ||
| dict(test_method='test_both_sides_already_serial')), | ||
| ('A partial update that never mentions cltype leaves an ' | ||
| 'already-SERIAL column alone', | ||
| dict(test_method='test_partial_update_without_cltype_is_ignored')), | ||
| ('A partial update that only changes the type of a SERIAL column ' | ||
| 'keeps its sequence', | ||
| dict(test_method='test_type_change_without_defval_keeps_serial')), | ||
| ] | ||
|
|
||
| def runTest(self): | ||
| getattr(self, self.test_method)() | ||
|
|
||
| def test_becoming_serial(self): | ||
| # Schema Diff's source column, reprojected as BIGSERIAL, with the | ||
| # real nextval() default preserved under 'serial_defval'. | ||
| data = { | ||
| 'cltype': 'bigserial', 'typname': 'bigserial', | ||
| 'serial_defval': "nextval('public.t_id_seq'::regclass)", | ||
| 'seqincrement': 1, 'seqstart': 1, 'seqmin': 1, | ||
| 'seqmax': 9223372036854775807, 'seqcache': 1, 'seqcycle': False, | ||
| } | ||
| # The target's current (plain, unreprojected) column. | ||
| old_col_data = { | ||
| 'cltype': 'integer', 'typname': 'integer', 'defval': None, | ||
| 'seqrelid': None, 'defseqrelid': None, 'attidentity': '', | ||
| } | ||
|
|
||
| BaseTableView._normalise_serial_column(data, old_col_data) | ||
|
|
||
| self.assertEqual(data['cltype'], 'bigint') | ||
| self.assertEqual(data['typname'], 'bigint') | ||
| self.assertEqual(data['defval'], | ||
| "nextval('public.t_id_seq'::regclass)") | ||
| self.assertEqual(data['serial_seq_create']['name'], 'public.t_id_seq') | ||
| self.assertEqual(data['serial_seq_create']['increment'], 1) | ||
| self.assertEqual(data['serial_seq_create']['data_type'], 'bigint') | ||
| self.assertNotIn('serial_defval', data) | ||
| self.assertNotIn('seqincrement', data) | ||
|
|
||
| def test_leaving_serial(self): | ||
| # Schema Diff's source column: a plain integer, never reprojected. | ||
| data = {'cltype': 'integer', 'typname': 'integer', 'defval': None} | ||
| # The target's current column genuinely is SERIAL. | ||
| old_col_data = { | ||
| 'cltype': 'integer', 'typname': 'integer', | ||
| 'defval': "nextval('public.t_id_seq'::regclass)", | ||
| 'seqrelid': 100, 'defseqrelid': 100, 'attidentity': '', | ||
| } | ||
|
|
||
| BaseTableView._normalise_serial_column(data, old_col_data) | ||
|
|
||
| self.assertEqual(data['serial_seq_drop'], 'public.t_id_seq') | ||
| # The type didn't really change; the default is still queued to | ||
| # be dropped by the generic template logic (data['defval'] stays | ||
| # None/empty and differs from o_data['defval']). | ||
| self.assertEqual(data['cltype'], 'integer') | ||
|
|
||
| def test_both_sides_already_serial(self): | ||
| # Both sides are BIGSERIAL; only some other property (a comment, | ||
| # say) differs. The reprojection emptied 'defval' on the source | ||
| # side; that must not be read as a request to drop the real one, | ||
| # and no sequence should be created or dropped. | ||
| data = { | ||
| 'cltype': 'bigserial', 'typname': 'bigserial', | ||
| 'serial_defval': "nextval('public.t_id_seq'::regclass)", | ||
| 'seqincrement': 1, | ||
| } | ||
| old_col_data = { | ||
| 'cltype': 'integer', 'typname': 'integer', | ||
| 'defval': "nextval('public.t_id_seq'::regclass)", | ||
| 'seqrelid': 100, 'defseqrelid': 100, 'attidentity': '', | ||
| } | ||
|
|
||
| BaseTableView._normalise_serial_column(data, old_col_data) | ||
|
|
||
| self.assertNotIn('defval', data) | ||
| self.assertNotIn('serial_seq_create', data) | ||
| self.assertNotIn('serial_seq_drop', data) | ||
| self.assertNotIn('seqincrement', data) | ||
|
|
||
| def test_partial_update_without_cltype_is_ignored(self): | ||
| # The ordinary column PUT (not Schema Diff) submits only the | ||
| # fields the user actually changed - e.g. a privilege - and omits | ||
| # 'cltype' entirely when the type itself wasn't touched, even if | ||
| # the column already is SERIAL. This must be a complete no-op. | ||
| data = {'attacl': {'added': []}} | ||
| old_col_data = { | ||
| 'cltype': 'integer', 'typname': 'integer', | ||
| 'defval': "nextval('public.t_id_seq'::regclass)", | ||
| 'seqrelid': 100, 'defseqrelid': 100, 'attidentity': '', | ||
| } | ||
|
|
||
| BaseTableView._normalise_serial_column(data, old_col_data) | ||
|
|
||
| self.assertEqual(data, {'attacl': {'added': []}}) | ||
|
|
||
| def test_type_change_without_defval_keeps_serial(self): | ||
| # Widening a SERIAL column to bigint in the table dialog sends | ||
| # only the new 'cltype'. With its nextval() default untouched the | ||
| # column is still SERIAL, so its sequence must not be queued for | ||
| # dropping (PostgreSQL would refuse whilst the default uses it). | ||
| data = {'cltype': 'bigint'} | ||
| old_col_data = { | ||
| 'cltype': 'integer', 'typname': 'integer', | ||
| 'defval': "nextval('public.t_id_seq'::regclass)", | ||
| 'seqrelid': 100, 'defseqrelid': 100, 'attidentity': '', | ||
| } | ||
|
|
||
| BaseTableView._normalise_serial_column(data, old_col_data) | ||
|
|
||
| self.assertEqual(data, {'cltype': 'bigint'}) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.