/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.pentaho.di.ui.core.database.dialog; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusAdapter; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import org.pentaho.di.core.Const; import org.pentaho.di.core.util.Utils; import org.pentaho.di.core.DBCache; import org.pentaho.di.core.Props; import org.pentaho.di.core.database.Database; import org.pentaho.di.core.database.DatabaseMeta; import org.pentaho.di.core.database.PartitionDatabaseMeta; import org.pentaho.di.core.database.SqlScriptStatement; import org.pentaho.di.core.exception.KettleDatabaseException; import org.pentaho.di.core.logging.KettleLogStore; import org.pentaho.di.core.logging.LogChannel; import org.pentaho.di.core.logging.LogChannelInterface; import org.pentaho.di.core.logging.LoggingObjectInterface; import org.pentaho.di.core.logging.LoggingObjectType; import org.pentaho.di.core.logging.SimpleLoggingObject; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.variables.VariableSpace; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.ui.core.PropsUI; import org.pentaho.di.ui.core.dialog.EnterTextDialog; import org.pentaho.di.ui.core.dialog.ErrorDialog; import org.pentaho.di.ui.core.dialog.PreviewRowsDialog; import org.pentaho.di.ui.core.gui.GUIResource; import org.pentaho.di.ui.core.gui.WindowProperty; import org.pentaho.di.ui.core.widget.StyledTextComp; import org.pentaho.di.ui.trans.step.BaseStepDialog; import org.pentaho.di.ui.trans.steps.tableinput.SQLValuesHighlight; /** * Dialog that allows the user to launch SQL statements towards the database. * * @author Matt * @since 13-10-2003 * */ public class SQLEditor { private static Class<?> PKG = SQLEditor.class; // for i18n purposes, needed by Translator2!! public static final LoggingObjectInterface loggingObject = new SimpleLoggingObject( "SQL Editor", LoggingObjectType.SPOON, null ); private PropsUI props; private Label wlScript; private StyledTextComp wScript; private FormData fdlScript, fdScript; private Label wlPosition; private FormData fdlPosition; private Button wExec, wClear, wCancel; private Listener lsExec, lsClear, lsCancel; private String input; private DatabaseMeta connection; private Shell shell; private DBCache dbcache; private LogChannelInterface log; private int style = SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | SWT.MIN; private Shell parentShell; private VariableSpace variables; private List<SqlScriptStatement> statements; private SQLValuesHighlight highlight; public SQLEditor( Shell parent, int style, DatabaseMeta ci, DBCache dbc, String sql ) { this( null, parent, style, ci, dbc, sql ); } public SQLEditor( VariableSpace space, Shell parent, int style, DatabaseMeta ci, DBCache dbc, String sql ) { props = PropsUI.getInstance(); log = new LogChannel( ci ); input = sql; connection = ci; dbcache = dbc; this.parentShell = parent; this.style = ( style != SWT.None ) ? style : this.style; this.variables = space; } public void open() { shell = new Shell( parentShell, style ); props.setLook( shell ); shell.setImage( GUIResource.getInstance().getImageConnection() ); FormLayout formLayout = new FormLayout(); formLayout.marginWidth = Const.FORM_MARGIN; formLayout.marginHeight = Const.FORM_MARGIN; shell.setLayout( formLayout ); shell.setText( BaseMessages.getString( PKG, "SQLEditor.Title" ) ); int margin = Const.MARGIN; // Script line wlScript = new Label( shell, SWT.NONE ); wlScript.setText( BaseMessages.getString( PKG, "SQLEditor.Editor.Label" ) ); props.setLook( wlScript ); fdlScript = new FormData(); fdlScript.left = new FormAttachment( 0, 0 ); fdlScript.top = new FormAttachment( 0, 0 ); wlScript.setLayoutData( fdlScript ); wScript = new StyledTextComp( this.variables, shell, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL, "" ); wScript.setText( "" ); props.setLook( wScript, Props.WIDGET_STYLE_FIXED ); fdScript = new FormData(); fdScript.left = new FormAttachment( 0, 0 ); fdScript.top = new FormAttachment( wlScript, margin ); fdScript.right = new FormAttachment( 100, -10 ); fdScript.bottom = new FormAttachment( 100, -70 ); wScript.setLayoutData( fdScript ); wScript.addModifyListener( new ModifyListener() { public void modifyText( ModifyEvent arg0 ) { setPosition(); } } ); wScript.addKeyListener( new KeyAdapter() { public void keyPressed( KeyEvent e ) { setPosition(); } public void keyReleased( KeyEvent e ) { setPosition(); } } ); wScript.addFocusListener( new FocusAdapter() { public void focusGained( FocusEvent e ) { setPosition(); } public void focusLost( FocusEvent e ) { setPosition(); } } ); wScript.addMouseListener( new MouseAdapter() { public void mouseDoubleClick( MouseEvent e ) { setPosition(); } public void mouseDown( MouseEvent e ) { setPosition(); } public void mouseUp( MouseEvent e ) { setPosition(); } } ); // SQL Higlighting highlight = new SQLValuesHighlight(); highlight.addKeyWords( connection.getReservedWords() ); wScript.addLineStyleListener( highlight ); wlPosition = new Label( shell, SWT.NONE ); wlPosition.setText( BaseMessages.getString( PKG, "SQLEditor.LineNr.Label", "0" ) ); props.setLook( wlPosition ); fdlPosition = new FormData(); fdlPosition.left = new FormAttachment( 0, 0 ); fdlPosition.top = new FormAttachment( wScript, margin ); fdlPosition.right = new FormAttachment( 100, 0 ); wlPosition.setLayoutData( fdlPosition ); wExec = new Button( shell, SWT.PUSH ); wExec.setText( BaseMessages.getString( PKG, "SQLEditor.Button.Execute" ) ); wClear = new Button( shell, SWT.PUSH ); wClear.setText( BaseMessages.getString( PKG, "SQLEditor.Button.ClearCache" ) ); wCancel = new Button( shell, SWT.PUSH ); wCancel.setText( BaseMessages.getString( PKG, "System.Button.Close" ) ); wClear.setToolTipText( BaseMessages.getString( PKG, "SQLEditor.Button.ClearCache.Tooltip" ) ); BaseStepDialog.positionBottomButtons( shell, new Button[] { wExec, wClear, wCancel }, margin, null ); // Add listeners lsCancel = new Listener() { public void handleEvent( Event e ) { cancel(); } }; lsClear = new Listener() { public void handleEvent( Event e ) { clearCache(); } }; lsExec = new Listener() { public void handleEvent( Event e ) { try { exec(); } catch ( Exception ge ) { // Ignore errors } } }; wCancel.addListener( SWT.Selection, lsCancel ); wClear.addListener( SWT.Selection, lsClear ); wExec.addListener( SWT.Selection, lsExec ); // Detect X or ALT-F4 or something that kills this window... shell.addShellListener( new ShellAdapter() { public void shellClosed( ShellEvent e ) { cancel(); } } ); BaseStepDialog.setSize( shell ); getData(); shell.open(); } public void setPosition() { String scr = wScript.getText(); int linenr = wScript.getLineAtOffset( wScript.getCaretOffset() ) + 1; int posnr = wScript.getCaretOffset(); // Go back from position to last CR: how many positions? int colnr = 0; while ( posnr > 0 && scr.charAt( posnr - 1 ) != '\n' && scr.charAt( posnr - 1 ) != '\r' ) { posnr--; colnr++; } wlPosition.setText( BaseMessages.getString( PKG, "SQLEditor.Position.Label", "" + linenr, "" + colnr ) ); } private void clearCache() { MessageBox mb = new MessageBox( shell, SWT.ICON_QUESTION | SWT.NO | SWT.YES | SWT.CANCEL ); mb.setMessage( BaseMessages.getString( PKG, "SQLEditor.ClearWholeCache.Message", connection.getName() ) ); mb.setText( BaseMessages.getString( PKG, "SQLEditor.ClearWholeCache.Title" ) ); int answer = mb.open(); switch ( answer ) { case SWT.NO: DBCache.getInstance().clear( connection.getName() ); mb = new MessageBox( shell, SWT.ICON_INFORMATION | SWT.OK ); mb.setMessage( BaseMessages.getString( PKG, "SQLEditor.ConnectionCacheCleared.Message", connection .getName() ) ); mb.setText( BaseMessages.getString( PKG, "SQLEditor.ConnectionCacheCleared.Title" ) ); mb.open(); break; case SWT.YES: DBCache.getInstance().clear( null ); mb = new MessageBox( shell, SWT.ICON_INFORMATION | SWT.OK ); mb.setMessage( BaseMessages.getString( PKG, "SQLEditor.WholeCacheCleared.Message" ) ); mb.setText( BaseMessages.getString( PKG, "SQLEditor.WholeCacheCleared.Title" ) ); mb.open(); break; case SWT.CANCEL: break; default: break; } } public void dispose() { props.setScreen( new WindowProperty( shell ) ); shell.dispose(); } /** * Copy information from the meta-data input to the dialog fields. */ public void getData() { if ( input != null ) { wScript.setText( input ); // if (connection!= null) wConnection.setText( connection ); } } private void cancel() { dispose(); } private void exec() { DatabaseMeta ci = connection; if ( ci == null ) { return; } StringBuilder message = new StringBuilder(); Database db = new Database( loggingObject, ci ); boolean first = true; PartitionDatabaseMeta[] partitioningInformation = ci.getPartitioningInformation(); for ( int partitionNr = 0; first || ( partitioningInformation != null && partitionNr < partitioningInformation.length ); partitionNr++ ) { first = false; String partitionId = null; if ( partitioningInformation != null && partitioningInformation.length > 0 ) { partitionId = partitioningInformation[partitionNr].getPartitionId(); } try { db.connect( partitionId ); String sqlScript = Utils.isEmpty( wScript.getSelectionText() ) ? wScript.getText() : wScript.getSelectionText(); // Multiple statements in the script need to be split into individual // executable statements statements = ci.getDatabaseInterface().getSqlScriptStatements( sqlScript + Const.CR ); int nrstats = 0; for ( SqlScriptStatement sql : statements ) { if ( sql.isQuery() ) { // A Query log.logDetailed( "launch SELECT statement: " + Const.CR + sql ); nrstats++; try { List<Object[]> rows = db.getRows( sql.getStatement(), 1000 ); RowMetaInterface rowMeta = db.getReturnRowMeta(); if ( rows.size() > 0 ) { PreviewRowsDialog prd = new PreviewRowsDialog( shell, ci, SWT.NONE, BaseMessages.getString( PKG, "SQLEditor.ResultRows.Title", Integer.toString( nrstats ) ), rowMeta, rows ); prd.open(); } else { MessageBox mb = new MessageBox( shell, SWT.ICON_INFORMATION | SWT.OK ); mb.setMessage( BaseMessages.getString( PKG, "SQLEditor.NoRows.Message", sql ) ); mb.setText( BaseMessages.getString( PKG, "SQLEditor.NoRows.Title" ) ); mb.open(); } } catch ( KettleDatabaseException dbe ) { new ErrorDialog( shell, BaseMessages.getString( PKG, "SQLEditor.ErrorExecSQL.Title" ), BaseMessages .getString( PKG, "SQLEditor.ErrorExecSQL.Message", sql ), dbe ); } } else { log.logDetailed( "launch DDL statement: " + Const.CR + sql ); // A DDL statement nrstats++; int startLogLine = KettleLogStore.getLastBufferLineNr(); try { log.logDetailed( "Executing SQL: " + Const.CR + sql ); db.execStatement( sql.getStatement() ); message.append( BaseMessages.getString( PKG, "SQLEditor.Log.SQLExecuted", sql ) ); message.append( Const.CR ); // Clear the database cache, in case we're using one... if ( dbcache != null ) { dbcache.clear( ci.getName() ); } // mark the statement in green in the dialog... // sql.setOk( true ); } catch ( Exception dbe ) { sql.setOk( false ); String error = BaseMessages.getString( PKG, "SQLEditor.Log.SQLExecError", sql, dbe.toString() ); message.append( error ).append( Const.CR ); ErrorDialog dialog = new ErrorDialog( shell, BaseMessages.getString( PKG, "SQLEditor.ErrorExecSQL.Title" ), error, dbe, true ); if ( dialog.isCancelled() ) { break; } } finally { int endLogLine = KettleLogStore.getLastBufferLineNr(); sql.setLoggingText( KettleLogStore.getAppender().getLogBufferFromTo( db.getLogChannelId(), true, startLogLine, endLogLine ).toString() ); sql.setComplete( true ); refreshExecutionResults(); } } } message.append( BaseMessages.getString( PKG, "SQLEditor.Log.StatsExecuted", Integer.toString( nrstats ) ) ); if ( partitionId != null ) { message.append( BaseMessages.getString( PKG, "SQLEditor.Log.OnPartition", partitionId ) ); } message.append( Const.CR ); } catch ( KettleDatabaseException dbe ) { MessageBox mb = new MessageBox( shell, SWT.OK | SWT.ICON_ERROR ); String error = BaseMessages.getString( PKG, "SQLEditor.Error.CouldNotConnect.Message", ( connection == null ? "" : connection.getName() ), dbe.getMessage() ); message.append( error ).append( Const.CR ); mb.setMessage( error ); mb.setText( BaseMessages.getString( PKG, "SQLEditor.Error.CouldNotConnect.Title" ) ); mb.open(); } finally { db.disconnect(); refreshExecutionResults(); } } EnterTextDialog dialog = new EnterTextDialog( shell, BaseMessages.getString( PKG, "SQLEditor.Result.Title" ), BaseMessages .getString( PKG, "SQLEditor.Result.Message" ), message.toString(), true ); dialog.open(); } /** * During or after an execution we will mark regions of the SQL editor dialog in green or red. */ protected void refreshExecutionResults() { highlight.setScriptStatements( statements ); wScript.redraw(); } }