Add support for type constructors for PostGIS spatial types. #2256
parent
75dc42cf24
commit
b08e90ea88
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev edb-as${{ matrix.pgver }}-server edb-as${{ matrix.pgver }}-server-pldebugger
|
||||
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev edb-as${{ matrix.pgver }}-server edb-as${{ matrix.pgver }}-server-pldebugger edb-as${{ matrix.pgver }}-postgis34
|
||||
|
||||
- name: Install pgagent on Linux
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' && matrix.pgver <= 16 }}
|
||||
|
|
@ -105,6 +105,10 @@ jobs:
|
|||
- name: Create pgagent extension on Linux
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' && matrix.pgver <= 16 }}
|
||||
run: psql -U enterprisedb -d postgres -p 58${{ matrix.pgver }} -c 'CREATE EXTENSION IF NOT EXISTS pgagent;'
|
||||
|
||||
- name: Create postgis extension on Linux
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' && matrix.pgver <= 16 }}
|
||||
run: psql -U enterprisedb -d postgres -p 58${{ matrix.pgver }} -c 'CREATE EXTENSION IF NOT EXISTS postgis;'
|
||||
|
||||
- name: Install Python dependencies on Linux
|
||||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ jobs:
|
|||
if: ${{ matrix.os == 'ubuntu-22.04' }}
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev postgresql-${{ matrix.pgver }} postgresql-${{ matrix.pgver }}-pldebugger pgagent
|
||||
sudo apt install -y libpq-dev libffi-dev libssl-dev libkrb5-dev zlib1g-dev postgresql-${{ matrix.pgver }} postgresql-${{ matrix.pgver }}-pldebugger pgagent postgresql-${{ matrix.pgver }}-postgis-3
|
||||
|
||||
- name: Install platform dependencies on macOS
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
|
|
@ -113,6 +113,7 @@ jobs:
|
|||
|
||||
psql -U postgres -p 59${{ matrix.pgver }} -c 'CREATE EXTENSION pgagent;'
|
||||
psql -U postgres -p 59${{ matrix.pgver }} -c 'CREATE EXTENSION pldbgapi;'
|
||||
psql -U postgres -p 59${{ matrix.pgver }} -c 'CREATE EXTENSION postgis;'
|
||||
|
||||
- name: Start PostgreSQL on macOS
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
|
|
|
|||
|
|
@ -308,6 +308,9 @@ class TableView(BaseTableView, DataTypeReader, SchemaDiffTableCompare):
|
|||
'count_rows': [{'get': 'count_rows'}],
|
||||
'compare': [{'get': 'compare'}, {'get': 'compare'}],
|
||||
'get_op_class': [{'get': 'get_op_class'}, {'get': 'get_op_class'}],
|
||||
'get_geometry_types': [
|
||||
{'get': 'geometry_types'},
|
||||
{'get': 'geometry_types'}],
|
||||
})
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
|
|
@ -695,6 +698,21 @@ class TableView(BaseTableView, DataTypeReader, SchemaDiffTableCompare):
|
|||
status=200
|
||||
)
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
def geometry_types(self, gid, sid, did, scid, tid=None, clid=None):
|
||||
"""
|
||||
Returns:
|
||||
This function will return list of geometry types available for
|
||||
column node for node-ajax-control
|
||||
"""
|
||||
status, types = self.get_geometry_types(self.conn)
|
||||
if not status:
|
||||
return internal_server_error(errormsg=types)
|
||||
return make_json_response(
|
||||
data=types,
|
||||
status=200
|
||||
)
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
def get_columns(self, gid, sid, did, scid, tid=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -24,11 +24,14 @@ export function getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser) {
|
|||
cacheLevel: 'table',
|
||||
}),
|
||||
()=>getNodeAjaxOptions('get_collations', pgBrowser.Nodes['collation'], treeNodeInfo, itemNodeData),
|
||||
()=>getNodeAjaxOptions('get_geometry_types', pgBrowser.Nodes['table'], treeNodeInfo, itemNodeData, {
|
||||
cacheLevel: 'table',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export default class ColumnSchema extends BaseUISchema {
|
||||
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions, inErd=false) {
|
||||
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions, geometryTypes, inErd=false) {
|
||||
super({
|
||||
name: undefined,
|
||||
attowner: undefined,
|
||||
|
|
@ -60,12 +63,15 @@ export default class ColumnSchema extends BaseUISchema {
|
|||
seqcycle: undefined,
|
||||
colconstype: 'n',
|
||||
genexpr: undefined,
|
||||
srid: null,
|
||||
geometry: null,
|
||||
});
|
||||
|
||||
this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
|
||||
this.nodeInfo = nodeInfo;
|
||||
this.cltypeOptions = cltypeOptions;
|
||||
this.collspcnameOptions = collspcnameOptions;
|
||||
this.geometryTypes = geometryTypes;
|
||||
this.inErd = inErd;
|
||||
|
||||
this.datatypes = [];
|
||||
|
|
@ -278,6 +284,42 @@ export default class ColumnSchema extends BaseUISchema {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id:'geometry', label: gettext('Geometry Type'), deps: ['cltype'],
|
||||
group: gettext('Definition'), type: 'select', options: this.geometryTypes,
|
||||
disabled: (state) => {
|
||||
return !(state.cltype == 'geometry' || state.cltype == 'geography');
|
||||
},
|
||||
depChange: (state) => {
|
||||
let cltype = state.cltype;
|
||||
if (cltype != 'geometry' && cltype != 'geography') {
|
||||
return {
|
||||
...state,
|
||||
geometry: null
|
||||
};
|
||||
}
|
||||
},
|
||||
visible: (state) => {
|
||||
return (state.cltype == 'geometry' || state.cltype == 'geography');
|
||||
}
|
||||
},{
|
||||
id:'srid', label: gettext('SRID'), deps: ['cltype'],
|
||||
group: gettext('Definition'), type: 'int',
|
||||
depChange: (state) => {
|
||||
let cltype = state.cltype;
|
||||
if (cltype != 'geometry' && cltype != 'geography') {
|
||||
return {
|
||||
...state,
|
||||
srid: null
|
||||
};
|
||||
}
|
||||
},
|
||||
disabled: function(state) {
|
||||
return !(state.cltype == 'geometry' || state.cltype == 'geography');
|
||||
},
|
||||
visible: function(state) {
|
||||
return (state.cltype == 'geometry' || state.cltype == 'geography');
|
||||
}
|
||||
},{
|
||||
id: 'attlen', label: gettext('Length/Precision'),
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'), width: 120, enableResizing: false,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
|
|||
from pgadmin.browser.server_groups.servers.databases.utils \
|
||||
import make_object_name
|
||||
from functools import wraps
|
||||
import re
|
||||
|
||||
|
||||
def get_template_path(f):
|
||||
|
|
@ -448,19 +449,24 @@ def fetch_length_precision(data):
|
|||
data['attlen'] = None
|
||||
data['attprecision'] = None
|
||||
|
||||
import re
|
||||
if 'typname' in data and (data['typname'] in ('geometry', 'geography')):
|
||||
# If we have geometry column
|
||||
parmas = parse_params(data['cltype'])
|
||||
if parmas:
|
||||
data['geometry'] = parmas[0]
|
||||
data['srid'] = parmas[1]
|
||||
else:
|
||||
parmas = parse_params(fulltype)
|
||||
|
||||
# If we have length & precision both
|
||||
if length and precision:
|
||||
match_obj = re.search(r'(\d+),(\d+)', fulltype)
|
||||
if match_obj:
|
||||
data['attlen'] = match_obj.group(1)
|
||||
data['attprecision'] = match_obj.group(2)
|
||||
if parmas:
|
||||
data['attlen'] = parmas[0]
|
||||
data['attprecision'] = parmas[1]
|
||||
elif length:
|
||||
# If we have length only
|
||||
match_obj = re.search(r'(\d+)', fulltype)
|
||||
if match_obj:
|
||||
data['attlen'] = match_obj.group(1)
|
||||
if parmas:
|
||||
data['attlen'] = parmas[0]
|
||||
data['attprecision'] = None
|
||||
|
||||
return data
|
||||
|
|
@ -474,3 +480,18 @@ def parse_column_variables(col_variables):
|
|||
k, v = spcoption.split('=')
|
||||
spcoptions.append({'name': k, 'value': v})
|
||||
return spcoptions
|
||||
|
||||
|
||||
def parse_params(fulltype):
|
||||
"""
|
||||
This function will fetch length and precision details
|
||||
from fulltype.
|
||||
|
||||
:param fulltype: Full type.
|
||||
:param data: Data.
|
||||
"""
|
||||
|
||||
match_obj = re.search(r'\((\d*[a-zA-Z]*),?(\d*)\)', fulltype)
|
||||
if match_obj:
|
||||
return [match_obj.group(1), match_obj.group(2)]
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
|
|||
{% if data.columns and data.columns|length > 0 %}
|
||||
{% for c in data.columns %}
|
||||
{% if c.name and c.cltype %}
|
||||
{% 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.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 %}
|
||||
{% 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 %}
|
||||
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
|
||||
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
|
||||
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
|
|||
{% if data.columns and data.columns|length > 0 %}
|
||||
{% for c in data.columns %}
|
||||
{% if c.name and c.cltype %}
|
||||
{% 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.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 %}
|
||||
{% 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 %}
|
||||
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
|
||||
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
|
||||
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
|
|||
{% if data.columns and data.columns|length > 0 %}
|
||||
{% for c in data.columns %}
|
||||
{% if c.name and c.cltype %}
|
||||
{% 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.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 %}
|
||||
{% 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 %}
|
||||
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
|
||||
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
|
||||
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
|
|||
{% if data.columns and data.columns|length > 0 %}
|
||||
{% for c in data.columns %}
|
||||
{% if c.name and c.cltype %}
|
||||
{% 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.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 %}
|
||||
{% 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 %}
|
||||
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
|
||||
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
|
||||
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ CREATE {% if data.relpersistence %}UNLOGGED {% endif %}TABLE{% if add_not_exists
|
|||
{% if data.columns and data.columns|length > 0 %}
|
||||
{% for c in data.columns %}
|
||||
{% if c.name and c.cltype %}
|
||||
{% 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.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 %}
|
||||
{% 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 %}
|
||||
{% if c.colconstype == 'i' and c.attidentity and c.attidentity != '' %}
|
||||
{% if c.attidentity == 'a' %} GENERATED ALWAYS AS IDENTITY{% elif c.attidentity == 'd' %} GENERATED BY DEFAULT AS IDENTITY{% endif %}
|
||||
{% if c.seqincrement or c.seqcycle or c.seqincrement or c.seqstart or c.seqmin or c.seqmax or c.seqcache %} ( {% endif %}
|
||||
|
|
|
|||
|
|
@ -2157,5 +2157,52 @@
|
|||
},
|
||||
"is_list": false
|
||||
}
|
||||
],
|
||||
"table_create_geometry": [
|
||||
{
|
||||
"name": "Create: Add Table with geometry columns",
|
||||
"is_positive_test": true,
|
||||
"inventory_data": {},
|
||||
"test_data": {
|
||||
"description": "Create Table API Test",
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"cltype": "bigint",
|
||||
"attacl": [],
|
||||
"is_primary_key": false,
|
||||
"attnotnull": true,
|
||||
"attlen": null,
|
||||
"attprecision": null,
|
||||
"attoptions": [],
|
||||
"seclabels": [],
|
||||
"colconstype": "n",
|
||||
"attidentity": "a"
|
||||
},
|
||||
{
|
||||
"name": "geom",
|
||||
"cltype": "geometry",
|
||||
"attacl": [],
|
||||
"is_primary_key": false,
|
||||
"attnotnull": true,
|
||||
"attlen": null,
|
||||
"attprecision": null,
|
||||
"attoptions": [],
|
||||
"seclabels": [],
|
||||
"colconstype": "n",
|
||||
"attidentity": "a",
|
||||
"geometry": "Point",
|
||||
"srid": "2154"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mocking_required": false,
|
||||
"mock_data": {},
|
||||
"expected_data": {
|
||||
"status_code": 200,
|
||||
"error_msg": null,
|
||||
"test_result_data": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from unittest.mock import patch
|
||||
|
||||
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
|
||||
utils as schema_utils
|
||||
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
|
||||
database_utils
|
||||
from pgadmin.utils import server_utils
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression import parent_node_dict
|
||||
from regression.python_test_utils import test_utils as utils
|
||||
from . import utils as tables_utils
|
||||
|
||||
|
||||
class TableGeometryTestCase(BaseTestGenerator):
|
||||
""" This class will add new collation under schema node. """
|
||||
url = '/browser/table/obj/'
|
||||
|
||||
# Generates scenarios
|
||||
scenarios = utils.generate_scenarios("table_create_geometry",
|
||||
tables_utils.test_cases)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Load test data
|
||||
self.data = self.test_data
|
||||
|
||||
# Check server version
|
||||
schema_info = parent_node_dict["schema"][-1]
|
||||
self.server_id = schema_info["server_id"]
|
||||
|
||||
if "server_min_version" in self.inventory_data:
|
||||
server_con = server_utils.connect_server(self, self.server_id)
|
||||
if not server_con["info"] == "Server connected.":
|
||||
raise Exception("Could not connect to server to add "
|
||||
"partitioned table.")
|
||||
if server_con["data"]["version"] < \
|
||||
self.inventory_data["server_min_version"]:
|
||||
self.skipTest(self.inventory_data["skip_msg"])
|
||||
|
||||
# Create db connection
|
||||
self.db_name = parent_node_dict["database"][-1]["db_name"]
|
||||
self.db_id = schema_info["db_id"]
|
||||
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
|
||||
self.server_id, self.db_id)
|
||||
if not db_con['data']["connected"]:
|
||||
raise Exception("Could not connect to database to add a table.")
|
||||
|
||||
# Create schema
|
||||
self.schema_id = schema_info["schema_id"]
|
||||
self.schema_name = schema_info["schema_name"]
|
||||
schema_response = schema_utils.verify_schemas(self.server,
|
||||
self.db_name,
|
||||
self.schema_name)
|
||||
if not schema_response:
|
||||
raise Exception("Could not find the schema to add a table.")
|
||||
|
||||
tables_utils.create_postgis_extension(self)
|
||||
|
||||
if not tables_utils.is_postgis_present(self):
|
||||
self.skipTest(tables_utils.SKIP_MSG_EXTENSION)
|
||||
|
||||
def runTest(self):
|
||||
""" This function will add table under schema node. """
|
||||
if "table_name" in self.data:
|
||||
self.table_name = self.data["table_name"]
|
||||
else:
|
||||
self.table_name = "test_table_add_%s" % (str(uuid.uuid4())[1:8])
|
||||
|
||||
db_user = self.server["username"]
|
||||
|
||||
# Get the common data
|
||||
self.data.update(tables_utils.get_table_common_data())
|
||||
if self.server_information and \
|
||||
'server_version' in self.server_information and \
|
||||
self.server_information['server_version'] >= 120000:
|
||||
self.data['spcname'] = None
|
||||
self.data.update({
|
||||
"name": self.table_name,
|
||||
"relowner": db_user,
|
||||
"schema": self.schema_name,
|
||||
"relacl": [{
|
||||
"grantee": db_user,
|
||||
"grantor": db_user,
|
||||
"privileges": [{
|
||||
"privilege_type": "a",
|
||||
"privilege": True,
|
||||
"with_grant": True
|
||||
}, {
|
||||
"privilege_type": "r",
|
||||
"privilege": True,
|
||||
"with_grant": False
|
||||
}, {
|
||||
"privilege_type": "w",
|
||||
"privilege": True,
|
||||
"with_grant": False
|
||||
}]
|
||||
}]
|
||||
})
|
||||
|
||||
# Add table
|
||||
if self.is_positive_test:
|
||||
response = tables_utils.api_create(self)
|
||||
|
||||
# Assert response
|
||||
utils.assert_status_code(self, response)
|
||||
|
||||
else:
|
||||
if self.mocking_required:
|
||||
with patch(self.mock_data["function_name"],
|
||||
side_effect=eval(self.mock_data["return_value"])):
|
||||
response = tables_utils.api_create(self)
|
||||
else:
|
||||
if self.table_name == "":
|
||||
del self.data["name"]
|
||||
response = tables_utils.api_create(self)
|
||||
|
||||
# Assert response
|
||||
utils.assert_status_code(self, response)
|
||||
utils.assert_error_message(self, response)
|
||||
|
||||
def tearDown(self):
|
||||
# Drop the extension
|
||||
tables_utils.drop_postgis_extension(self)
|
||||
# Disconnect the database
|
||||
database_utils.disconnect_database(self, self.server_id, self.db_id)
|
||||
|
|
@ -18,6 +18,7 @@ from regression.python_test_utils import test_utils as utils
|
|||
|
||||
# Load test data from json file.
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
SKIP_MSG_EXTENSION = ("PostGIS extension is not installed in the database. ")
|
||||
with open(CURRENT_PATH + "/table_test_data.json") as data_file:
|
||||
test_cases = json.load(data_file)
|
||||
|
||||
|
|
@ -593,3 +594,73 @@ def get_table_id(server, db_name, table_name):
|
|||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
raise
|
||||
|
||||
|
||||
def create_postgis_extension(self):
|
||||
"""
|
||||
This function will create postgis extension in the database.
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
connection = utils.get_db_connection(self.db_name,
|
||||
self.server['username'],
|
||||
self.server['db_password'],
|
||||
self.server['host'],
|
||||
self.server['port'],
|
||||
self.server['sslmode'])
|
||||
pg_cursor = connection.cursor()
|
||||
# Check if postgis extension is available to install
|
||||
pg_cursor.execute('''SELECT COUNT(*) FROM pg_available_extensions WHERE
|
||||
name ='postgis' ''')
|
||||
res = pg_cursor.fetchone()
|
||||
if res and len(res) > 0 and int(res[0]) == 1:
|
||||
pg_cursor.execute('''CREATE EXTENSION IF NOT EXISTS postgis''')
|
||||
connection.commit()
|
||||
connection.close()
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def drop_postgis_extension(self):
|
||||
"""
|
||||
This function will drop postgis extension in the database.
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
connection = utils.get_db_connection(self.db_name,
|
||||
self.server['username'],
|
||||
self.server['db_password'],
|
||||
self.server['host'],
|
||||
self.server['port'],
|
||||
self.server['sslmode'])
|
||||
pg_cursor = connection.cursor()
|
||||
pg_cursor.execute('''DROP EXTENSION IF EXISTS postgis''')
|
||||
connection.commit()
|
||||
connection.close()
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
|
||||
def is_postgis_present(self):
|
||||
"""
|
||||
This function will check if postgis extension is present in the
|
||||
database.
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
connection = utils.get_db_connection(self.db_name,
|
||||
self.server['username'],
|
||||
self.server['db_password'],
|
||||
self.server['host'],
|
||||
self.server['port'],
|
||||
self.server['sslmode'])
|
||||
pg_cursor = connection.cursor()
|
||||
res = utils.check_extension_exists(pg_cursor, 'postgis')
|
||||
connection.close()
|
||||
if res and len(res) > 0 and int(res[0]) == 1:
|
||||
return True
|
||||
except Exception:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from flask import render_template
|
|||
from pgadmin.browser.collection import CollectionNodeModule
|
||||
from pgadmin.utils.ajax import internal_server_error
|
||||
from pgadmin.utils.driver import get_driver
|
||||
from pgadmin.utils import check_extension_exists
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
from pgadmin.utils.constants import DATATYPE_TIME_WITH_TIMEZONE,\
|
||||
DATATYPE_TIME_WITHOUT_TIMEZONE,\
|
||||
|
|
@ -192,6 +193,31 @@ class DataTypeReader:
|
|||
|
||||
return True, res
|
||||
|
||||
def get_geometry_types(self, conn):
|
||||
"""
|
||||
Returns geometry types.
|
||||
Args:
|
||||
conn: Connection Object
|
||||
"""
|
||||
types = []
|
||||
status, res = check_extension_exists(conn, 'postgis')
|
||||
|
||||
if not status:
|
||||
return status, res
|
||||
|
||||
if res:
|
||||
sql = '''SELECT postgis_typmod_type(i) FROM
|
||||
generate_series(4, 63) AS i;'''
|
||||
status, rset = conn.execute_2darray(sql)
|
||||
if not status:
|
||||
return status, rset
|
||||
for row in rset['rows']:
|
||||
types.append({
|
||||
'label': row['postgis_typmod_type'],
|
||||
'value': row['postgis_typmod_type']
|
||||
})
|
||||
return True, types
|
||||
|
||||
@staticmethod
|
||||
def get_length_precision(elemoid_or_name, typname=None):
|
||||
precision = False
|
||||
|
|
|
|||
|
|
@ -588,10 +588,16 @@ def prequisite(trans_id, sgid, sid, did):
|
|||
if not status:
|
||||
return internal_server_error(errormsg=schemas)
|
||||
|
||||
status, types = helper.get_geometry_types()
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=types)
|
||||
|
||||
return make_json_response(
|
||||
data={
|
||||
'col_types': col_types,
|
||||
'schemas': schemas['rows']
|
||||
'schemas': schemas['rows'],
|
||||
'geometry_types': types
|
||||
},
|
||||
status=200
|
||||
)
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ export default class ERDTool extends React.Component {
|
|||
this.erdDialogs.showTableDialog({
|
||||
title, attributes, isNew, tableNodes: this.diagram.getModel().getNodesDict(),
|
||||
colTypes: this.diagram.getCache('colTypes'), schemas: this.diagram.getCache('schemas'),
|
||||
serverInfo, callback
|
||||
geometryTypes: this.diagram.getCache('geometryTypes'),serverInfo, callback
|
||||
});
|
||||
};
|
||||
} else if(dialogName === 'onetomany_dialog' || dialogName === 'manytomany_dialog' || dialogName === 'onetoone_dialog') {
|
||||
|
|
@ -875,6 +875,7 @@ export default class ERDTool extends React.Component {
|
|||
let data = response.data.data;
|
||||
this.diagram.setCache('colTypes', data['col_types']);
|
||||
this.diagram.setCache('schemas', data['schemas']);
|
||||
this.diagram.setCache('geometryTypes', data['geometry_types']);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleAxiosCatch(error);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default class ERDDialogs {
|
|||
showTableDialog(params) {
|
||||
let schema = getTableDialogSchema(
|
||||
params.attributes, params.isNew, params.tableNodes,
|
||||
params.colTypes, params.schemas);
|
||||
params.colTypes, params.schemas, params.geometryTypes);
|
||||
this.modal.showModal(params.title, (closeModal)=>{
|
||||
return (
|
||||
<SchemaView
|
||||
|
|
|
|||
|
|
@ -309,7 +309,11 @@ export class TableNodeWidget extends React.Component {
|
|||
|
||||
let cltype = col.cltype;
|
||||
if(col.attlen) {
|
||||
cltype += '('+ col.attlen + (col.attprecision ? ',' + col.attprecision : '') +')';
|
||||
cltype += `(${col.attlen}${col.attprecision ? ','+col.attprecision :''})`;
|
||||
}
|
||||
|
||||
if(col.geometry) {
|
||||
cltype += `(${col.geometry}${col.srid ? ','+col.srid : ''})`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ class ERDTableView(BaseTableView, DataTypeReader):
|
|||
condition = self.get_types_condition_sql(False)
|
||||
return DataTypeReader.get_types(self, self.conn, condition, True)
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
def get_geometry_types(self, conn_id=None, did=None, sid=None):
|
||||
return DataTypeReader.get_geometry_types(self, self.conn)
|
||||
|
||||
@BaseTableView.check_precondition
|
||||
def fetch_all_tables(self, did=None, sid=None, scid=None):
|
||||
all_tables = []
|
||||
|
|
@ -109,6 +113,11 @@ class ERDHelper:
|
|||
return self.table_view.get_types(
|
||||
conn_id=self.conn_id, did=self.did, sid=self.sid)
|
||||
|
||||
def get_geometry_types(self):
|
||||
return self.table_view.get_geometry_types(
|
||||
conn_id=self.conn_id, did=self.did, sid=self.sid
|
||||
)
|
||||
|
||||
def get_table_sql(self, data, with_drop=False):
|
||||
SQL, _ = self.table_view.sql(
|
||||
conn_id=self.conn_id, did=self.did, sid=self.sid,
|
||||
|
|
|
|||
|
|
@ -984,3 +984,16 @@ def get_safe_post_logout_redirect():
|
|||
if url.startswith(item):
|
||||
return url
|
||||
return url_for('security.login')
|
||||
|
||||
|
||||
def check_extension_exists(conn, extension_name):
|
||||
sql = f"SELECT * FROM pg_extension WHERE extname = '{extension_name}'"
|
||||
status, res = conn.execute_scalar(sql)
|
||||
if status:
|
||||
if res:
|
||||
return status, True
|
||||
else:
|
||||
return status, False
|
||||
else:
|
||||
# If the query fails, we assume the extension does not exist
|
||||
return status, res
|
||||
|
|
|
|||
|
|
@ -1885,3 +1885,10 @@ def module_patch(*args):
|
|||
|
||||
# did not find a module, just return the default mock
|
||||
return mock.patch(*args)
|
||||
|
||||
|
||||
def check_extension_exists(cursor, extension_name):
|
||||
cursor.execute(f"""SELECT COUNT(*) FROM pg_extension
|
||||
WHERE extname='{extension_name}'""")
|
||||
res = cursor.fetchone()
|
||||
return res
|
||||
|
|
|
|||
Loading…
Reference in New Issue