Fixed below review comments

- Removed the "lineWrapping" option from the codemirror textarea because
  it was creating issue in the code folding.
- Handle the values while depositing during debugging.
- Properly handle the Array values while saving it to sqlite database
  and displayed in input dialog.
- SQL code folding was not supported in codemirror so added the same.
pull/3/head
Neel Patel 2016-05-06 16:02:11 +05:30 committed by Akshay Joshi
parent 1efba422bc
commit 149c59aa4f
8 changed files with 248 additions and 74 deletions

View File

@ -768,9 +768,10 @@
initialize: function() {
Backgrid.Cell.prototype.initialize.apply(this, arguments);
// set value to empty array.
var m = arguments[0].model;
if (_.isUndefined(this.collection)) {
this.collection = new (Backbone.Collection.extend({
model: arrayCellModel}));
model: arrayCellModel}))(m.get('value'));
}
this.model.set(this.column.get('name'), this.collection);
@ -790,9 +791,10 @@
initialize: function() {
Backgrid.Cell.prototype.initialize.apply(this, arguments);
// set value to empty array.
var m = arguments[0].model;
if (_.isUndefined(this.collection)) {
this.collection = new (Backbone.Collection.extend({
model: arrayCellModel}));
model: arrayCellModel}))(m.get('value'));
}

View File

@ -0,0 +1,83 @@
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.pgadminKeywordRangeFinder = function(cm, start, startTkn, endTkn) {
var line = start.line, lineText = cm.getLine(line);
var at = lineText.length, startChar, tokenType;
for (; at > 0;) {
var found = lineText.lastIndexOf(startTkn, at);
var startToken = startTkn;
var endToken = endTkn;
if (found < start.ch) {
var found = lineText.lastIndexOf("[", at);
if (found < start.ch) break;
var startToken = '[';
var endToken = ']';
}
tokenType = cm.getTokenAt(CodeMirror.Pos(line, found + 1)).type;
if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; }
at = found - 1;
}
if (startChar == null || lineText.lastIndexOf(startToken) > startChar) return;
var count = 1, lastLine = cm.lineCount(), end, endCh;
outer: for (var i = line + 1; i < lastLine; ++i) {
var text = cm.getLine(i), pos = 0;
for (;;) {
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
if (nextOpen < 0) nextOpen = text.length;
if (nextClose < 0) nextClose = text.length;
pos = Math.min(nextOpen, nextClose);
if (pos == text.length) break;
if (cm.getTokenAt(CodeMirror.Pos(i, pos + 1)).type == tokenType) {
if (pos == nextOpen) ++count;
else if (!--count) {
end = i;
endCh = pos;
break outer;
}
}
++pos;
}
}
if (end == null || end == line + 1) return;
return {from: CodeMirror.Pos(line, startChar + startTkn.length),
to: CodeMirror.Pos(end, endCh)};
};
CodeMirror.pgadminBeginRangeFinder = function(cm, start) {
var startToken = 'BEGIN';
var endToken = 'END;';
var fromToPos = CodeMirror.pgadminKeywordRangeFinder(cm, start, startToken, endToken);
return fromToPos;
};
CodeMirror.pgadminIfRangeFinder = function(cm, start) {
var startToken = 'IF';
var endToken = 'END IF';
var fromToPos = CodeMirror.pgadminKeywordRangeFinder(cm, start, startToken, endToken);
return fromToPos;
};
CodeMirror.pgadminLoopRangeFinder = function(cm, start) {
var startToken = 'LOOP';
var endToken = 'END LOOP';
var fromToPos = CodeMirror.pgadminKeywordRangeFinder(cm, start, startToken, endToken);
return fromToPos;
};
CodeMirror.pgadminCaseRangeFinder = function(cm, start) {
var startToken = 'CASE';
var endToken = 'END CASE';
var fromToPos = CodeMirror.pgadminKeywordRangeFinder(cm, start, startToken, endToken);
return fromToPos;
};
});

View File

