Added support for expression in exclusion constraints. Fixes #5571

This commit is contained in:
Aditya Toshniwal 2020-12-24 12:50:57 +05:30 committed by Akshay Joshi
parent a92595012f
commit 5448de2d3f
19 changed files with 247 additions and 116 deletions

View File

@ -58,13 +58,15 @@ Click the *Columns* tab to continue.
:alt: Exclusion constraint dialog columns tab :alt: Exclusion constraint dialog columns tab
:align: center :align: center
Use the fields in the *Columns* tab to to specify the column(s) to which the Use the fields in the *Columns* tab to specify the column(s) or expression(s)
constraint applies. Use the drop-down listbox next to *Column* to select a to which the constraint applies. Use the *Is expression ?* switch to enable
column and click the *Add* icon (+) to provide details of the action on the expression text input. Use the drop-down listbox next to *Column*
column: to select a column. Once the *Column* is selected or the *Expression* is
entered then click the *Add* icon (+) to provide details of the action on the
column/expression:
* The *Column* field is populated with the selection made in the *Column* * The *Col/Exp* field is populated with the selection made in the *Column*
drop-down listbox. drop-down listbox or the *Expression* entered.
* If applicable, use the drop-down listbox in the *Operator class* to specify * If applicable, use the drop-down listbox in the *Operator class* to specify
the operator class that will be used by the index for the column. the operator class that will be used by the index for the column.
* Move the *DESC* switch to *DESC* to specify a descending sort order. The * Move the *DESC* switch to *DESC* to specify a descending sort order. The

BIN
docs/en_US/images/exclusion_constraint_columns.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 93 KiB

BIN
docs/en_US/images/exclusion_constraint_sql.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -19,6 +19,7 @@ Housekeeping
Bug fixes Bug fixes
********* *********
| `Issue #5571 <https://redmine.postgresql.org/issues/5571>`_ - Added support for expression in exclusion constraints.
| `Issue #5875 <https://redmine.postgresql.org/issues/5875>`_ - Ensure that the 'template1' database should not be visible after pg_upgrade. | `Issue #5875 <https://redmine.postgresql.org/issues/5875>`_ - Ensure that the 'template1' database should not be visible after pg_upgrade.
| `Issue #5965 <https://redmine.postgresql.org/issues/5965>`_ - Ensure that the macro query result should be download properly. | `Issue #5965 <https://redmine.postgresql.org/issues/5965>`_ - Ensure that the macro query result should be download properly.
| `Issue #5973 <https://redmine.postgresql.org/issues/5973>`_ - Added appropriate help message and a placeholder for letting users know about the account password expiry for Login/Group Role. | `Issue #5973 <https://redmine.postgresql.org/issues/5973>`_ - Added appropriate help message and a placeholder for letting users know about the account password expiry for Login/Group Role.

View File

