Skip to content

Commit b366d21

Browse files
authored
Allow users to customize "OF TYPE" columns during table creation. #229
1 parent fbbc12a commit b366d21

File tree

9 files changed

+109
-8
lines changed

9 files changed

+109
-8
lines changed

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,20 @@ def get_oftype(self, gid, sid, did, scid, tid=None):
797797
if not status:
798798
return internal_server_error(errormsg=type_cols)
799799

800+
# Mark columns as inherited from type (not table)
801+
# so the UI allows editing defaults/constraints
802+
# Also store original values for comparison later
803+
for col in type_cols['rows']:
804+
if 'inheritedfrom' in col:
805+
col['inheritedfromtype'] = col['inheritedfrom']
806+
# Keep inheritedfrom for backward compatibility
807+
# but UI will check inheritedfromtype first
808+
809+
# Store original values from the composite type
810+
# This allows backend to detect actual modifications
811+
col['original_defval'] = col.get('defval')
812+
col['original_attnotnull'] = col.get('attnotnull', False)
813+
800814
res.append({
801815
'label': row['typname'],
802816
'value': row['typname'],

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ export default class ColumnSchema extends BaseUISchema {
160160
return { cell: this.attlenRange(state) ? 'int' : '' };
161161
}
162162

163+
isInheritedFromType(state) {
164+
return !isEmptyString(state.inheritedfromtype);
165+
}
166+
163167
get baseFields() {
164168
let obj = this;
165169

@@ -284,6 +288,30 @@ export default class ColumnSchema extends BaseUISchema {
284288
}
285289
return false;
286290
},
291+
},{
292+
/* This field is used to track inheritance from composite types */
293+
id: 'inheritedfromtype',
294+
label: gettext('Inherited from type'),
295+
type: 'text',
296+
visible: false,
297+
},{
298+
/* This field is used to track inheritance from parent tables */
299+
id: 'inheritedfromtable',
300+
label: gettext('Inherited from table'),
301+
type: 'text',
302+
visible: false,
303+
},{
304+
/* Store original DEFAULT from composite type to detect modifications */
305+
id: 'original_defval',
306+
label: gettext('Original Default'),
307+
type: 'text',
308+
visible: false,
309+
},{
310+
/* Store original NOT NULL from composite type to detect modifications */
311+
id: 'original_attnotnull',
312+
label: gettext('Original NOT NULL'),
313+
type: 'boolean',
314+
visible: false,
287315
},{
288316
id:'geometry', label: gettext('Geometry Type'), deps: ['cltype'],
289317
group: gettext('Definition'), type: 'select', options: this.geometryTypes,
@@ -465,19 +493,28 @@ export default class ColumnSchema extends BaseUISchema {
465493
disabled: function(state) {
466494
let isDisabled = ['serial', 'bigserial', 'smallserial'].indexOf(state.cltype) > -1;
467495
isDisabled = isDisabled || state.colconstype != 'n';
496+
if(!isDisabled && this.isInheritedFromType(state)) {
497+
return isDisabled = false;
498+
}
468499
return isDisabled;
469500
}, depChange: (state)=>{
470501
let isDisabled = false;
471502
if(!obj.inSchemaWithModelCheck(state)) {
472503
isDisabled = ['serial', 'bigserial', 'smallserial'].indexOf(state.cltype) > -1;
473504
}
474505
isDisabled = isDisabled || state.colconstype != 'n';
475-
if (isDisabled && obj.isNew(state)) {
506+
// Allow editing for OF TYPE columns, but block for table inheritance
507+
if (isDisabled && obj.isNew(state) && !this.isInheritedFromType(state)) {
476508
return {defval: undefined};
477509
}
478510
}, editable: function(state) {
511+
// Allow editing for OF TYPE columns (inheritedfromtype)
512+
// But block editing for parent table inheritance (inheritedfromtable or inheritedfrom without inheritedfromtype)
513+
if (this.isInheritedFromType(state)) {
514+
return true;
515+
}
479516
// inheritedfrom has value then we should disable it
480-
return !(!isEmptyString(state.inheritedfrom) || !this.editableCheckForTable(state));
517+
return this.editableCheckForTable(state);
481518
},
482519
},{
483520
id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/utils.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,9 +364,59 @@ def parse_format_columns(data, mode=None):
364364
# tables 'CREATE' mode
365365
final_columns = []
366366

367+
# Get list of columns in primary key constraint
368+
pk_columns = set()
369+
if 'primary_key' in data and len(data['primary_key']) > 0:
370+
for pk in data['primary_key']:
371+
if 'columns' in pk:
372+
for col in pk['columns']:
373+
if 'column' in col:
374+
pk_columns.add(col['column'])
375+
367376
for c in columns:
377+
# Include non-inherited columns
368378
if c.get('inheritedfrom', None) is None:
369379
final_columns.append(c)
380+
# Also include columns inherited from composite types (OF TYPE)
381+
# that have modifications (WITH OPTIONS clause)
382+
elif c.get('inheritedfromtype', None) is not None:
383+
# Check if column has been modified or is in a constraint
384+
has_modifications = False
385+
386+
# Check if column is in PRIMARY KEY constraint
387+
# Note: We don't include the column for PRIMARY KEY because
388+
# it's added as a separate table-level constraint
389+
# Uncomment this if you want column-level PRIMARY KEY:
390+
# if c.get('name') in pk_columns:
391+
# has_modifications = True
392+
393+
# Check if DEFAULT value was actually modified
394+
# (different from type)
395+
original_defval = c.get('original_defval')
396+
current_defval = c.get('defval')
397+
# Compare as strings, treating None and empty string
398+
# as equivalent
399+
orig_val = str(original_defval) \
400+
if original_defval is not None else ''
401+
curr_val = str(current_defval) \
402+
if current_defval is not None else ''
403+
if orig_val != curr_val:
404+
has_modifications = True
405+
406+
# Check if NOT NULL was actually modified
407+
# (different from type)
408+
original_attnotnull = c.get('original_attnotnull', False)
409+
current_attnotnull = c.get('attnotnull', False)
410+
if original_attnotnull != current_attnotnull:
411+
has_modifications = True
412+
413+
if has_modifications:
414+
# Mark this column to use WITH OPTIONS syntax in template
415+
# Skip identity columns as WITH OPTIONS
416+
# cannot be combined with GENERATED ALWAYS AS IDENTITY
417+
if c.get('colconstype') != 'i':
418+
c['has_with_options'] = True
419+
final_columns.append(c)
370420

371421
# Now we have all lis of columns which we need
372422
# to include in our create definition, Let's format them

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ export class LikeSchema extends BaseUISchema {
275275
}
276276

277277
resetVals(state) {
278-
if(this.isRelationDisable(state) && this.top.isNew()) {
278+
if(this.isRelationDisable(state) && this.top && this.top.isNew()) {
279279
return {
280280
like_default_value: false,
281281
like_constraints: false,

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/create.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
4646
{% if data.columns and data.columns|length > 0 %}
4747
{% for c in data.columns %}
4848
{% if c.name and c.cltype %}
49-
{% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' %} DEFAULT {{c.defval}}{% endif %}
49+
{% if c.inheritedfromtype and c.has_with_options %}{# Use WITH OPTIONS syntax for modified OF TYPE columns #}{{conn|qtIdent(c.name)}} WITH OPTIONS{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' %} DEFAULT {{c.defval}}{% endif %}{% elif c.inheritedfromtable %}{# Inherited from parent table - keep as comment #}-- Inherited from table {{c.inheritedfromtable}}: {{conn|qtIdent(c.name)}}{% else %}{# Regular column or inherited without modifications #}{% if c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' %} DEFAULT {{c.defval}}{% endif %}{% endif %}
5050
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
5151
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
5252
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/create.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
5252
{% if data.columns and data.columns|length > 0 %}
5353
{% for c in data.columns %}
5454
{% if c.name and c.cltype %}
55-
{% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}
55+
{% if c.inheritedfromtype and c.has_with_options %}{# Use WITH OPTIONS syntax for modified OF TYPE columns #}{{conn|qtIdent(c.name)}} WITH OPTIONS{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}{% elif c.inheritedfromtable %}{# Inherited from parent table - keep as comment #}-- Inherited from table {{c.inheritedfromtable}}: {{conn|qtIdent(c.name)}}{% else %}{# Regular column or inherited without modifications #}{% if c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}{% endif %}
5656
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
5757
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
5858
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/14_plus/create.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
5454
{% if data.columns and data.columns|length > 0 %}
5555
{% for c in data.columns %}
5656
{% if c.name and c.cltype %}
57-
{% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{% if c.attcompression is defined and c.attcompression is not none and c.attcompression != '' %} COMPRESSION {{c.attcompression}}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}
57+
{% if c.inheritedfromtype and c.has_with_options %}{# Use WITH OPTIONS syntax for modified OF TYPE columns #}{{conn|qtIdent(c.name)}} WITH OPTIONS{% if c.attcompression is defined and c.attcompression is not none and c.attcompression != '' %} COMPRESSION {{c.attcompression}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}{% elif c.inheritedfromtable %}{# Inherited from parent table - keep as comment #}-- Inherited from table {{c.inheritedfromtable}}: {{conn|qtIdent(c.name)}}{% else %}{# Regular column or inherited without modifications #}{% if c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{% if c.attcompression is defined and c.attcompression is not none and c.attcompression != '' %} COMPRESSION {{c.attcompression}}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}{% endif %}
5858
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
5959
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
6060
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/16_plus/create.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
5454
{% if data.columns and data.columns|length > 0 %}
5555
{% for c in data.columns %}
5656
{% if c.name and c.cltype %}
57-
{% if c.inheritedfromtable %}-- Inherited from table {{c.inheritedfromtable}}: {% elif c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{%if c.attstorage is defined and c.attstorage != c.defaultstorage%} STORAGE {%if c.attstorage == 'p' %}PLAIN{% elif c.attstorage == 'm'%}MAIN{% elif c.attstorage == 'e'%}EXTERNAL{% elif c.attstorage == 'x'%}EXTENDED{% elif c.attstorage == 'd'%}DEFAULT{% endif %}{% endif %}{% if c.attcompression is defined and c.attcompression is not none and c.attcompression != '' %} COMPRESSION {{c.attcompression}}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}
57+
{% if c.inheritedfromtype and c.has_with_options %}{# Use WITH OPTIONS syntax for modified OF TYPE columns #}{{conn|qtIdent(c.name)}} WITH OPTIONS{%if c.attstorage is defined and c.attstorage != c.defaultstorage%} STORAGE {%if c.attstorage == 'p' %}PLAIN{% elif c.attstorage == 'm'%}MAIN{% elif c.attstorage == 'e'%}EXTERNAL{% elif c.attstorage == 'x'%}EXTENDED{% elif c.attstorage == 'd'%}DEFAULT{% endif %}{% endif %}{% if c.attcompression is defined and c.attcompression is not none and c.attcompression != '' %} COMPRESSION {{c.attcompression}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}{% elif c.inheritedfromtable %}{# Inherited from parent table - keep as comment #}-- Inherited from table {{c.inheritedfromtable}}: {{conn|qtIdent(c.name)}}{% else %}{# Regular column or inherited without modifications #}{% if c.inheritedfromtype %}-- Inherited from type {{c.inheritedfromtype}}: {% endif %}{{conn|qtIdent(c.name)}} {% if is_sql %}{{c.displaytypname}}{% else %}{{ GET_TYPE.CREATE_TYPE_SQL(conn, c.cltype, c.attlen, c.attprecision, c.hasSqrBracket) }}{% endif %}{% if c.geometry and not is_sql %}({{c.geometry}}{% if c.srid %},{{c.srid}}{% endif %}){% endif %}{%if c.attstorage is defined and c.attstorage != c.defaultstorage%} STORAGE {%if c.attstorage == 'p' %}PLAIN{% elif c.attstorage == 'm'%}MAIN{% elif c.attstorage == 'e'%}EXTERNAL{% elif c.attstorage == 'x'%}EXTENDED{% elif c.attstorage == 'd'%}DEFAULT{% endif %}{% endif %}{% if c.attcompression is defined and c.attcompression is not none and c.attcompression != '' %} COMPRESSION {{c.attcompression}}{% endif %}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.attnotnull %} NOT NULL{% endif %}{% if c.defval is defined and c.defval is not none and c.defval != '' and c.colconstype != 'g' %} DEFAULT {{c.defval}}{% endif %}{% endif %}
5858
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
5959
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
6060
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}

0 commit comments

Comments
 (0)