@ -142,7 +142,7 @@ def init_function(node_type, sid, did, scid, fid):
# Check server type is ppas or not
ppas_server = False
if server_type == 'ppas':
ppas_server = True;
ppas_server = True
# Set the template path required to read the sql files
template_path = 'debugger/sql'
@ -214,14 +214,14 @@ def init_function(node_type, sid, did, scid, fid):
data['require_input'] = True
if r_set['rows'][0]['proargmodes']:
pro_arg_modes = r_set['rows'][0]['proargmodes'].split(",");
pro_arg_modes = r_set['rows'][0]['proargmodes'].split(",")
for pr_arg_mode in pro_arg_modes:
if pr_arg_mode == 'o' or pr_arg_mode == 't':
data['require_input'] = False
continue;
continue
else:
data['require_input'] = True
break;
break
r_set['rows'][0]['require_input'] = data['require_input']
@ -243,7 +243,7 @@ def init_function(node_type, sid, did, scid, fid):
'args_value': ''
}
session['funcData'] = function_data;
session['funcData'] = function_data
return make_json_response(
data=r_set['rows'],
@ -319,7 +319,7 @@ def initialize_target(debug_type, sid, did, scid, func_id):
else:
debugger_data = session['debuggerData']
status = True;
status = True
# Find out the debugger version and store it in session variables
status, rid = conn.execute_scalar(
@ -338,9 +338,9 @@ def initialize_target(debug_type, sid, did, scid, func_id):
if status:
if rid == 2 or rid == 3:
debugger_version = rid;
debugger_version = rid
else:
status = False;
status = False
# Add the debugger version information to pgadmin4 log file
current_app.logger.debug("Debugger version is: %d", debugger_version)
@ -401,7 +401,7 @@ def initialize_target(debug_type, sid, did, scid, func_id):
}
# Update the session variable of function information
session['functionData'] = function_data;
session['functionData'] = function_data
# Delete the 'funcData' session variables as it is not used now as target is initialized
del session['funcData']
@ -447,13 +447,8 @@ def close(trans_id):
# Release the connection
if conn.connected():
if obj['debug_type'] == 'indirect':
# render the SQL template and send the query to server
sql = render_template("/".join([template_path, 'abort_target.sql']),
session_id=obj['session_id'])
status, res = conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
# on successful connection cancel the running transaction
status, result = conn.cancel_transaction(obj['conn_id'], obj['database_id'])
# Delete the existing debugger data in session variable
del session['debuggerData'][str(trans_id)]
@ -599,27 +594,27 @@ def start_debugger_listener(trans_id):
# the return value is transformed into an extra OUT-parameter
# named "_retval_"
if session['functionData'][str(trans_id)]['args_name']:
arg_name = session['functionData'][str(trans_id)]['args_name'].split(",");
arg_name = session['functionData'][str(trans_id)]['args_name'].split(",")
if '_retval_' in arg_name:
arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(",");
arg_mode.pop();
arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(",")
arg_mode.pop()
else:
arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(",");
arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(",")
else:
arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(",");
arg_mode = session['functionData'][str(trans_id)]['arg_mode'].split(",")
else:
arg_mode = ['i'] * len(session['functionData'][str(trans_id)]['args_type'].split(","));
arg_mode = ['i'] * len(session['functionData'][str(trans_id)]['args_type'].split(","))
if session['functionData'][str(trans_id)]['args_type']:
if session['functionData'][str(trans_id)]['args_name']:
arg_name = session['functionData'][str(trans_id)]['args_name'].split(",");
arg_name = session['functionData'][str(trans_id)]['args_name'].split(",")
if '_retval_' in arg_name:
arg_type = session['functionData'][str(trans_id)]['args_type'].split(",");
arg_type.pop();
arg_type = session['functionData'][str(trans_id)]['args_type'].split(",")
arg_type.pop()
else:
arg_type = session['functionData'][str(trans_id)]['args_type'].split(",");
arg_type = session['functionData'][str(trans_id)]['args_type'].split(",")
else:
arg_type = session['functionData'][str(trans_id)]['args_type'].split(",");
arg_type = session['functionData'][str(trans_id)]['args_type'].split(",")
# Below are two different template to execute and start executer
if manager.server_type != 'pg' and manager.version < 90300:
@ -811,7 +806,7 @@ def messages(trans_id):
# Notice message returned by the server is "NOTICE: PLDBGBREAK:7".
# From the above message we need to find out port number as "7" so below logic will find 7 as port number
# and attach listened to that port number
offset = notify[0].find('PLDBGBREAK');
offset = notify[0].find('PLDBGBREAK')
str_len = len('PLDBGBREAK')
str_len = str_len + 1
tmpOffset = 0
@ -1066,11 +1061,18 @@ def deposit_parameter_value(trans_id):
status, result = conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=result)
# Check if value deposited successfully or not and depending on the result, return the message information.
if result['rows'][0]['pldbg_deposit_value']:
info = gettext('Value deposited successfully')
else:
info = gettext('Error while setting the value')
return make_json_response(data={'status': status, 'info':info, 'result': result['rows'][0]['pldbg_deposit_value']})
else:
status = False
result = gettext('Not connected to server or connection with the server has been closed.')
return make_json_response(data={'status': status, 'result': result['rows']})
return make_json_response(data={'status': status, 'result': result})
@blueprint.route('/select_frame/<int:trans_id>/<int:frame_id>', methods=['GET'])
@ -1200,6 +1202,18 @@ def set_arguments_sqlite(sid, did, scid, func_id):
server_id=data[i]['server_id'], database_id=data[i]['database_id'], schema_id=data[i]['schema_id'],
function_id=data[i]['function_id'], arg_id=data[i]['arg_id']).count()
# handle the Array list sent from the client
array_string = ''
if data[i]['value'].__class__.__name__ in ('list') and data[i]['value']:
for k in range(0, len(data[i]['value'])):
array_string += data[i]['value'][k]['value']
if k != (len(data[i]['value']) - 1):
array_string += ','
elif data[i]['value'].__class__.__name__ in ('list') and not data[i]['value']:
array_string = ''
else:
array_string = data[i]['value']
# Check if data is already available in database then update the existing value otherwise add the new value
if DbgFuncArgsExists:
DbgFuncArgs = DebuggerFunctionArguments.query.filter_by(
@ -1209,7 +1223,7 @@ def set_arguments_sqlite(sid, did, scid, func_id):
DbgFuncArgs.is_null = data[i]['is_null']
DbgFuncArgs.is_expression = data[i]['is_expression']
DbgFuncArgs.use_default = data[i]['use_default']
DbgFuncArgs.value = data[i]['value']
DbgFuncArgs.value = array_string
else:
debugger_func_args = DebuggerFunctionArguments(
server_id = data[i]['server_id'],
@ -1220,7 +1234,7 @@ def set_arguments_sqlite(sid, did, scid, func_id):
is_null = data[i]['is_null'],
is_expression = data[i]['is_expression'],
use_default = data[i]['use_default'],
value = data[i]['value']
value = array_string
)
db.session.add(debugger_func_args)

View File

@ -53,4 +53,33 @@
.debugger-container .CodeMirror-activeline-background {
background: #50B0F0;
}
}
.CodeMirror-foldmarker {
color: blue;
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
font-family: arial;
line-height: .3;
cursor: pointer;
}
.CodeMirror, .CodeMirror-gutters {
min-height: 100%;
}
.CodeMirror-foldgutter {
width: .9em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open:after {
content: "\25BC";
}
.CodeMirror-foldgutter-folded:after {
content: "\25B6";
}

View File

@ -287,11 +287,25 @@ define(
}
// Need to update the func_obj variable from sqlite database if available
// TODO: Need to check, how to update the value in Array fields....
if (func_args_data.length != 0) {
for (i = 0; i < func_args_data.length; i++) {
var index = func_args_data[i]['arg_id'];
func_obj.push({ "name": argname[index], "type": argtype[index], "is_null": func_args_data[i]['is_null'] ? true: false, "expr": func_args_data[i]['is_expression']? true: false, "value": func_args_data[i]['value'], "use_default": func_args_data[i]['use_default']? true: false, "default_value": def_val_list[index]});
var values = [];
if (argtype[index].indexOf("[]") !=-1) {
var vals = func_args_data[i]['value'].split(",");
if (argtype[index].indexOf("integer") != -1) {
_.each(vals, function(val){
values.push({'value': parseInt(val)});
});
}
_.each(vals, function(val){
values.push({'value': val});
});
} else {
values = func_args_data[i]['value'];
}
func_obj.push({ "name": argname[index], "type": argtype[index], "is_null": func_args_data[i]['is_null'] ? true: false, "expr": func_args_data[i]['is_expression']? true: false, "value": values, "use_default": func_args_data[i]['use_default']? true: false, "default_value": def_val_list[index]});
}
}
}
@ -358,11 +372,24 @@ define(
}
// Need to update the func_obj variable from sqlite database if available
// TODO: Need to check, how to update the value in Array fields....
if (func_args_data.length != 0) {
for (i = 0; i < func_args_data.length; i++) {
var index = func_args_data[i]['arg_id'];
func_obj.push({ "name": myargname[index], "type": argtype[index], "is_null": func_args_data[i]['is_null'] ? true: false, "expr": func_args_data[i]['is_expression']? true: false, "value": func_args_data[i]['value'], "use_default": func_args_data[i]['use_default']? true: false, "default_value": def_val_list[index]});
var values = [];
if (argtype[index].indexOf("[]") !=-1) {
var vals = func_args_data[i]['value'].split(",");
if (argtype[index].indexOf("integer") != -1) {
_.each(vals, function(val){
values.push({'value': parseInt(val)});
});
}
_.each(vals, function(val){
values.push({'value': val});
});
} else {
values = func_args_data[i]['value'];
}
func_obj.push({ "name": myargname[index], "type": argtype[index], "is_null": func_args_data[i]['is_null'] ? true: false, "expr": func_args_data[i]['is_expression']? true: false, "value": values, "use_default": func_args_data[i]['use_default']? true: false, "default_value": def_val_list[index]});
}
}
}
@ -423,7 +450,6 @@ define(
this.grid.collection.each(function(m) {
// TODO: Removed temporary for testing.....
// Check if value is set to NULL then we should ignore the value field
if (m.get('is_null')) {
args_value_list.push({ 'name': m.get('name'),

View File

@ -1,7 +1,8 @@
define(
['jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin','pgadmin.browser',
'backbone', 'backgrid', 'codemirror', 'backform','pgadmin.tools.debugger.ui',
'wcdocker', 'pgadmin.backform', 'pgadmin.backgrid', 'codemirror/addon/selection/active-line'],
'wcdocker', 'pgadmin.backform', 'pgadmin.backgrid', 'codemirror/addon/selection/active-line',
'codemirror/addon/fold/foldgutter', 'codemirror/addon/fold/foldcode', 'codemirror/addon/fold/pgadmin-sqlfoldcode'],
function($, _, S, Alertify, pgAdmin, pgBrowser, Backbone, Backgrid, CodeMirror, Backform, debug_function_again) {
if (pgAdmin.Browser.tree != null) {
@ -251,7 +252,7 @@ define(
During the execution we should poll the result in minimum seconds but once the execution is completed
and wait for the another debugging session then we should decrease the polling frequency.
*/
if (pgTools.DirectDebug.direct_execution_completed) {
if (pgTools.DirectDebug.polling_timeout_idle) {
// poll the result after 1 second
var poll_timeout = 1000;
}
@ -273,6 +274,7 @@ define(
}
else {
if (res.data.result[0].src != undefined || res.data.result[0].src != null) {
pgTools.DirectDebug.polling_timeout_idle = false;
pgTools.DirectDebug.docker.finishLoading(50);
pgTools.DirectDebug.editor.setValue(res.data.result[0].src);
self.UpdateBreakpoint(trans_id);
@ -293,8 +295,10 @@ define(
self.clear_all_breakpoint(trans_id);
self.execute_query(trans_id);
pgTools.DirectDebug.first_time_indirect_debug = true;
pgTools.DirectDebug.polling_timeout_idle = false;
}
else {
pgTools.DirectDebug.polling_timeout_idle = false;
pgTools.DirectDebug.docker.finishLoading(50);
// If the source is really changed then only update the breakpoint information
if (res.data.result[0].src != pgTools.DirectDebug.editor.getValue()) {
@ -321,6 +325,7 @@ define(
}
}
else if (res.data.status === 'Busy') {
pgTools.DirectDebug.polling_timeout_idle = true;
// If status is Busy then poll the result by recursive call to the poll function
if (!pgTools.DirectDebug.debug_type) {
pgTools.DirectDebug.docker.startLoading('{{ _('Waiting for another session to invoke the target...') }}');
@ -368,7 +373,7 @@ define(
During the execution we should poll the result in minimum seconds but once the execution is completed
and wait for the another debugging session then we should decrease the polling frequency.
*/
if (pgTools.DirectDebug.direct_execution_completed) {
if (pgTools.DirectDebug.polling_timeout_idle) {
// poll the result to check that execution is completed or not after 1200 ms
var poll_end_timeout = 1200;
}
@ -391,6 +396,7 @@ define(
*/
pgTools.DirectDebug.editor.removeLineClass(self.active_line_no, 'wrap', 'CodeMirror-activeline-background');
pgTools.DirectDebug.direct_execution_completed = true;
pgTools.DirectDebug.polling_timeout_idle = true;
//Set the alertify message to inform the user that execution is completed.
Alertify.notify(
@ -418,6 +424,7 @@ define(
self.AddResults(res.data.result);
pgTools.DirectDebug.results_panel.focus();
pgTools.DirectDebug.direct_execution_completed = true;
pgTools.DirectDebug.polling_timeout_idle = true;
//Set the alertify message to inform the user that execution is completed.
Alertify.notify(
@ -537,10 +544,9 @@ define(
var self = this;
//Check first if previous execution was completed or not
if (pgTools.DirectDebug.direct_execution_completed) {
// TODO: We need to get the arguments given by the user from sqlite database
if (pgTools.DirectDebug.direct_execution_completed &&
pgTools.DirectDebug.direct_execution_completed == pgTools.DirectDebug.polling_timeout_idle) {
self.Restart(trans_id);
pgTools.DirectDebug.direct_execution_completed = false;
}
else {
// Make ajax call to listen the database message
@ -802,7 +808,6 @@ define(
if (result.length != 0)
{
for (i = 0; i < result.length; i++) {
// TODO: change the my_func_test_2 with name of the function to be executed.
my_obj.push({ "name": result[i].targetname, "value": result[i].args, "line_no": result[i].linenumber });
}
}
@ -871,7 +876,6 @@ define(
if (result.value.length != 0)
{
for (i = 0; i < result.value.length; i++) {
// TODO: change the my_func_test_2 with name of the function to be executed.
my_obj.push({ "value": result.value[i]});
}
}
@ -1014,6 +1018,13 @@ define(
if (res.data.status) {
// Get the updated variables value
self.GetLocalVariables(pgTools.DirectDebug.trans_id);
// Show the message to the user that deposit value is success or failure
Alertify.notify(
res.data.info,
res.data.result ? 'success': 'error',
3,
function() { }
);
}
},
error: function(e) {
@ -1187,6 +1198,7 @@ define(
this.debug_type = debug_type;
this.first_time_indirect_debug = false;
this.direct_execution_completed = false;
this.polling_timeout_idle = false;
var docker = this.docker = new wcDocker(
'#container', {
@ -1268,14 +1280,14 @@ define(
}
else if (res.data.status === 'NotConnected') {
Alertify.alert(
'Data grid poll result error',
'Not connected to server or connection with the server has been closed.',
res.data.result
);
}
},
error: function(e) {
Alertify.alert(
'Debugger listener starting error'
'Debugger: Error fetching messages information'
);
}
});
@ -1283,31 +1295,33 @@ define(
},
// Callback function when user click on gutters of codemirror to set/clear the breakpoint
onBreakPoint: function(cm, m) {
onBreakPoint: function(cm, m, gutter) {
var self = this;
// TODO::
// We may want to check, if break-point is allowed at this moment or not
var info = cm.lineInfo(m);
// If breakpoint gutter is clicked and execution is not completed then only set the breakpoint
if (gutter == "breakpoints" && !pgTools.DirectDebug.polling_timeout_idle ) {
// We may want to check, if break-point is allowed at this moment or not
var info = cm.lineInfo(m);
// If gutterMarker is undefined that means there is no marker defined previously
// So we need to set the breakpoint command here...
if (info.gutterMarkers == undefined) {
controller.set_breakpoint(self.trans_id,m+1,1); //set the breakpoint
}
else {
controller.set_breakpoint(self.trans_id,m+1,0); //clear the breakpoint
}
// If gutterMarker is undefined that means there is no marker defined previously
// So we need to set the breakpoint command here...
if (info.gutterMarkers == undefined) {
controller.set_breakpoint(self.trans_id,m+1,1); //set the breakpoint
}
else {
controller.set_breakpoint(self.trans_id,m+1,0); //clear the breakpoint
}
cm.setGutterMarker(
m, "breakpoints", info.gutterMarkers ? null : function() {
var marker = document.createElement("div");
cm.setGutterMarker(
m, "breakpoints", info.gutterMarkers ? null : function() {
var marker = document.createElement("div");
marker.style.color = "#822";
marker.innerHTML = "●";
marker.style.color = "#822";
marker.innerHTML = "●";
return marker;
return marker;
}());
}
},
// Create the debugger layout with splitter and display the appropriate data received from server.
@ -1401,8 +1415,14 @@ define(
var editor = self.editor = CodeMirror.fromTextArea(
code_editor_area.get(0), {
lineNumbers: true,
lineWrapping: true,
gutters: ["note-gutter", "CodeMirror-linenumbers", "breakpoints"],
foldOptions: {
widget: "\u2026"
},
foldGutter: {
rangeFinder: CodeMirror.fold.combine(CodeMirror.pgadminBeginRangeFinder, CodeMirror.pgadminIfRangeFinder,
CodeMirror.pgadminLoopRangeFinder, CodeMirror.pgadminCaseRangeFinder)
},
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "breakpoints"],
mode: "text/x-pgsql",
readOnly: true
});

View File

@ -34,7 +34,7 @@
{% set strParam = "p_param" ~ (loop.index - 1) %}
{% set str_declare = str_declare ~ "\t" ~ strParam ~ " " ~ arg_type[loop.index - 1] %}
{% if arg_mode == 'b' %}
{### TODO: to check for Null parameters received from client ###}
{### Handle Null parameters received from client ###}
{% if data[input_value_index]['type'] == 'text' and data[input_value_index]['value'] != 'NULL' %}
{% set tmp_val = data[input_value_index]['value']|qtLiteral %}
{% set str_declare = str_declare ~ " := " ~ strParam ~ " " ~ tmp_val ~ "::" ~ data[input_value_index]['type'] %}
@ -61,7 +61,7 @@
{% set str_statement = str_statement ~ "VARIADIC " %}
{% endif %}
{### TODO: to check for Null parameters received from client ###}
{### Handle Null parameters received from client ###}
{% if data[input_value_index]['type'] == 'text' and data[input_value_index]['value'] != 'NULL' %}
{% set tmp_var = data[input_value_index]['value']|qtLiteral %}
{% set str_statement = str_statement ~ tmp_var ~ "::" ~ data[input_value_index]['type'] %}
@ -101,4 +101,4 @@
{### Return final query formed with above condition ###}
{% if not inside_loop.value %}
{{ strQuery }}
{% endif %}
{% endif %}

View File

@ -11,7 +11,7 @@
{% if 'type' in dict_item and 'value' in dict_item %}
{% if dict_item['type'] == 'text' and dict_item['value'] != 'NULL' %}
{{ dict_item['value']|qtLiteral }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %}
{% elif dict_item['type'] == 'text' and dict_item['value'] == 'NULL' %}
{% elif dict_item['value'] == 'NULL' %}
{{ dict_item['value'] }}::{{ dict_item['type'] }}{% if not loop.last %}, {% endif %}
{% else %}
{% if '[]' in dict_item['type'] %}
@ -29,4 +29,4 @@
{% endif %}
{% endfor %}
{% endif %}
)
)