@ -560,11 +560,10 @@ class TableView(BaseTableView, DataTypeReader, VacuumSettings,
Returns: Returns:
""" """
data = request.args if request.args else None data = request.args
try: try:
if data and 'col_type' in data:
result = exclusion_utils.get_operator( result = exclusion_utils.get_operator(
self.conn, data['col_type'], self.conn, data.get('col_type', None),
self.blueprint.show_system_objects) self.blueprint.show_system_objects)
return make_json_response( return make_json_response(

View File

@ -778,58 +778,18 @@ class ExclusionConstraintView(PGChildNodeView):
""" """
try: try:
SQL = render_template( status, rows = exclusion_utils.get_exclusion_constraints(
"/".join([self.template_path, self._PROPERTIES_SQL]), self.conn, did, tid, exid, template_path=self.template_path
did=did, tid=tid, conn=self.conn, cid=exid) )
status, result = self.conn.execute_dict(SQL)
if not status: if not status:
return internal_server_error(errormsg=result) return rows
if len(result['rows']) == 0: if len(rows) == 0:
return gone(_("Could not find the exclusion constraint.")) return gone(_("Could not find the exclusion constraint."))
data = result['rows'][0] data = rows[0]
data['schema'] = self.schema data['schema'] = self.schema
data['table'] = self.table data['table'] = self.table
sql = render_template(
"/".join([self.template_path, 'get_constraint_cols.sql']),
cid=exid,
colcnt=data['col_count'])
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
columns = []
for row in res['rows']:
nulls_order = True if (row['options'] & 2) else False
order = False if row['options'] & 1 else True
columns.append({"column": row['coldef'].strip('"'),
"oper_class": row['opcname'],
"order": order,
"nulls_order": nulls_order,
"operator": row['oprname']
})
data['columns'] = columns
# Add Include details of the index supported for PG-11+
if self.manager.version >= 110000:
sql = render_template(
"/".join(
[self.template_path, 'get_constraint_include.sql']
),
cid=exid)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
data['include'] = [col['colname'] for col in res['rows']]
if data.get('amname', '') == "":
data['amname'] = 'btree'
SQL = render_template( SQL = render_template(
"/".join([self.template_path, self._CREATE_SQL]), data=data) "/".join([self.template_path, self._CREATE_SQL]), data=data)

View File

@ -19,6 +19,7 @@ define('pgadmin.node.exclusion_constraint', [
var ExclusionConstraintColumnModel = pgBrowser.Node.Model.extend({ var ExclusionConstraintColumnModel = pgBrowser.Node.Model.extend({
defaults: { defaults: {
column: undefined, column: undefined,
is_exp: false,
oper_class: undefined, oper_class: undefined,
order: false, order: false,
nulls_order: false, nulls_order: false,
@ -32,8 +33,20 @@ define('pgadmin.node.exclusion_constraint', [
return d; return d;
}, },
schema: [{ schema: [{
id: 'column', label: gettext('Column'), type:'text', editable: false, id: 'column', label: gettext('Col/Exp'), type:'text', editable: false,
cell:'string', cell:'string',
},{
id: 'is_exp', label: '', type:'boolean', editable: false,
cell: Backgrid.StringCell.extend({
formatter: {
fromRaw: function (rawValue) {
return rawValue ? 'E' : 'C';
},
toRaw: function (val) {
return val;
},
},
}), visible: false,
},{ },{
id: 'oper_class', label: gettext('Operator class'), type:'text', id: 'oper_class', label: gettext('Operator class'), type:'text',
node: 'table', url: 'get_oper_class', first_empty: true, node: 'table', url: 'get_oper_class', first_empty: true,
@ -175,7 +188,7 @@ define('pgadmin.node.exclusion_constraint', [
self.column.set('options', []); self.column.set('options', []);
if (url && !_.isUndefined(col_type) && !_.isNull(col_type) && col_type != '') { if (url) {
var node = this.column.get('schema_node'), var node = this.column.get('schema_node'),
eventHandler = m.top || m, eventHandler = m.top || m,
node_info = this.column.get('node_info'), node_info = this.column.get('node_info'),
@ -210,9 +223,11 @@ define('pgadmin.node.exclusion_constraint', [
validate: function() { validate: function() {
this.errorModel.clear(); this.errorModel.clear();
var operator = this.get('operator'), var operator = this.get('operator'),
column_name = this.get('column'); column_name = this.get('column'),
is_exp = this.get('is_exp');
if (_.isUndefined(operator) || _.isNull(operator)) { if (_.isUndefined(operator) || _.isNull(operator)) {
var msg = gettext('Please specify operator for column: ') + column_name; var msg = gettext('Please specify operator for column: ') + column_name;
if(is_exp) msg = gettext('Please specify operator for expression: ') + column_name;
this.errorModel.set('operator', msg); this.errorModel.set('operator', msg);
return msg; return msg;
} }
@ -231,8 +246,15 @@ define('pgadmin.node.exclusion_constraint', [
var self = this, var self = this,
node = 'exclusion_constraint', node = 'exclusion_constraint',
headerSchema = [{ headerSchema = [{
id: 'column', label:'', type:'text', id: 'is_exp', label: gettext('Is expression ?'), type: 'switch',
node: 'column', control: Backform.NodeListByNameControl.extend({ control: 'switch', controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-6 pg-el-12',
},{
id: 'column', label: gettext('Column'), type:'text',
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-6 pg-el-12',
node: 'column', deps: ['is_exp'],
control: Backform.NodeListByNameControl.extend({
initialize: function() { initialize: function() {
// Here we will decide if we need to call URL // Here we will decide if we need to call URL
// Or fetch the data from parent columns collection // Or fetch the data from parent columns collection
@ -310,7 +332,8 @@ define('pgadmin.node.exclusion_constraint', [
} }
}, },
template: _.template([ template: _.template([
'<div class="<%=Backform.controlsClassName%> <%=extraClasses.join(\' \')%>">', '<span class="<%=controlLabelClassName%>"><%=label%></span>',
'<div class="<%=controlsClassName%> <%=extraClasses.join(\' \')%>">',
' <select class="pgadmin-node-select form-control" name="<%=name%>" style="width:100%;" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >', ' <select class="pgadmin-node-select form-control" name="<%=name%>" style="width:100%;" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=required ? "required" : ""%> >',
' <% for (var i=0; i < options.length; i++) { %>', ' <% for (var i=0; i < options.length; i++) { %>',
' <% var option = options[i]; %>', ' <% var option = options[i]; %>',
@ -363,10 +386,21 @@ define('pgadmin.node.exclusion_constraint', [
readonly: function() { readonly: function() {
return !_.isUndefined(self.model.get('oid')); return !_.isUndefined(self.model.get('oid'));
}, },
disabled: function(m) {
return m.get('is_exp');
},
},{
id: 'exp', label: gettext('Expression'), type: 'text',
editable: true, deps: ['is_exp'],
controlLabelClassName: 'control-label pg-el-sm-4 pg-el-12',
controlsClassName: 'pgadmin-controls pg-el-sm-6 pg-el-12',
disabled: function(m) {
return !m.get('is_exp');
},
}], }],
headerDefaults = {column: null}, headerDefaults = {is_exp: false, column: null, exp: null},
gridCols = ['column', 'oper_class', 'order', 'nulls_order', 'operator']; gridCols = ['is_exp', 'column', 'oper_class', 'order', 'nulls_order', 'operator'];
self.headerData = new (Backbone.Model.extend({ self.headerData = new (Backbone.Model.extend({
defaults: headerDefaults, defaults: headerDefaults,
@ -396,22 +430,16 @@ define('pgadmin.node.exclusion_constraint', [
}, },
generateHeader: function(data) { generateHeader: function(data) {
var isNew = _.isUndefined(this.model.get('oid'));
var header = [ var header = [
'<div class="subnode-header-form">', '<div class="subnode-header-form '+ (isNew ? '' : 'd-none') +'">',
' <div>', ' <div>',
' <div class="row">', ' <div header="is_exp"></div>',
' <div class="col-4">', ' <div header="column"></div>',
' <label class="control-label"><%-column_label%></label>', ' <div header="exp"></div>',
' </div>',
' <div class="col-4" header="column"></div>',
' </div>',
' </div>', ' </div>',
'</div>'].join('\n'); '</div>'].join('\n');
_.extend(data, {
column_label: gettext('Column'),
});
var self = this, var self = this,
headerTmpl = _.template(header), headerTmpl = _.template(header),
$header = $(headerTmpl(data)), $header = $(headerTmpl(data)),
@ -514,6 +542,9 @@ define('pgadmin.node.exclusion_constraint', [
} }
if (self.control_data.canAdd) { if (self.control_data.canAdd) {
if(data['is_exp']) {
inSelected = false;
} else {
self.collection.each(function(m) { self.collection.each(function(m) {
if (!inSelected) { if (!inSelected) {
_.each(checkVars, function(v) { _.each(checkVars, function(v) {
@ -529,6 +560,7 @@ define('pgadmin.node.exclusion_constraint', [
} }
}); });
} }
}
else { else {
inSelected = true; inSelected = true;
} }
@ -538,18 +570,22 @@ define('pgadmin.node.exclusion_constraint', [
addColumns: function(ev) { addColumns: function(ev) {
ev.preventDefault(); ev.preventDefault();
var self = this, var self = this;
column = self.headerData.get('column'); let newHeaderData = {
is_exp: self.headerData.get('is_exp'),
column: self.headerData.get('is_exp') ? self.headerData.get('exp') : self.headerData.get('column'),
};
if (column && column != '') { if (newHeaderData.column && newHeaderData.column != '') {
var coll = self.model.get(self.field.get('name')), var coll = self.model.get(self.field.get('name')),
m = new (self.field.get('model'))( m = new (self.field.get('model'))(
self.headerData.toJSON(), { newHeaderData, {
silent: true, top: self.model.top, silent: true, top: self.model.top,
collection: coll, handler: coll, collection: coll, handler: coll,
}), }),
col_types =self.field.get('col_types') || []; col_types =self.field.get('col_types') || [];
if(!m.get('is_exp')) {
for(var i=0; i < col_types.length; i++) { for(var i=0; i < col_types.length; i++) {
var col_type = col_types[i]; var col_type = col_types[i];
if (col_type['name'] == m.get('column')) { if (col_type['name'] == m.get('column')) {
@ -557,6 +593,7 @@ define('pgadmin.node.exclusion_constraint', [
break; break;
} }
} }
}
coll.add(m); coll.add(m);
@ -786,7 +823,7 @@ define('pgadmin.node.exclusion_constraint', [
!_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew())); !_.isUndefined(m.get('oid'))) || (_.isFunction(m.isNew) && !m.isNew()));
}, },
},{ },{
id: 'columns', label: gettext('Columns'), id: 'columns', label: gettext('Columns/Expressions'),
type: 'collection', group: gettext('Columns'), type: 'collection', group: gettext('Columns'),
deps:['amname'], canDelete: true, editable: false, deps:['amname'], canDelete: true, editable: false,
canAdd: function(m) { canAdd: function(m) {

View File

@ -0,0 +1,14 @@
-- Constraint: Exclusion_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.tableforexclusion DROP CONSTRAINT "Exclusion_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.tableforexclusion
ADD CONSTRAINT "Exclusion_$%{}[]()&*^!@""'`\/#" EXCLUDE USING gist (
(col1 + col3) WITH <>,
col2 WITH <>)
WITH (FILLFACTOR=12)
WHERE (col1 > 1)
DEFERRABLE INITIALLY DEFERRED;
COMMENT ON CONSTRAINT "Exclusion_$%{}[]()&*^!@""'`\/#" ON testschema.tableforexclusion
IS 'Comment for create';

View File

@ -14,6 +14,9 @@
}, { }, {
"name": "col2", "name": "col2",
"cltype": "text" "cltype": "text"
}, {
"name": "col3",
"cltype": "integer",
}], }],
"is_partitioned": false, "is_partitioned": false,
"schema": "testschema", "schema": "testschema",
@ -76,6 +79,43 @@
"data": { "data": {
"name": "Exclusion_$%{}[]()&*^!@\"'`\\/#a" "name": "Exclusion_$%{}[]()&*^!@\"'`\\/#a"
} }
}, {
"type": "create",
"name": "Create Exclusion Constraint with expressions",
"endpoint": "NODE-exclusion_constraint.obj",
"sql_endpoint": "NODE-exclusion_constraint.sql_id",
"data": {
"name": "Exclusion_$%{}[]()&*^!@\"'`\\/#_1",
"comment": "Comment for create",
"fillfactor": "12",
"amname": "gist",
"columns": [
{
"column": "col2",
"order": false,
"nulls_order": false,
"operator": "<>",
"is_sort_nulls_applicable": false,
"is_exp": false
},
{
"column": "(col1+col3)",
"order": false,
"nulls_order": false,
"operator": "<>",
"is_sort_nulls_applicable": false,
"is_exp": true
}
]
},
"expected_sql_file": "create_exclusion_constraint_exp.sql"
}, {
"type": "delete",
"name": "Drop Exclusion Constraint",
"endpoint": "NODE-exclusion_constraint.delete_id",
"data": {
"name": "Exclusion_$%{}[]()&*^!@\"'`\\/#_1a"
}
} }
] ]
} }

View File

@ -0,0 +1,14 @@
-- Constraint: Exclusion_$%{}[]()&*^!@"'`\/#
-- ALTER TABLE testschema.tableforexclusion DROP CONSTRAINT "Exclusion_$%{}[]()&*^!@""'`\/#";
ALTER TABLE testschema.tableforexclusion
ADD CONSTRAINT "Exclusion_$%{}[]()&*^!@""'`\/#" EXCLUDE USING gist (
(col1 + col3) WITH <>,
col2 WITH <>)
WITH (FILLFACTOR=12)
WHERE (col1 > 1)
DEFERRABLE INITIALLY DEFERRED;
COMMENT ON CONSTRAINT "Exclusion_$%{}[]()&*^!@""'`\/#" ON testschema.tableforexclusion
IS 'Comment for create';

View File

@ -14,6 +14,9 @@
}, { }, {
"name": "col2", "name": "col2",
"cltype": "text" "cltype": "text"
}, {
"name": "col3",
"cltype": "integer",
}], }],
"is_partitioned": false, "is_partitioned": false,
"schema": "testschema", "schema": "testschema",
@ -49,7 +52,8 @@
"order": false, "order": false,
"nulls_order": false, "nulls_order": false,
"operator": "<>", "operator": "<>",
"is_sort_nulls_applicable": false "is_sort_nulls_applicable": false,
"is_exp": false
} }
] ]
}, },
@ -90,7 +94,8 @@
"order": false, "order": false,
"nulls_order": false, "nulls_order": false,
"operator": "<>", "operator": "<>",
"is_sort_nulls_applicable": false "is_sort_nulls_applicable": false,
"is_exp": false
} }
] ]
}, },
@ -115,6 +120,43 @@
"data": { "data": {
"name": "Exclusion_$%{}[]()&*^!@\"'`\\/#_1a" "name": "Exclusion_$%{}[]()&*^!@\"'`\\/#_1a"
} }
}, {
"type": "create",
"name": "Create Exclusion Constraint with expressions",
"endpoint": "NODE-exclusion_constraint.obj",
"sql_endpoint": "NODE-exclusion_constraint.sql_id",
"data": {
"name": "Exclusion_$%{}[]()&*^!@\"'`\\/#_1",
"comment": "Comment for create",
"fillfactor": "12",
"amname": "gist",
"columns": [
{
"column": "col2",
"order": false,
"nulls_order": false,
"operator": "<>",
"is_sort_nulls_applicable": false,
"is_exp": false
},
{
"column": "(col1+col3)",
"order": false,
"nulls_order": false,
"operator": "<>",
"is_sort_nulls_applicable": false,
"is_exp": true
}
]
},
"expected_sql_file": "create_exclusion_constraint_exp.sql"
}, {
"type": "delete",
"name": "Drop Exclusion Constraint",
"endpoint": "NODE-exclusion_constraint.delete_id",
"data": {
"name": "Exclusion_$%{}[]()&*^!@\"'`\\/#_1a"
}
} }
] ]
} }

View File

@ -79,7 +79,8 @@ def _get_columns(res):
"order": order, "order": order,
"nulls_order": nulls_order, "nulls_order": nulls_order,
"operator": row['oprname'], "operator": row['oprname'],
"col_type": row['datatype'] "col_type": row['datatype'],
"is_exp": row['is_exp']
}) })
return columns return columns
@ -127,6 +128,9 @@ def get_exclusion_constraints(conn, did, tid, exid=None, template_path=None):
ex['include'] = [col['colname'] for col in res['rows']] ex['include'] = [col['colname'] for col in res['rows']]
if ex.get('amname', '') == "":
ex['amname'] = 'btree'
return True, result['rows'] return True, result['rows']

View File

@ -1,7 +1,7 @@
ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ( ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
{% for col in data.columns %}{% if loop.index != 1 %}, {% for col in data.columns %}{% if loop.index != 1 %},
{% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.include|length > 0 %} {% endif %}{% if col.is_exp %}{{col.column}}{% else %}{{ conn|qtIdent(col.column)}}{% endif %}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.include|length > 0 %}
INCLUDE ({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}){% endif %}{% if data.fillfactor %} INCLUDE ({% for col in data.include %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(col)}}{% endfor %}){% endif %}{% if data.fillfactor %}

View File

@ -10,7 +10,8 @@ SELECT
, ,
coll.collname, coll.collname,
nspc.nspname as collnspname, nspc.nspname as collnspname,
format_type(ty.oid,NULL) AS datatype format_type(ty.oid,NULL) AS datatype,
CASE WHEN pg_get_indexdef(i.indexrelid, {{loop.index}}, true) = a.attname THEN FALSE ELSE TRUE END AS is_exp
FROM pg_index i FROM pg_index i
JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND attnum = {{loop.index}}) JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND attnum = {{loop.index}})
JOIN pg_type ty ON ty.oid=a.atttypid JOIN pg_type ty ON ty.oid=a.atttypid

View File

@ -1,3 +1,4 @@
{% if type is not none %}
SELECT DISTINCT op.oprname as oprname SELECT DISTINCT op.oprname as oprname
FROM pg_operator op, FROM pg_operator op,
( SELECT oid ( SELECT oid
@ -28,3 +29,8 @@ FROM pg_operator op,
WHERE typname = {{type|qtLiteral}}) AS types WHERE typname = {{type|qtLiteral}}) AS types
WHERE oprcom > 0 AND WHERE oprcom > 0 AND
(op.oprleft=types.oid OR op.oprright=types.oid) (op.oprleft=types.oid OR op.oprright=types.oid)
{% else %}
SELECT DISTINCT op.oprname as oprname
FROM pg_operator op
WHERE oprcom > 0
{% endif %}

View File

@ -1,7 +1,7 @@
ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} ( ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
{% for col in data.columns %}{% if loop.index != 1 %}, {% for col in data.columns %}{% if loop.index != 1 %},
{% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %} {% endif %}{% if col.is_exp %}{{col.column}}{% else %}{{ conn|qtIdent(col.column)}}{% endif %}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %} WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}

View File

@ -10,7 +10,8 @@ SELECT
, ,
coll.collname, coll.collname,
nspc.nspname as collnspname, nspc.nspname as collnspname,
format_type(ty.oid,NULL) AS col_type format_type(ty.oid,NULL) AS datatype,
CASE WHEN pg_get_indexdef(i.indexrelid, {{loop.index}}, true) = a.attname THEN FALSE ELSE TRUE END AS is_exp
FROM pg_index i FROM pg_index i
JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND attnum = {{loop.index}}) JOIN pg_attribute a ON (a.attrelid = i.indexrelid AND attnum = {{loop.index}})
JOIN pg_type ty ON ty.oid=a.atttypid JOIN pg_type ty ON ty.oid=a.atttypid

View File

@ -1,3 +1,4 @@
{% if type is not none %}
SELECT DISTINCT op.oprname as oprname SELECT DISTINCT op.oprname as oprname
FROM pg_operator op, FROM pg_operator op,
( SELECT oid ( SELECT oid
@ -27,3 +28,8 @@ FROM pg_operator op,
WHERE typname = {{type|qtLiteral}}) AS types WHERE typname = {{type|qtLiteral}}) AS types
WHERE oprcom > 0 AND WHERE oprcom > 0 AND
(op.oprleft=types.oid OR op.oprright=types.oid) (op.oprleft=types.oid OR op.oprright=types.oid)
{% else %}
SELECT DISTINCT op.oprname as oprname
FROM pg_operator op
WHERE oprcom > 0
{% endif %}

View File

@ -259,9 +259,13 @@ define([
*/ */
_.extend( _.extend(
Backform.InputControl.prototype, { Backform.InputControl.prototype, {
defaults: _.extend(Backform.InputControl.prototype.defaults, {
controlLabelClassName: Backform.controlLabelClassName,
controlsClassName: Backform.controlsClassName,
}),
template: _.template([ template: _.template([
'<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>', '<label class="<%=controlLabelClassName%>" for="<%=cId%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%>">', '<div class="<%=controlsClassName%>">',
' <input type="<%=type%>" id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly aria-readonly=true" : ""%> <%=required ? "required" : ""%> />', ' <input type="<%=type%>" id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>" name="<%=name%>" maxlength="<%=maxlength%>" value="<%-value%>" placeholder="<%-placeholder%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "readonly aria-readonly=true" : ""%> <%=required ? "required" : ""%> />',
' <% if (helpMessage && helpMessage.length) { %>', ' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>', ' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',