From 67c9cb7abc81249633d731af877eff0cc721800a Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 20 Oct 2022 15:37:28 +0200 Subject: [PATCH] IDEMPIERE-5451 Create a form SQL Query - similar to SQL process to execute queries (FHCA-3849) (#1533) * IDEMPIERE-5451 Create a form SQL Query - similar to SQL process to execute queries (FHCA-3849) * - fix wrong default in SysConfig * - remove unnecessary annotation * - process the query in a read-only transaction * - commit not needed since this is readonly --- .../i9/oracle/202210131930_IDEMPIERE-5451.sql | 82 +++++ .../202210131930_IDEMPIERE-5451.sql | 79 +++++ .../src/org/compiere/model/MSysConfig.java | 8 +- .../src/org/compiere/model/SystemIDs.java | 1 + .../adempiere/webui/apps/form/WSQLQuery.java | 335 ++++++++++++++++++ 5 files changed, 503 insertions(+), 2 deletions(-) create mode 100644 migration/i9/oracle/202210131930_IDEMPIERE-5451.sql create mode 100644 migration/i9/postgresql/202210131930_IDEMPIERE-5451.sql create mode 100644 org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WSQLQuery.java diff --git a/migration/i9/oracle/202210131930_IDEMPIERE-5451.sql b/migration/i9/oracle/202210131930_IDEMPIERE-5451.sql new file mode 100644 index 0000000000..f7c9905d3c --- /dev/null +++ b/migration/i9/oracle/202210131930_IDEMPIERE-5451.sql @@ -0,0 +1,82 @@ +-- IDEMPIERE-5451 Create a form SQL Query - similar to SQL process to execute queries (FHCA-3849) +SELECT register_migration_script('202210131930_IDEMPIERE-5451.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Oct 13, 2022, 7:30:19 PM CEST +INSERT INTO AD_Form (AD_Form_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,Classname,AccessLevel,EntityType,IsBetaFunctionality,AD_Form_UU) VALUES (200018,0,0,'Y',TO_TIMESTAMP('2022-10-13 19:30:19','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2022-10-13 19:30:19','YYYY-MM-DD HH24:MI:SS'),100,'SQL Process','Process SQL Queries','Process SQL Queries','org.adempiere.webui.apps.form.WSQLQuery','4','D','N','c14e305d-8d88-4ee5-9cf6-b2e5ce1213b6') +; + +-- Oct 13, 2022, 7:30:35 PM CEST +UPDATE AD_Form SET Name='SQL Query',Updated=TO_TIMESTAMP('2022-10-13 19:30:35','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Form_ID=200018 +; + +-- Oct 13, 2022, 7:31:00 PM CEST +INSERT INTO AD_Menu (AD_Menu_ID,Name,Description,Action,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsSummary,IsSOTrx,AD_Form_ID,IsReadOnly,EntityType,IsCentrallyMaintained,AD_Menu_UU) VALUES (200218,'SQL Query','Process SQL Queries','X',0,0,'Y',TO_TIMESTAMP('2022-10-13 19:31:00','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2022-10-13 19:31:00','YYYY-MM-DD HH24:MI:SS'),100,'N','N',200018,'N','D','Y','08ed5ba3-ade5-41af-8abc-a10066e072e3') +; + +-- Oct 13, 2022, 7:31:00 PM CEST +INSERT INTO AD_TreeNodeMM (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo, AD_TreeNodeMM_UU) SELECT t.AD_Client_ID, 0, 'Y', getDate(), 100, getDate(), 100,t.AD_Tree_ID, 200218, 0, 999, Generate_UUID() FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='MM' AND NOT EXISTS (SELECT * FROM AD_TreeNodeMM e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=200218) +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=12,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200218 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=13,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=289 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=14,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=302 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=15,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200168 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=16,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200169 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=17,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=303 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=18,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200047 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=19,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200048 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=20,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=321 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=21,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=461 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=22,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=53193 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=23,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200161 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=24,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=53322 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=25,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=383 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=26,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200177 +; + diff --git a/migration/i9/postgresql/202210131930_IDEMPIERE-5451.sql b/migration/i9/postgresql/202210131930_IDEMPIERE-5451.sql new file mode 100644 index 0000000000..98b2e477db --- /dev/null +++ b/migration/i9/postgresql/202210131930_IDEMPIERE-5451.sql @@ -0,0 +1,79 @@ +-- IDEMPIERE-5451 Create a form SQL Query - similar to SQL process to execute queries (FHCA-3849) +SELECT register_migration_script('202210131930_IDEMPIERE-5451.sql') FROM dual; + +-- Oct 13, 2022, 7:30:19 PM CEST +INSERT INTO AD_Form (AD_Form_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,Classname,AccessLevel,EntityType,IsBetaFunctionality,AD_Form_UU) VALUES (200018,0,0,'Y',TO_TIMESTAMP('2022-10-13 19:30:19','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2022-10-13 19:30:19','YYYY-MM-DD HH24:MI:SS'),100,'SQL Process','Process SQL Queries','Process SQL Queries','org.adempiere.webui.apps.form.WSQLQuery','4','D','N','c14e305d-8d88-4ee5-9cf6-b2e5ce1213b6') +; + +-- Oct 13, 2022, 7:30:35 PM CEST +UPDATE AD_Form SET Name='SQL Query',Updated=TO_TIMESTAMP('2022-10-13 19:30:35','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Form_ID=200018 +; + +-- Oct 13, 2022, 7:31:00 PM CEST +INSERT INTO AD_Menu (AD_Menu_ID,Name,Description,"action",AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsSummary,IsSOTrx,AD_Form_ID,IsReadOnly,EntityType,IsCentrallyMaintained,AD_Menu_UU) VALUES (200218,'SQL Query','Process SQL Queries','X',0,0,'Y',TO_TIMESTAMP('2022-10-13 19:31:00','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2022-10-13 19:31:00','YYYY-MM-DD HH24:MI:SS'),100,'N','N',200018,'N','D','Y','08ed5ba3-ade5-41af-8abc-a10066e072e3') +; + +-- Oct 13, 2022, 7:31:00 PM CEST +INSERT INTO AD_TreeNodeMM (AD_Client_ID,AD_Org_ID, IsActive,Created,CreatedBy,Updated,UpdatedBy, AD_Tree_ID, Node_ID, Parent_ID, SeqNo, AD_TreeNodeMM_UU) SELECT t.AD_Client_ID, 0, 'Y', statement_timestamp(), 100, statement_timestamp(), 100,t.AD_Tree_ID, 200218, 0, 999, Generate_UUID() FROM AD_Tree t WHERE t.AD_Client_ID=0 AND t.IsActive='Y' AND t.IsAllNodes='Y' AND t.TreeType='MM' AND NOT EXISTS (SELECT * FROM AD_TreeNodeMM e WHERE e.AD_Tree_ID=t.AD_Tree_ID AND Node_ID=200218) +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=12,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200218 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=13,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=289 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=14,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=302 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=15,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200168 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=16,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200169 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=17,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=303 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=18,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200047 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=19,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200048 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=20,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=321 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=21,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=461 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=22,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=53193 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=23,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200161 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=24,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=53322 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=25,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=383 +; + +-- Oct 13, 2022, 7:31:15 PM CEST +UPDATE AD_TreeNodeMM SET Parent_ID=155, SeqNo=26,Updated=TO_TIMESTAMP('2022-10-13 19:31:15','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tree_ID=10 AND Node_ID=200177 +; + diff --git a/org.adempiere.base/src/org/compiere/model/MSysConfig.java b/org.adempiere.base/src/org/compiere/model/MSysConfig.java index 89cf6acae5..93424d5eaf 100644 --- a/org.adempiere.base/src/org/compiere/model/MSysConfig.java +++ b/org.adempiere.base/src/org/compiere/model/MSysConfig.java @@ -44,7 +44,7 @@ public class MSysConfig extends X_AD_SysConfig /** * */ - private static final long serialVersionUID = 9018760438155531804L; + private static final long serialVersionUID = -1225938049955333281L; public static final String ADDRESS_VALIDATION = "ADDRESS_VALIDATION"; public static final String ALERT_SEND_ATTACHMENT_AS_XLS = "ALERT_SEND_ATTACHMENT_AS_XLS"; @@ -102,7 +102,11 @@ public class MSysConfig extends X_AD_SysConfig public static final String ENABLE_PAYMENTBOX_BUTTON = "ENABLE_PAYMENTBOX_BUTTON"; public static final String FEEDBACK_EMAIL_CC = "FEEDBACK_EMAIL_CC"; public static final String FEEDBACK_EMAIL_TO = "FEEDBACK_EMAIL_TO"; - public static final String FORM_SQL_PROCESS_ALLOWED_KEYWORDS = "FORM_SQL_PROCESS_ALLOWED_KEYWORDS"; + public static final String FORM_SQL_PROCESS_ALLOWED_KEYWORDS = "FORM_SQL_PROCESS_ALLOWED_KEYWORDS"; + public static final String FORM_SQL_QUERY_ALLOWED_KEYWORDS = "FORM_SQL_QUERY_ALLOWED_KEYWORDS"; + public static final String FORM_SQL_QUERY_LOG_ISSUE = "FORM_SQL_QUERY_LOG_ISSUE"; + public static final String FORM_SQL_QUERY_MAX_RECORDS = "FORM_SQL_QUERY_MAX_RECORDS"; + public static final String FORM_SQL_QUERY_TIMEOUT_IN_SECONDS = "FORM_SQL_QUERY_TIMEOUT_IN_SECONDS"; public static final String GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS = "GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS"; public static final String HTML_REPORT_MINIFY = "HTML_REPORT_MINIFY"; public static final String HTML_REPORT_THEME = "HTML_REPORT_THEME"; diff --git a/org.adempiere.base/src/org/compiere/model/SystemIDs.java b/org.adempiere.base/src/org/compiere/model/SystemIDs.java index 3ef88880e7..4b8e8a76a2 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemIDs.java +++ b/org.adempiere.base/src/org/compiere/model/SystemIDs.java @@ -67,6 +67,7 @@ public class SystemIDs public final static int FORM_ADD_AUTHORIZATION = 200016; public final static int FORM_MFA_REGISTER = 200017; public final static int FORM_SQL_PROCESS = 111; + public final static int FORM_SQL_QUERY = 200018; public final static int MENU_NOTICE = 233; diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WSQLQuery.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WSQLQuery.java new file mode 100644 index 0000000000..dd50f594e1 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/apps/form/WSQLQuery.java @@ -0,0 +1,335 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + * Contributors: * + * - Carlos Ruiz - globalqss - bxservice * + **********************************************************************/ + +package org.adempiere.webui.apps.form; + +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import org.adempiere.exceptions.DBException; +import org.adempiere.webui.component.Button; +import org.adempiere.webui.component.Label; +import org.adempiere.webui.component.ListModelTable; +import org.adempiere.webui.component.Textbox; +import org.adempiere.webui.component.WListItemRenderer; +import org.adempiere.webui.component.WListbox; +import org.adempiere.webui.panel.ADForm; +import org.adempiere.webui.theme.ThemeManager; +import org.adempiere.webui.util.ZKUpdateUtil; +import org.compiere.model.MIssue; +import org.compiere.model.MSysConfig; +import org.compiere.model.SystemIDs; +import org.compiere.util.CLogger; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.compiere.util.Msg; +import org.compiere.util.Trx; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zul.Borderlayout; +import org.zkoss.zul.Center; +import org.zkoss.zul.Div; +import org.zkoss.zul.Frozen; +import org.zkoss.zul.North; +import org.zkoss.zul.South; + +/** + * A Custom Form to process SQL queries. + * + * The statement to be executed can be restricted using SysConfig + */ +public class WSQLQuery extends ADForm implements EventListener +{ + /** + * + */ + private static final long serialVersionUID = -6641250848300700313L; + + /** Log. */ + private static final CLogger log = CLogger.getCLogger(WSQLQuery.class); + + /** Grid used to layout components. */ + private Borderlayout layout = new Borderlayout(); + /** SQL label. */ + private Label m_lblSql = new Label("SQL"); + /** SQL statement field. */ + private Textbox m_txbSqlField = new Textbox(); + /** Process button. */ + private Button m_btnSql = createProcessButton(); + /** Field to hold result of SQL statement execution. */ + private Textbox m_txbResultField = new Textbox(); + /** Grid to show the result data. */ + private ListModelTable model = null; + private WListbox listbox = new WListbox(); + + /** + * REGEX_REMOVE_COMMENTS + */ + private static final String REGEX_REMOVE_COMMENTS = "/\\*(?:.|[\\n\\r])*?\\*/"; + + /** + * REGEX_REMOVE_QUOTED_STRINGS + */ + private static final String REGEX_REMOVE_QUOTED_STRINGS = "'(?:.|[\\n\\r])*?'"; + + /** + * REGEX_REMOVE_LEADING_SPACES + */ + private static final String REGEX_REMOVE_LEADING_SPACES = "^\\s+"; + + /** + * Default constructor. + */ + public WSQLQuery() { + super(); + } + + @Override + protected void initForm() { + North north = new North(); + Center center = new Center(); + South south = new South(); + final int maxStatementLength = 10000; + final int noStatementRows = 3; + final int noResultRows = 2; + + ZKUpdateUtil.setWidth(layout, "100%"); + ZKUpdateUtil.setHeight(layout, "100%"); + layout.setStyle("background-color: transparent; position: relative;"); + + Div div = new Div(); + // create the top row of components + m_txbSqlField.setMultiline(true); + m_txbSqlField.setMaxlength(maxStatementLength); + m_txbSqlField.setRows(noStatementRows); + ZKUpdateUtil.setHflex(m_txbSqlField, "1"); + m_txbSqlField.setReadonly(false); + + m_btnSql.addEventListener(Events.ON_CLICK, this); + + div.appendChild(m_lblSql); + div.appendChild(m_txbSqlField); + div.appendChild(m_btnSql); + north.appendChild(div); + layout.appendChild(north); + + // create the bottom row of components + m_txbResultField.setRows(noResultRows); + ZKUpdateUtil.setHflex(m_txbResultField, "1"); + m_txbResultField.setReadonly(true); + + center.appendChild(listbox); + ZKUpdateUtil.setVflex(listbox, "1"); + ZKUpdateUtil.setHflex(listbox, "1"); + + layout.appendChild(center); + + south.appendChild(m_txbResultField); + layout.appendChild(south); + + this.appendChild(layout); + + return; + } + + /** + * Create Process Button. + * @return button + */ + public static final Button createProcessButton() { + Button btnProcess = new Button(); + if(ThemeManager.isUseFontIconForImage()) + btnProcess.setIconSclass("z-icon-Process"); + else + btnProcess.setImage(ThemeManager.getThemeResource("images/Process24.png")); + btnProcess.setName(Msg.getMsg(Env.getCtx(), "Process")); + + return btnProcess; + } // createProcessButton + + /** + * Process SQL Statements. + * + * @param sqlStatement a single SQL statement + * @param allowDML whether to allow DML statements + * @return a string summarizing the results + */ + public String processStatement (String sqlStatement) { + m_txbResultField.setText(null); + listbox.clear(); + + if (sqlStatement == null || sqlStatement.length() == 0) + return ""; + StringBuilder sb = new StringBuilder(); + char[] chars = sqlStatement.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (Character.isWhitespace(c)) + sb.append(' '); + else + sb.append(c); + } + String sql = sb.toString().trim(); + if (sql.length() == 0) + return ""; + // + StringBuilder result = new StringBuilder(); + String SQL = sql.toUpperCase(); + String cleanSQL = SQL + .replaceAll(REGEX_REMOVE_COMMENTS, "") + .replaceAll(REGEX_REMOVE_QUOTED_STRINGS, "") + .replaceFirst(REGEX_REMOVE_LEADING_SPACES, ""); + + if (cleanSQL.contains(";")) { + result.append("ERROR: Multiple Commands Not Allowed"); + return result.toString(); + } + + int timeout = MSysConfig.getIntValue(MSysConfig.FORM_SQL_QUERY_TIMEOUT_IN_SECONDS, 120); + int maxRecords = MSysConfig.getIntValue(MSysConfig.FORM_SQL_QUERY_MAX_RECORDS, 500); + + String[] allowedKeywords = MSysConfig.getValue(MSysConfig.FORM_SQL_QUERY_ALLOWED_KEYWORDS, "SELECT ,WITH ,SHOW ").split(","); + boolean isError = true; + for (int i = 0; i < allowedKeywords.length; i++) { + if (cleanSQL.startsWith(allowedKeywords[i])) { + isError = false; + break; + } + } + if (isError) { + result.append("ERROR: Not Allowed Command"); + return result.toString(); + } + + List header = new ArrayList(); + header.add("#"); + model = new ListModelTable(); + Frozen frozen = new Frozen(); + frozen.setColumns(1); + listbox.appendChild(frozen); + + Trx trx = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + try { + long start = System.currentTimeMillis(); + + String trxName = Trx.createTrxName("WSQLQuery"); + trx = Trx.get(trxName, false); + trx.setDisplayName(getClass().getName()+"_processStatement"); + trx.getConnection().setReadOnly(true); + + pstmt = DB.prepareNormalReadReplicaStatement(sql, trxName); + pstmt.setQueryTimeout(timeout); + rs = pstmt.executeQuery(); + + ResultSetMetaData meta = rs.getMetaData(); + int count = 0; + for (int col = 1; col <= meta.getColumnCount(); col++) { + String columnName = meta.getColumnLabel(col); + header.add(columnName); + } + + while (rs.next ()) { + if (count >= maxRecords) { + result.append("Maximum of " + maxRecords + " records reached. "); + break; + } + List row = new ArrayList(); + row.add(++count); + for (int col = 1; col <= meta.getColumnCount(); col++) { + String colName = header.get(col).toLowerCase(); + if (rs.getObject(col) instanceof BigDecimal + && (colName.endsWith("_id") || colName.equals("createdby") || colName.equals("updatedby"))) + row.add(rs.getInt(col)); + else + row.add(rs.getObject(col)); + } // for all columns + model.add(row); + } + long end = System.currentTimeMillis(); + BigDecimal durationSeconds = BigDecimal.valueOf(end).subtract(BigDecimal.valueOf(start)).divide(BigDecimal.valueOf(1000.0)); + result.append("Count = ").append(count); + if (MSysConfig.getBooleanValue(MSysConfig.FORM_SQL_QUERY_LOG_ISSUE, true)) { + MIssue issue = new MIssue(Env.getCtx(), 0, null); + issue.setIssueSummary("SQL executed on SQL Query form"); + issue.setStackTrace(sql); + issue.setResponseText(result.toString()); + issue.setIssueSource(MIssue.ISSUESOURCE_Form); + issue.setUserName(Env.getContext(Env.getCtx(), Env.AD_USER_NAME)); + issue.setAD_Form_ID(SystemIDs.FORM_SQL_QUERY); + issue.setProcessed(true); + issue.setComments("Duration : " + durationSeconds + " seconds"); + issue.saveEx(); + } + + // Show the result in WListbox + WListItemRenderer renderer = new WListItemRenderer(header); + model.setNoColumns(header.size()); + listbox.setModel(model); + listbox.setItemRenderer(renderer); + listbox.initialiseHeader(); + listbox.setSizedByContent(true); + + } catch (Exception e) { + if (trx != null) { + trx.rollback(); + } + if (DBException.isTimeout(e)) { + result.append("Maximum of " + timeout + " seconds reached, query cancelled."); + } else { + e.printStackTrace(); + String exception = e.toString(); + log.log(Level.SEVERE, "process statement: " + sql + " - " + exception); + result.append("Exception => ").append(exception); + } + } finally { + DB.close(rs, pstmt); + rs = null; pstmt = null; + if (trx != null) { + trx.close(); + trx = null; + } + } + + return result.toString(); + } + + /** + * Process the events for this form + * @param event + */ + public void onEvent(Event event) throws Exception { + if (event.getTarget() == m_btnSql) + m_txbResultField.setText(processStatement(m_txbSqlField.getText())); + super.onEvent(event); + } +}