pull/6/head
parent
0c5a5ba9c2
commit
55254a649f
|
@ -16,11 +16,11 @@
|
||||||
"13": ["", "false", "bool"],
|
"13": ["", "false", "bool"],
|
||||||
"14": ["", "[null]", "text[]"],
|
"14": ["", "[null]", "text[]"],
|
||||||
"15": ["{}", "{}", "text[]"],
|
"15": ["{}", "{}", "text[]"],
|
||||||
"16": ["{data,,'',\"\",\\'\\',\\\"\\\"}", "{data,[null],,,'',\"\"}", "text[]"],
|
"16": ["{data,NULL,'',\"\"}", "{data,NULL,'',\"\"}", "text[]"],
|
||||||
"17": ["{}", "{}", "int[]"],
|
"17": ["{}", "{}", "int[]"],
|
||||||
"18": ["{123,,456}", "{123,[null],456}", "int[]"],
|
"18": ["{123,,456}", "{123,NULL,456}", "int[]"],
|
||||||
"19": ["", "[null]", "boolean[]"],
|
"19": ["", "[null]", "boolean[]"],
|
||||||
"20": ["{false,,true}", "{false,[null],true}", "boolean[]"]
|
"20": ["{false,null,true}", "{f,NULL,t}", "boolean[]"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,28 +210,13 @@
|
||||||
$input.select();
|
$input.select();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var data = [];
|
$input.val(defaultValue = item[args.column.field]);
|
||||||
for (var k in item[args.column.field]) {
|
$input.select();
|
||||||
if (_.isUndefined(item[args.column.field][k]) || _.isNull(item[args.column.field][k])) {
|
|
||||||
data.push('');
|
|
||||||
} else if (item[args.column.field][k] === "") {
|
|
||||||
data.push("''");
|
|
||||||
} else if (item[args.column.field][k] === "''") {
|
|
||||||
data.push("\\'\\'");
|
|
||||||
} else if (item[args.column.field][k] === '""') {
|
|
||||||
data.push('\\"\\"');
|
|
||||||
} else {
|
|
||||||
data.push(item[args.column.field][k]);
|
|
||||||
$input.select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultValue = data;
|
|
||||||
$input.val('{' + data.join() +'}');
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serializeValue = function () {
|
this.serializeValue = function () {
|
||||||
|
|
||||||
var value = $input.val();
|
var value = $input.val();
|
||||||
// If empty return null
|
// If empty return null
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
|
@ -249,31 +234,7 @@
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
return $.trim(value);
|
||||||
// Remove leading { and trailing }.
|
|
||||||
// Also remove leading and trailing whitespaces.
|
|
||||||
var value = $.trim(value.slice(1, -1));
|
|
||||||
|
|
||||||
if(value == '') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = [];
|
|
||||||
value = value.split(',');
|
|
||||||
for (var k in value) {
|
|
||||||
if (value[k] == "") {
|
|
||||||
data.push(null); //empty string from editor is null value.
|
|
||||||
} else if (value[k] === "''" || value[k] === '""') {
|
|
||||||
data.push(''); // double quote from editor is blank string;
|
|
||||||
} else if (value[k] === "\\'\\'") {
|
|
||||||
data.push("''");
|
|
||||||
} else if (value[k] === '\\"\\"') {
|
|
||||||
data.push('""');
|
|
||||||
} else {
|
|
||||||
data.push(value[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -943,14 +904,16 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serializeValue = function () {
|
this.serializeValue = function () {
|
||||||
if ($input.val() === "") {
|
var value = $input.val();
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(args.column.is_array) {
|
if(args.column.is_array) {
|
||||||
// Remove leading { and trailing }.
|
// Remove leading { and trailing }.
|
||||||
// Also remove leading and trailing whitespaces.
|
// Also remove leading and trailing whitespaces.
|
||||||
var val = $.trim($input.val().slice(1, -1));
|
var val = $.trim(value.slice(1, -1));
|
||||||
|
|
||||||
if(val == '') {
|
if(val == '') {
|
||||||
return [];
|
return [];
|
||||||
|
@ -964,7 +927,7 @@
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $input.val();
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.applyValue = function (item, state) {
|
this.applyValue = function (item, state) {
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
"Checkmark": CheckmarkFormatter,
|
"Checkmark": CheckmarkFormatter,
|
||||||
"Text": TextFormatter,
|
"Text": TextFormatter,
|
||||||
"Binary": BinaryFormatter,
|
"Binary": BinaryFormatter,
|
||||||
"JsonStringArray": JsonArrayFormatter,
|
|
||||||
"NumbersArray": NumbersArrayFormatter,
|
|
||||||
"TextArray": TextArrayFormatter,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -73,36 +70,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function JsonArrayFormatter(row, cell, value, columnDef, dataContext) {
|
|
||||||
// If column has default value, set placeholder
|
|
||||||
var data = NullAndDefaultFormatter(row, cell, value, columnDef, dataContext);
|
|
||||||
if (data) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
var data = [];
|
|
||||||
for (var k in value) {
|
|
||||||
// Stringify only if it's json object
|
|
||||||
var v = value[k];
|
|
||||||
if (typeof v === "object" && !Array.isArray(v)) {
|
|
||||||
return data.push(_.escape(JSON.stringify(v)));
|
|
||||||
} else if (Array.isArray(v)) {
|
|
||||||
var temp = [];
|
|
||||||
$.each(v, function(i, val) {
|
|
||||||
if (typeof val === "object") {
|
|
||||||
temp.push(JSON.stringify(val));
|
|
||||||
} else {
|
|
||||||
temp.push(val)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return data.push(_.escape("[" + temp.join() + "]"));
|
|
||||||
} else {
|
|
||||||
return data.push(_.escape(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '{' + data.join() + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function NumbersFormatter(row, cell, value, columnDef, dataContext) {
|
function NumbersFormatter(row, cell, value, columnDef, dataContext) {
|
||||||
// If column has default value, set placeholder
|
// If column has default value, set placeholder
|
||||||
var data = NullAndDefaultNumberFormatter(row, cell, value, columnDef, dataContext);
|
var data = NullAndDefaultNumberFormatter(row, cell, value, columnDef, dataContext);
|
||||||
|
@ -113,24 +80,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function NumbersArrayFormatter(row, cell, value, columnDef, dataContext) {
|
|
||||||
// If column has default value, set placeholder
|
|
||||||
var data = NullAndDefaultNumberFormatter(row, cell, value, columnDef, dataContext);
|
|
||||||
if (data) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
data = [];
|
|
||||||
for(var k in value) {
|
|
||||||
if (value[k] == null) {
|
|
||||||
data.push("<span class='disabled_cell'>[null]</span>");
|
|
||||||
} else {
|
|
||||||
data.push(_.escape(value[k]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "<span style='float:right'>{" + data.join() + "}</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
|
function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
|
||||||
/* Checkbox has 3 states
|
/* Checkbox has 3 states
|
||||||
* 1) checked=true
|
* 1) checked=true
|
||||||
|
@ -155,24 +104,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextArrayFormatter(row, cell, value, columnDef, dataContext) {
|
|
||||||
// If column has default value, set placeholder
|
|
||||||
var data = NullAndDefaultFormatter(row, cell, value, columnDef, dataContext);
|
|
||||||
if (data) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
data = [];
|
|
||||||
for(var k in value) {
|
|
||||||
if (value[k] === null) {
|
|
||||||
data.push("<span class='disabled_cell'>[null]</span>");
|
|
||||||
} else {
|
|
||||||
data.push(_.escape(value[k]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "{" + data.join() + "}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function BinaryFormatter(row, cell, value, columnDef, dataContext) {
|
function BinaryFormatter(row, cell, value, columnDef, dataContext) {
|
||||||
// If column has default value, set placeholder
|
// If column has default value, set placeholder
|
||||||
var data = NullAndDefaultFormatter(row, cell, value, columnDef, dataContext);
|
var data = NullAndDefaultFormatter(row, cell, value, columnDef, dataContext);
|
||||||
|
|
|
@ -119,7 +119,8 @@ def initialize_datagrid(cmd_type, obj_type, sid, did, obj_id):
|
||||||
try:
|
try:
|
||||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
|
||||||
conn = manager.connection(did=did, conn_id=conn_id,
|
conn = manager.connection(did=did, conn_id=conn_id,
|
||||||
use_binary_placeholder=True)
|
use_binary_placeholder=True,
|
||||||
|
array_to_string=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return internal_server_error(errormsg=str(e))
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
|
|
@ -379,7 +379,8 @@ def check_transaction_status(trans_id):
|
||||||
try:
|
try:
|
||||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
|
||||||
conn = manager.connection(did=trans_obj.did, conn_id=trans_obj.conn_id,
|
conn = manager.connection(did=trans_obj.did, conn_id=trans_obj.conn_id,
|
||||||
use_binary_placeholder=True)
|
use_binary_placeholder=True,
|
||||||
|
array_to_string=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, internal_server_error(errormsg=str(e)), None, None, None
|
return False, internal_server_error(errormsg=str(e)), None, None, None
|
||||||
|
|
||||||
|
@ -526,7 +527,8 @@ def start_query_tool(trans_id):
|
||||||
try:
|
try:
|
||||||
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(trans_obj.sid)
|
||||||
conn = manager.connection(did=trans_obj.did, conn_id=conn_id,
|
conn = manager.connection(did=trans_obj.did, conn_id=conn_id,
|
||||||
use_binary_placeholder=True)
|
use_binary_placeholder=True,
|
||||||
|
array_to_string=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return internal_server_error(errormsg=str(e))
|
return internal_server_error(errormsg=str(e))
|
||||||
|
|
||||||
|
@ -641,7 +643,6 @@ def preferences(trans_id):
|
||||||
return success_return()
|
return success_return()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/poll/<int:trans_id>', methods=["GET"], endpoint='poll')
|
@blueprint.route('/poll/<int:trans_id>', methods=["GET"], endpoint='poll')
|
||||||
@login_required
|
@login_required
|
||||||
def poll(trans_id):
|
def poll(trans_id):
|
||||||
|
|
|
@ -605,24 +605,24 @@ define('tools.querytool', [
|
||||||
else if (c.cell == 'Json') {
|
else if (c.cell == 'Json') {
|
||||||
options['editor'] = is_editable ? Slick.Editors.JsonText
|
options['editor'] = is_editable ? Slick.Editors.JsonText
|
||||||
: Slick.Editors.ReadOnlyJsonText;
|
: Slick.Editors.ReadOnlyJsonText;
|
||||||
options['formatter'] = c.is_array ? Slick.Formatters.JsonStringArray : Slick.Formatters.JsonString;
|
options['formatter'] = Slick.Formatters.JsonString;
|
||||||
} else if (c.cell == 'number' ||
|
} else if (c.cell == 'number' ||
|
||||||
$.inArray(c.type, ['oid', 'xid', 'real']) !== -1
|
$.inArray(c.type, ['oid', 'xid', 'real']) !== -1
|
||||||
) {
|
) {
|
||||||
options['editor'] = is_editable ? Slick.Editors.CustomNumber
|
options['editor'] = is_editable ? Slick.Editors.CustomNumber
|
||||||
: Slick.Editors.ReadOnlyText;
|
: Slick.Editors.ReadOnlyText;
|
||||||
options['formatter'] = c.is_array ? Slick.Formatters.NumbersArray : Slick.Formatters.Numbers;
|
options['formatter'] = Slick.Formatters.Numbers;
|
||||||
} else if (c.cell == 'boolean') {
|
} else if (c.cell == 'boolean') {
|
||||||
options['editor'] = is_editable ? Slick.Editors.Checkbox
|
options['editor'] = is_editable ? Slick.Editors.Checkbox
|
||||||
: Slick.Editors.ReadOnlyCheckbox;
|
: Slick.Editors.ReadOnlyCheckbox;
|
||||||
options['formatter'] = c.is_array ? Slick.Formatters.CheckmarkArray : Slick.Formatters.Checkmark;
|
options['formatter'] = Slick.Formatters.Checkmark;
|
||||||
} else if (c.cell == 'binary') {
|
} else if (c.cell == 'binary') {
|
||||||
// We do not support editing binary data in SQL editor and data grid.
|
// We do not support editing binary data in SQL editor and data grid.
|
||||||
options['formatter'] = Slick.Formatters.Binary;
|
options['formatter'] = Slick.Formatters.Binary;
|
||||||
}else {
|
}else {
|
||||||
options['editor'] = is_editable ? Slick.Editors.pgText
|
options['editor'] = is_editable ? Slick.Editors.pgText
|
||||||
: Slick.Editors.ReadOnlypgText;
|
: Slick.Editors.ReadOnlypgText;
|
||||||
options['formatter'] = c.is_array ? Slick.Formatters.TextArray : Slick.Formatters.Text;
|
options['formatter'] = Slick.Formatters.Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
grid_columns.push(options)
|
grid_columns.push(options)
|
||||||
|
|
|
@ -21,7 +21,6 @@ import sys
|
||||||
|
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extras
|
|
||||||
from flask import g, current_app, session
|
from flask import g, current_app, session
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
from flask_security import current_user
|
from flask_security import current_user
|
||||||
|
@ -34,14 +33,16 @@ from pgadmin.utils.exception import ConnectionLost
|
||||||
from .keywords import ScanKeyword
|
from .keywords import ScanKeyword
|
||||||
from ..abstract import BaseDriver, BaseConnection
|
from ..abstract import BaseDriver, BaseConnection
|
||||||
from .cursor import DictCursor
|
from .cursor import DictCursor
|
||||||
|
from .typecast import register_global_typecasters, register_string_typecasters,\
|
||||||
|
register_binary_typecasters, register_array_to_string_typecasters,\
|
||||||
|
ALL_JSON_TYPES
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3,):
|
if sys.version_info < (3,):
|
||||||
# Python2 in-built csv module do not handle unicode
|
# Python2 in-built csv module do not handle unicode
|
||||||
# backports.csv module ported from PY3 csv module for unicode handling
|
# backports.csv module ported from PY3 csv module for unicode handling
|
||||||
from backports import csv
|
from backports import csv
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
|
||||||
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
|
|
||||||
IS_PY2 = True
|
IS_PY2 = True
|
||||||
else:
|
else:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
@ -50,134 +51,9 @@ else:
|
||||||
|
|
||||||
_ = gettext
|
_ = gettext
|
||||||
|
|
||||||
unicode_type_for_record = psycopg2.extensions.new_type(
|
|
||||||
(2249,),
|
|
||||||
"RECORD",
|
|
||||||
psycopg2.extensions.UNICODE
|
|
||||||
)
|
|
||||||
|
|
||||||
unicode_array_type_for_record_array = psycopg2.extensions.new_array_type(
|
# Register global type caster which will be applicable to all connections.
|
||||||
(2287,),
|
register_global_typecasters()
|
||||||
"ARRAY_RECORD",
|
|
||||||
unicode_type_for_record
|
|
||||||
)
|
|
||||||
|
|
||||||
# This registers a unicode type caster for datatype 'RECORD'.
|
|
||||||
psycopg2.extensions.register_type(unicode_type_for_record)
|
|
||||||
|
|
||||||
# This registers a array unicode type caster for datatype 'ARRAY_RECORD'.
|
|
||||||
psycopg2.extensions.register_type(unicode_array_type_for_record_array)
|
|
||||||
|
|
||||||
|
|
||||||
# define type caster to convert various pg types into string type
|
|
||||||
pg_types_to_string_type = psycopg2.extensions.new_type(
|
|
||||||
(
|
|
||||||
# To cast bytea, interval type
|
|
||||||
17, 1186,
|
|
||||||
|
|
||||||
# to cast int4range, int8range, numrange tsrange, tstzrange, daterange
|
|
||||||
3904, 3926, 3906, 3908, 3910, 3912, 3913,
|
|
||||||
|
|
||||||
# date, timestamp, timestamptz, bigint, double precision
|
|
||||||
1700, 1082, 1114, 1184, 20, 701,
|
|
||||||
|
|
||||||
# real, time without time zone
|
|
||||||
700, 1083, 1183
|
|
||||||
),
|
|
||||||
'TYPECAST_TO_STRING', psycopg2.STRING
|
|
||||||
)
|
|
||||||
|
|
||||||
# define type caster to convert pg array types of above types into
|
|
||||||
# array of string type
|
|
||||||
pg_array_types_to_array_of_string_type = psycopg2.extensions.new_array_type(
|
|
||||||
(
|
|
||||||
# To cast bytea[] type
|
|
||||||
1001,
|
|
||||||
|
|
||||||
# bigint[]
|
|
||||||
1016,
|
|
||||||
|
|
||||||
# double precision[], real[]
|
|
||||||
1022, 1021
|
|
||||||
),
|
|
||||||
'TYPECAST_TO_ARRAY_OF_STRING', pg_types_to_string_type
|
|
||||||
)
|
|
||||||
|
|
||||||
# This registers a type caster to convert various pg types into string type
|
|
||||||
psycopg2.extensions.register_type(pg_types_to_string_type)
|
|
||||||
|
|
||||||
# This registers a type caster to convert various pg array types into
|
|
||||||
# array of string type
|
|
||||||
psycopg2.extensions.register_type(pg_array_types_to_array_of_string_type)
|
|
||||||
|
|
||||||
|
|
||||||
def register_string_typecasters(connection):
|
|
||||||
if connection.encoding != 'UTF8':
|
|
||||||
# In python3 when database encoding is other than utf-8 and client
|
|
||||||
# encoding is set to UNICODE then we need to map data from database
|
|
||||||
# encoding to utf-8.
|
|
||||||
# This is required because when client encoding is set to UNICODE then
|
|
||||||
# psycopg assumes database encoding utf-8 and not the actual encoding.
|
|
||||||
# Not sure whether it's bug or feature in psycopg for python3.
|
|
||||||
if sys.version_info >= (3,):
|
|
||||||
def return_as_unicode(value, cursor):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
# Treat value as byte sequence of database encoding and then
|
|
||||||
# decode it as utf-8 to get correct unicode value.
|
|
||||||
return bytes(
|
|
||||||
value, encodings[cursor.connection.encoding]
|
|
||||||
).decode('utf-8')
|
|
||||||
|
|
||||||
unicode_type = psycopg2.extensions.new_type(
|
|
||||||
# "char", name, text, character, character varying
|
|
||||||
(19, 18, 25, 1042, 1043, 0),
|
|
||||||
'UNICODE', return_as_unicode)
|
|
||||||
else:
|
|
||||||
def return_as_unicode(value, cursor):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
# Decode it as utf-8 to get correct unicode value.
|
|
||||||
return value.decode('utf-8')
|
|
||||||
|
|
||||||
unicode_type = psycopg2.extensions.new_type(
|
|
||||||
# "char", name, text, character, character varying
|
|
||||||
(19, 18, 25, 1042, 1043, 0),
|
|
||||||
'UNICODE', return_as_unicode)
|
|
||||||
|
|
||||||
unicode_array_type = psycopg2.extensions.new_array_type(
|
|
||||||
# "char"[], name[], text[], character[], character varying[]
|
|
||||||
(1002, 1003, 1009, 1014, 1015, 0
|
|
||||||
), 'UNICODEARRAY', unicode_type)
|
|
||||||
|
|
||||||
psycopg2.extensions.register_type(unicode_type)
|
|
||||||
psycopg2.extensions.register_type(unicode_array_type)
|
|
||||||
|
|
||||||
|
|
||||||
def register_binary_typecasters(connection):
|
|
||||||
psycopg2.extensions.register_type(
|
|
||||||
psycopg2.extensions.new_type(
|
|
||||||
(
|
|
||||||
# To cast bytea type
|
|
||||||
17,
|
|
||||||
),
|
|
||||||
'BYTEA_PLACEHOLDER',
|
|
||||||
# Only show placeholder if data actually exists.
|
|
||||||
lambda value, cursor: 'binary data' if value is not None else None),
|
|
||||||
connection
|
|
||||||
)
|
|
||||||
|
|
||||||
psycopg2.extensions.register_type(
|
|
||||||
psycopg2.extensions.new_type(
|
|
||||||
(
|
|
||||||
# To cast bytea[] type
|
|
||||||
1001,
|
|
||||||
),
|
|
||||||
'BYTEA_ARRAY_PLACEHOLDER',
|
|
||||||
# Only show placeholder if data actually exists.
|
|
||||||
lambda value, cursor: 'binary data[]' if value is not None else None),
|
|
||||||
connection
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Connection(BaseConnection):
|
class Connection(BaseConnection):
|
||||||
|
@ -262,7 +138,7 @@ class Connection(BaseConnection):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, manager, conn_id, db, auto_reconnect=True, async=0,
|
def __init__(self, manager, conn_id, db, auto_reconnect=True, async=0,
|
||||||
use_binary_placeholder=False):
|
use_binary_placeholder=False, array_to_string=False):
|
||||||
assert (manager is not None)
|
assert (manager is not None)
|
||||||
assert (conn_id is not None)
|
assert (conn_id is not None)
|
||||||
|
|
||||||
|
@ -284,6 +160,7 @@ class Connection(BaseConnection):
|
||||||
# This flag indicates the connection reconnecting status.
|
# This flag indicates the connection reconnecting status.
|
||||||
self.reconnecting = False
|
self.reconnecting = False
|
||||||
self.use_binary_placeholder = use_binary_placeholder
|
self.use_binary_placeholder = use_binary_placeholder
|
||||||
|
self.array_to_string = array_to_string
|
||||||
|
|
||||||
super(Connection, self).__init__()
|
super(Connection, self).__init__()
|
||||||
|
|
||||||
|
@ -302,6 +179,7 @@ class Connection(BaseConnection):
|
||||||
res['async'] = self.async
|
res['async'] = self.async
|
||||||
res['wasConnected'] = self.wasConnected
|
res['wasConnected'] = self.wasConnected
|
||||||
res['use_binary_placeholder'] = self.use_binary_placeholder
|
res['use_binary_placeholder'] = self.use_binary_placeholder
|
||||||
|
res['array_to_string'] = self.array_to_string
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -469,6 +347,11 @@ Failed to connect to the database server(#{server_id}) for connection ({conn_id}
|
||||||
|
|
||||||
register_string_typecasters(self.conn)
|
register_string_typecasters(self.conn)
|
||||||
|
|
||||||
|
if self.array_to_string:
|
||||||
|
register_array_to_string_typecasters(self.conn)
|
||||||
|
|
||||||
|
# Register type casters for binary data only after registering array to
|
||||||
|
# string type casters.
|
||||||
if self.use_binary_placeholder:
|
if self.use_binary_placeholder:
|
||||||
register_binary_typecasters(self.conn)
|
register_binary_typecasters(self.conn)
|
||||||
|
|
||||||
|
@ -799,15 +682,13 @@ WHERE
|
||||||
json_columns = []
|
json_columns = []
|
||||||
conn_encoding = cur.connection.encoding
|
conn_encoding = cur.connection.encoding
|
||||||
|
|
||||||
# json, jsonb, json[], jsonb[]
|
|
||||||
json_types = (114, 199, 3802, 3807)
|
|
||||||
for c in cur.ordered_description():
|
for c in cur.ordered_description():
|
||||||
# This is to handle the case in which column name is non-ascii
|
# This is to handle the case in which column name is non-ascii
|
||||||
column_name = c.to_dict()['name']
|
column_name = c.to_dict()['name']
|
||||||
if IS_PY2:
|
if IS_PY2:
|
||||||
column_name = column_name.decode(conn_encoding)
|
column_name = column_name.decode(conn_encoding)
|
||||||
header.append(column_name)
|
header.append(column_name)
|
||||||
if c.to_dict()['type_code'] in json_types:
|
if c.to_dict()['type_code'] in ALL_JSON_TYPES:
|
||||||
json_columns.append(column_name)
|
json_columns.append(column_name)
|
||||||
|
|
||||||
if IS_PY2:
|
if IS_PY2:
|
||||||
|
@ -1801,7 +1682,7 @@ class ServerManager(object):
|
||||||
|
|
||||||
def connection(
|
def connection(
|
||||||
self, database=None, conn_id=None, auto_reconnect=True, did=None,
|
self, database=None, conn_id=None, auto_reconnect=True, did=None,
|
||||||
async=None, use_binary_placeholder=False
|
async=None, use_binary_placeholder=False, array_to_string=False
|
||||||
):
|
):
|
||||||
if database is not None:
|
if database is not None:
|
||||||
if hasattr(str, 'decode') and \
|
if hasattr(str, 'decode') and \
|
||||||
|
@ -1856,7 +1737,8 @@ WHERE db.oid = {0}""".format(did))
|
||||||
async = 1 if async is True else 0
|
async = 1 if async is True else 0
|
||||||
self.connections[my_id] = Connection(
|
self.connections[my_id] = Connection(
|
||||||
self, my_id, database, auto_reconnect, async,
|
self, my_id, database, auto_reconnect, async,
|
||||||
use_binary_placeholder=use_binary_placeholder
|
use_binary_placeholder=use_binary_placeholder,
|
||||||
|
array_to_string=array_to_string
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.connections[my_id]
|
return self.connections[my_id]
|
||||||
|
@ -1886,7 +1768,8 @@ WHERE db.oid = {0}""".format(did))
|
||||||
conn = self.connections[conn_info['conn_id']] = Connection(
|
conn = self.connections[conn_info['conn_id']] = Connection(
|
||||||
self, conn_info['conn_id'], conn_info['database'],
|
self, conn_info['conn_id'], conn_info['database'],
|
||||||
True, conn_info['async'],
|
True, conn_info['async'],
|
||||||
use_binary_placeholder=conn_info['use_binary_placeholder']
|
use_binary_placeholder=conn_info['use_binary_placeholder'],
|
||||||
|
array_to_string=conn_info['array_to_string']
|
||||||
)
|
)
|
||||||
# only try to reconnect if connection was connected previously.
|
# only try to reconnect if connection was connected previously.
|
||||||
if conn_info['wasConnected']:
|
if conn_info['wasConnected']:
|
||||||
|
|
|
@ -72,7 +72,7 @@ class _WrapperColumn(object):
|
||||||
def __getitem__(self, idx):
|
def __getitem__(self, idx):
|
||||||
"""Overrides __getitem__ to fetch item from original object"""
|
"""Overrides __getitem__ to fetch item from original object"""
|
||||||
if idx == 0 and self.dummy_name is not None:
|
if idx == 0 and self.dummy_name is not None:
|
||||||
return self.name
|
return self.dummy_name
|
||||||
return self.orig_col.__getitem__(idx)
|
return self.orig_col.__getitem__(idx)
|
||||||
|
|
||||||
def __setitem__(self, *args, **kwargs):
|
def __setitem__(self, *args, **kwargs):
|
||||||
|
@ -200,7 +200,7 @@ class DictCursor(_cursor):
|
||||||
|
|
||||||
def fetchall(self):
|
def fetchall(self):
|
||||||
"""
|
"""
|
||||||
Fetch all tuples as orderd dictionary list.
|
Fetch all tuples as ordered dictionary list.
|
||||||
"""
|
"""
|
||||||
tuples = _cursor.fetchall(self)
|
tuples = _cursor.fetchall(self)
|
||||||
if tuples is not None:
|
if tuples is not None:
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2017, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Typecast various data types so that they can be compatible with Javascript
|
||||||
|
data types.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from psycopg2 import STRING as _STRING
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2.extensions import encodings
|
||||||
|
|
||||||
|
|
||||||
|
# OIDs of data types which need to typecast as string to avoid JavaScript
|
||||||
|
# compatibility issues.
|
||||||
|
# e.g JavaScript does not support 64 bit integers. It has 64-bit double
|
||||||
|
# giving only 53 bits of integer range (IEEE 754)
|
||||||
|
# So to avoid loss of remaining 11 bits (64-53) we need to typecast bigint to
|
||||||
|
# string.
|
||||||
|
|
||||||
|
TO_STRING_DATATYPES = (
|
||||||
|
# To cast bytea, interval type
|
||||||
|
17, 1186,
|
||||||
|
|
||||||
|
# date, timestamp, timestamptz, bigint, double precision
|
||||||
|
1700, 1082, 1114, 1184, 20, 701,
|
||||||
|
|
||||||
|
# real, time without time zone
|
||||||
|
700, 1083
|
||||||
|
)
|
||||||
|
|
||||||
|
# OIDs of array data types which need to typecast to array of string.
|
||||||
|
# This list may contain:
|
||||||
|
# OIDs of data types from PSYCOPG_SUPPORTED_ARRAY_DATATYPES as they need to be
|
||||||
|
# typecast to array of string.
|
||||||
|
# Also OIDs of data types which psycopg2 does not typecast array of that
|
||||||
|
# data type. e.g: uuid, bit, varbit, etc.
|
||||||
|
|
||||||
|
TO_ARRAY_OF_STRING_DATATYPES = (
|
||||||
|
# To cast bytea[] type
|
||||||
|
1001,
|
||||||
|
|
||||||
|
# bigint[]
|
||||||
|
1016,
|
||||||
|
|
||||||
|
# double precision[], real[]
|
||||||
|
1022, 1021,
|
||||||
|
|
||||||
|
# bit[], varbit[]
|
||||||
|
1561, 1563,
|
||||||
|
)
|
||||||
|
|
||||||
|
# OID of record array data type
|
||||||
|
RECORD_ARRAY = (2287,)
|
||||||
|
|
||||||
|
|
||||||
|
# OIDs of builtin array datatypes supported by psycopg2
|
||||||
|
# OID reference psycopg2/psycopg/typecast_builtins.c
|
||||||
|
#
|
||||||
|
# For these array data types psycopg2 returns result in list.
|
||||||
|
# For all other array data types psycopg2 returns result as string (string
|
||||||
|
# representing array literal)
|
||||||
|
# e.g:
|
||||||
|
#
|
||||||
|
# For below two sql psycopg2 returns result in different formats.
|
||||||
|
# SELECT '{foo,bar}'::text[];
|
||||||
|
# print('type of {} ==> {}'.format(res[0], type(res[0])))
|
||||||
|
# SELECT '{<a>foo</a>,<b>bar</b>}'::xml[];
|
||||||
|
# print('type of {} ==> {}'.format(res[0], type(res[0])))
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
# type of ['foo', 'bar'] ==> <type 'list'>
|
||||||
|
# type of {<a>foo</a>,<b>bar</b>} ==> <type 'str'>
|
||||||
|
|
||||||
|
PSYCOPG_SUPPORTED_BUILTIN_ARRAY_DATATYPES = (
|
||||||
|
1016, 1005, 1006, 1007, 1021, 1022, 1231,
|
||||||
|
1002, 1003, 1009, 1014, 1015, 1002, 1003,
|
||||||
|
1009, 1014, 1015, 1000, 1115, 1185, 1183,
|
||||||
|
1270, 1182, 1187, 1001, 1028, 1013, 1041,
|
||||||
|
651, 1040
|
||||||
|
)
|
||||||
|
|
||||||
|
# json, jsonb
|
||||||
|
# OID reference psycopg2/lib/_json.py
|
||||||
|
PSYCOPG_SUPPORTED_JSON_TYPES = (114, 3802)
|
||||||
|
|
||||||
|
# json[], jsonb[]
|
||||||
|
PSYCOPG_SUPPORTED_JSON_ARRAY_TYPES = (199, 3807)
|
||||||
|
|
||||||
|
ALL_JSON_TYPES = PSYCOPG_SUPPORTED_JSON_TYPES +\
|
||||||
|
PSYCOPG_SUPPORTED_JSON_ARRAY_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
# INET[], CIDR[]
|
||||||
|
# OID reference psycopg2/lib/_ipaddress.py
|
||||||
|
PSYCOPG_SUPPORTED_IPADDRESS_ARRAY_TYPES = (1041, 651)
|
||||||
|
|
||||||
|
|
||||||
|
# uuid[]
|
||||||
|
# OID reference psycopg2/lib/extras.py
|
||||||
|
PSYCOPG_SUPPORTED_IPADDRESS_ARRAY_TYPES = (2951,)
|
||||||
|
|
||||||
|
|
||||||
|
# int4range, int8range, numrange, daterange tsrange, tstzrange[]
|
||||||
|
# OID reference psycopg2/lib/_range.py
|
||||||
|
PSYCOPG_SUPPORTED_RANGE_TYPES = (3904, 3926, 3906, 3912, 3908, 3910)
|
||||||
|
|
||||||
|
|
||||||
|
# int4range[], int8range[], numrange[], daterange[] tsrange[], tstzrange[]
|
||||||
|
# OID reference psycopg2/lib/_range.py
|
||||||
|
PSYCOPG_SUPPORTED_RANGE_ARRAY_TYPES = (3905, 3927, 3907, 3913, 3909, 3911)
|
||||||
|
|
||||||
|
|
||||||
|
def register_global_typecasters():
|
||||||
|
if sys.version_info < (3,):
|
||||||
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
|
||||||
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
|
||||||
|
|
||||||
|
unicode_type_for_record = psycopg2.extensions.new_type(
|
||||||
|
(2249,),
|
||||||
|
"RECORD",
|
||||||
|
psycopg2.extensions.UNICODE
|
||||||
|
)
|
||||||
|
|
||||||
|
unicode_array_type_for_record_array = psycopg2.extensions.new_array_type(
|
||||||
|
RECORD_ARRAY,
|
||||||
|
"ARRAY_RECORD",
|
||||||
|
unicode_type_for_record
|
||||||
|
)
|
||||||
|
|
||||||
|
# This registers a unicode type caster for datatype 'RECORD'.
|
||||||
|
psycopg2.extensions.register_type(unicode_type_for_record)
|
||||||
|
|
||||||
|
# This registers a array unicode type caster for datatype 'ARRAY_RECORD'.
|
||||||
|
psycopg2.extensions.register_type(unicode_array_type_for_record_array)
|
||||||
|
|
||||||
|
# define type caster to convert various pg types into string type
|
||||||
|
pg_types_to_string_type = psycopg2.extensions.new_type(
|
||||||
|
TO_STRING_DATATYPES + PSYCOPG_SUPPORTED_RANGE_TYPES,
|
||||||
|
'TYPECAST_TO_STRING', _STRING
|
||||||
|
)
|
||||||
|
|
||||||
|
# define type caster to convert pg array types of above types into
|
||||||
|
# array of string type
|
||||||
|
pg_array_types_to_array_of_string_type = psycopg2.extensions.new_array_type(
|
||||||
|
TO_ARRAY_OF_STRING_DATATYPES,
|
||||||
|
'TYPECAST_TO_ARRAY_OF_STRING', pg_types_to_string_type
|
||||||
|
)
|
||||||
|
|
||||||
|
# This registers a type caster to convert various pg types into string type
|
||||||
|
psycopg2.extensions.register_type(pg_types_to_string_type)
|
||||||
|
|
||||||
|
# This registers a type caster to convert various pg array types into
|
||||||
|
# array of string type
|
||||||
|
psycopg2.extensions.register_type(pg_array_types_to_array_of_string_type)
|
||||||
|
|
||||||
|
|
||||||
|
def register_string_typecasters(connection):
|
||||||
|
if connection.encoding != 'UTF8':
|
||||||
|
# In python3 when database encoding is other than utf-8 and client
|
||||||
|
# encoding is set to UNICODE then we need to map data from database
|
||||||
|
# encoding to utf-8.
|
||||||
|
# This is required because when client encoding is set to UNICODE then
|
||||||
|
# psycopg assumes database encoding utf-8 and not the actual encoding.
|
||||||
|
# Not sure whether it's bug or feature in psycopg for python3.
|
||||||
|
if sys.version_info >= (3,):
|
||||||
|
def return_as_unicode(value, cursor):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
# Treat value as byte sequence of database encoding and then
|
||||||
|
# decode it as utf-8 to get correct unicode value.
|
||||||
|
return bytes(
|
||||||
|
value, encodings[cursor.connection.encoding]
|
||||||
|
).decode('utf-8')
|
||||||
|
|
||||||
|
unicode_type = psycopg2.extensions.new_type(
|
||||||
|
# "char", name, text, character, character varying
|
||||||
|
(19, 18, 25, 1042, 1043, 0),
|
||||||
|
'UNICODE', return_as_unicode)
|
||||||
|
else:
|
||||||
|
def return_as_unicode(value, cursor):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
# Decode it as utf-8 to get correct unicode value.
|
||||||
|
return value.decode('utf-8')
|
||||||
|
|
||||||
|
unicode_type = psycopg2.extensions.new_type(
|
||||||
|
# "char", name, text, character, character varying
|
||||||
|
(19, 18, 25, 1042, 1043, 0),
|
||||||
|
'UNICODE', return_as_unicode)
|
||||||
|
|
||||||
|
unicode_array_type = psycopg2.extensions.new_array_type(
|
||||||
|
# "char"[], name[], text[], character[], character varying[]
|
||||||
|
(1002, 1003, 1009, 1014, 1015, 0
|
||||||
|
), 'UNICODEARRAY', unicode_type)
|
||||||
|
|
||||||
|
psycopg2.extensions.register_type(unicode_type)
|
||||||
|
psycopg2.extensions.register_type(unicode_array_type)
|
||||||
|
|
||||||
|
|
||||||
|
def register_binary_typecasters(connection):
|
||||||
|
psycopg2.extensions.register_type(
|
||||||
|
psycopg2.extensions.new_type(
|
||||||
|
(
|
||||||
|
# To cast bytea type
|
||||||
|
17,
|
||||||
|
),
|
||||||
|
'BYTEA_PLACEHOLDER',
|
||||||
|
# Only show placeholder if data actually exists.
|
||||||
|
lambda value, cursor: 'binary data' if value is not None else None),
|
||||||
|
connection
|
||||||
|
)
|
||||||
|
|
||||||
|
psycopg2.extensions.register_type(
|
||||||
|
psycopg2.extensions.new_type(
|
||||||
|
(
|
||||||
|
# To cast bytea[] type
|
||||||
|
1001,
|
||||||
|
),
|
||||||
|
'BYTEA_ARRAY_PLACEHOLDER',
|
||||||
|
# Only show placeholder if data actually exists.
|
||||||
|
lambda value, cursor: 'binary data[]' if value is not None else None),
|
||||||
|
connection
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register_array_to_string_typecasters(connection):
|
||||||
|
psycopg2.extensions.register_type(
|
||||||
|
psycopg2.extensions.new_type(
|
||||||
|
PSYCOPG_SUPPORTED_BUILTIN_ARRAY_DATATYPES +
|
||||||
|
PSYCOPG_SUPPORTED_JSON_ARRAY_TYPES +
|
||||||
|
PSYCOPG_SUPPORTED_IPADDRESS_ARRAY_TYPES +
|
||||||
|
PSYCOPG_SUPPORTED_RANGE_ARRAY_TYPES +
|
||||||
|
TO_ARRAY_OF_STRING_DATATYPES,
|
||||||
|
'ARRAY_TO_STRING',
|
||||||
|
_STRING),
|
||||||
|
connection
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue