/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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.jkiss.dbeaver.ui.controls.resultset;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.DBValueFormatting;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.data.DBDDisplayFormat;
import org.jkiss.dbeaver.model.data.DBDLabelValuePair;
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.exec.DBCSession;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.dbeaver.ui.UIIcon;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.ViewerColumnController;
import org.jkiss.dbeaver.ui.controls.ListContentProvider;
import org.jkiss.dbeaver.ui.data.IValueController;
import org.jkiss.dbeaver.ui.data.IValueEditor;
import org.jkiss.dbeaver.ui.data.editors.ReferenceValueEditor;
import org.jkiss.dbeaver.ui.dialogs.BaseDialog;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.util.*;
import java.util.regex.Pattern;
class FilterValueEditDialog extends BaseDialog {
private static final Log log = Log.getLog(FilterValueEditDialog.class);
private static final String DIALOG_ID = "DBeaver.FilterValueEditDialog";//$NON-NLS-1$
public static final int MAX_MULTI_VALUES = 1000;
public static final String MULTI_KEY_LABEL = "...";
@NotNull
private final ResultSetViewer viewer;
@NotNull
private final DBDAttributeBinding attr;
@NotNull
private final ResultSetRow[] rows;
@NotNull
private final DBCLogicalOperator operator;
private Object value;
private IValueEditor editor;
private Text textControl;
private CheckboxTableViewer table;
private String filterPattern;
private KeyLoadJob loadJob;
public FilterValueEditDialog(@NotNull ResultSetViewer viewer, @NotNull DBDAttributeBinding attr, @NotNull ResultSetRow[] rows, @NotNull DBCLogicalOperator operator) {
super(viewer.getControl().getShell(), "Edit value", null);
this.viewer = viewer;
this.attr = attr;
this.rows = rows;
this.operator = operator;
}
@Override
protected IDialogSettings getDialogBoundsSettings()
{
return UIUtils.getDialogSettings(DIALOG_ID + "." + operator.name());
}
@Override
protected Composite createDialogArea(Composite parent)
{
Composite composite = super.createDialogArea(parent);
Label label = new Label(composite, SWT.NONE);
label.setText(attr.getName() + " " + operator.getStringValue() + " :");
label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
int argumentCount = operator.getArgumentCount();
if (argumentCount == 1) {
createSingleValueEditor(composite);
} else if (argumentCount < 0) {
createMultiValueSelector(composite);
}
return parent;
}
private void createSingleValueEditor(Composite composite) {
Composite editorPlaceholder = UIUtils.createPlaceholder(composite, 1);
editorPlaceholder.setLayoutData(new GridData(GridData.FILL_BOTH));
editorPlaceholder.setLayout(new FillLayout());
ResultSetRow singleRow = rows[0];
final ResultSetValueController valueController = new ResultSetValueController(
viewer,
attr,
singleRow,
IValueController.EditType.INLINE,
editorPlaceholder) {
@Override
public boolean isReadOnly() {
// Filter value is never read-only
return false;
}
};
try {
editor = valueController.getValueManager().createEditor(valueController);
if (editor != null) {
editor.createControl();
editor.primeEditorValue(valueController.getValue());
}
} catch (DBException e) {
log.error("Can't create inline value editor", e);
}
if (editor == null) {
textControl = new Text(composite, SWT.BORDER | SWT.WRAP | SWT.V_SCROLL);
textControl.setText("");
GridData gd = new GridData(GridData.FILL_BOTH);
gd.widthHint = 300;
gd.heightHint = 300;
gd.minimumHeight = 100;
gd.minimumWidth = 100;
textControl.setLayoutData(gd);
}
}
private void createMultiValueSelector(Composite composite) {
table = CheckboxTableViewer.newCheckList(composite, SWT.BORDER | SWT.MULTI | SWT.CHECK | SWT.FULL_SELECTION);
table.getTable().setLinesVisible(true);
table.getTable().setHeaderVisible(true);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.widthHint = 400;
gd.heightHint = 300;
table.getTable().setLayoutData(gd);
table.setContentProvider(new ListContentProvider());
ViewerColumnController columnController = new ViewerColumnController(getClass().getName(), table);
columnController.addColumn("Value", "Value", SWT.LEFT, true, true, new ColumnLabelProvider() {
@Override
public String getText(Object element) {
return attr.getValueHandler().getValueDisplayString(attr, ((DBDLabelValuePair)element).getValue(), DBDDisplayFormat.UI);
}
});
columnController.addColumn("Description", "Row description (composed from dictionary columns)", SWT.LEFT, true, true, new ColumnLabelProvider() {
@Override
public String getText(Object element) {
return ((DBDLabelValuePair)element).getLabel();
}
});
columnController.createColumns();
MenuManager menuMgr = new MenuManager();
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager)
{
UIUtils.fillDefaultTableContextMenu(manager, table.getTable());
manager.add(new Separator());
manager.add(new Action("Select &All") {
@Override
public void run() {
for (DBDLabelValuePair row : getMultiValues()) {
table.setChecked(row, true);
}
}
});
manager.add(new Action("Select &None") {
@Override
public void run() {
for (DBDLabelValuePair row : getMultiValues()) {
table.setChecked(row, false);
}
}
});
}
});
menuMgr.setRemoveAllWhenShown(true);
table.getTable().setMenu(menuMgr.createContextMenu(table.getTable()));
if (attr.getDataKind() == DBPDataKind.STRING) {
// Create filter text
final Text valueFilterText = new Text(composite, SWT.BORDER);
valueFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
valueFilterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
filterPattern = valueFilterText.getText();
if (filterPattern.isEmpty()) {
filterPattern = null;
}
loadValues();
}
});
}
filterPattern = null;
loadValues();
}
private void loadValues() {
if (loadJob != null) {
if (loadJob.getState() == Job.RUNNING) {
loadJob.cancel();
}
loadJob.schedule(100);
return;
}
// Load values
final DBSEntityReferrer enumerableConstraint = ReferenceValueEditor.getEnumerableConstraint(attr);
if (enumerableConstraint != null) {
loadConstraintEnum(enumerableConstraint);
} else if (attr.getEntityAttribute() instanceof DBSAttributeEnumerable) {
loadAttributeEnum((DBSAttributeEnumerable) attr.getEntityAttribute());
} else {
loadMultiValueList(Collections.<DBDLabelValuePair>emptyList());
}
}
private void loadConstraintEnum(final DBSEntityReferrer refConstraint) {
loadJob = new KeyLoadJob("Load constraint '" + refConstraint.getName() + "' values") {
@Override
protected Collection<DBDLabelValuePair> readEnumeration(DBCSession session) throws DBException {
final DBSEntityAttribute tableColumn = attr.getEntityAttribute();
if (tableColumn == null) {
return null;
}
final DBSEntityAttributeRef fkColumn = DBUtils.getConstraintAttribute(session.getProgressMonitor(), refConstraint, tableColumn);
if (fkColumn == null) {
return null;
}
DBSEntityAssociation association;
if (refConstraint instanceof DBSEntityAssociation) {
association = (DBSEntityAssociation)refConstraint;
} else {
return null;
}
final DBSEntityAttribute refColumn = DBUtils.getReferenceAttribute(session.getProgressMonitor(), association, tableColumn);
if (refColumn == null) {
return null;
}
final DBSEntityAttribute fkAttribute = fkColumn.getAttribute();
final DBSEntityConstraint refConstraint = association.getReferencedConstraint();
final DBSConstraintEnumerable enumConstraint = (DBSConstraintEnumerable) refConstraint;
if (fkAttribute != null && enumConstraint != null) {
return enumConstraint.getKeyEnumeration(
session,
refColumn,
filterPattern,
null,
MAX_MULTI_VALUES);
}
return null;
}
};
loadJob.schedule();
}
private void loadAttributeEnum(final DBSAttributeEnumerable attributeEnumerable) {
table.getTable().getColumn(1).setText("Count");
loadJob = new KeyLoadJob("Load '" + attr.getName() + "' values") {
@Override
protected Collection<DBDLabelValuePair> readEnumeration(DBCSession session) throws DBException {
return attributeEnumerable.getValueEnumeration(session, filterPattern, MAX_MULTI_VALUES);
}
};
loadJob.schedule();
}
private void loadMultiValueList(@NotNull Collection<DBDLabelValuePair> values) {
Pattern pattern = null;
if (!CommonUtils.isEmpty(filterPattern)) {
pattern = Pattern.compile(SQLUtils.makeLikePattern("%" + filterPattern + "%"), Pattern.CASE_INSENSITIVE);
}
// Get all values from actual RSV data
boolean hasNulls = false;
java.util.Map<Object, DBDLabelValuePair> rowData = new HashMap<>();
for (DBDLabelValuePair pair : values) {
final DBDLabelValuePair oldLabel = rowData.get(pair.getValue());
if (oldLabel != null) {
// Duplicate label for single key - may happen in case of composite foreign keys
String multiLabel = oldLabel.getLabel() + "," + pair.getLabel();
if (multiLabel.length() > 200) {
multiLabel = multiLabel.substring(0, 200) + MULTI_KEY_LABEL;
}
rowData.put(pair.getValue(), new DBDLabelValuePair(multiLabel, pair.getValue()));
} else{
rowData.put(pair.getValue(), pair);
}
}
// Add values from fetched rows
for (ResultSetRow row : viewer.getModel().getAllRows()) {
Object cellValue = viewer.getModel().getCellValue(attr, row);
if (DBUtils.isNullValue(cellValue)) {
hasNulls = true;
continue;
}
if (!rowData.containsKey(cellValue)) {
String itemString = attr.getValueHandler().getValueDisplayString(attr, cellValue, DBDDisplayFormat.UI);
rowData.put(cellValue, new DBDLabelValuePair(itemString, cellValue));
}
}
java.util.List<DBDLabelValuePair> sortedList = new ArrayList<>(rowData.values());
Collections.sort(sortedList);
if (pattern != null) {
for (Iterator<DBDLabelValuePair> iter = sortedList.iterator(); iter.hasNext(); ) {
final DBDLabelValuePair valuePair = iter.next();
String itemString = attr.getValueHandler().getValueDisplayString(attr, valuePair.getValue(), DBDDisplayFormat.UI);
if (!pattern.matcher(itemString).matches() && (valuePair.getLabel() == null || !pattern.matcher(valuePair.getLabel()).matches())) {
iter.remove();
}
}
}
Collections.sort(sortedList);
if (hasNulls) {
sortedList.add(0, new DBDLabelValuePair(DBValueFormatting.getDefaultValueDisplayString(null, DBDDisplayFormat.UI), null));
}
Set<Object> checkedValues = new HashSet<>();
for (ResultSetRow row : rows) {
Object value = viewer.getModel().getCellValue(attr, row);
checkedValues.add(value);
}
table.setInput(sortedList);
DBDLabelValuePair firstVisibleItem = null;
for (DBDLabelValuePair row : sortedList) {
Object cellValue = row.getValue();
if (checkedValues.contains(cellValue)) {
table.setChecked(row, true);
if (firstVisibleItem == null) {
firstVisibleItem = row;
}
}
}
ViewerColumnController.getFromControl(table.getTable()).repackColumns();
if (firstVisibleItem != null) {
final Widget item = table.testFindItem(firstVisibleItem);
if (item != null) {
table.getTable().setSelection((TableItem) item);
table.getTable().showItem((TableItem) item);
}
}
}
@Override
protected void createButtonsForButtonBar(Composite parent)
{
if (operator.getArgumentCount() == 1) {
Button copyButton = createButton(parent, IDialogConstants.DETAILS_ID, "Clipboard", false);
copyButton.setImage(DBeaverIcons.getImage(UIIcon.FILTER_CLIPBOARD));
}
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
}
@Override
protected void buttonPressed(int buttonId) {
if (buttonId == IDialogConstants.DETAILS_ID) {
try {
Object value = ResultSetUtils.getAttributeValueFromClipboard(attr);
editor.primeEditorValue(value);
} catch (DBException e) {
UIUtils.showErrorDialog(getShell(), "Copy from clipboard", "Can't copy value", e);
}
} else {
super.buttonPressed(buttonId);
}
}
@Override
protected void okPressed()
{
if (table != null) {
java.util.List<Object> values = new ArrayList<>();
for (DBDLabelValuePair item : getMultiValues()) {
if (table.getChecked(item)) {
values.add(item.getValue());
}
}
value = values.toArray();
} else if (editor != null) {
try {
value = editor.extractEditorValue();
} catch (DBException e) {
log.error("Can't get editor value", e);
}
} else {
value = textControl.getText();
}
super.okPressed();
}
private Collection<DBDLabelValuePair> getMultiValues() {
return (Collection<DBDLabelValuePair>)table.getInput();
}
public Object getValue() {
return value;
}
private abstract class KeyLoadJob extends AbstractJob {
protected KeyLoadJob(String name) {
super(name);
}
@Override
protected IStatus run(DBRProgressMonitor monitor) {
final DBCExecutionContext executionContext = viewer.getExecutionContext();
if (executionContext == null) {
return Status.OK_STATUS;
}
try (DBCSession session = DBUtils.openUtilSession(monitor, executionContext.getDataSource(), "Read value enumeration")) {
final Collection<DBDLabelValuePair> valueEnumeration = readEnumeration(session);
if (valueEnumeration == null) {
return Status.OK_STATUS;
} else {
populateValues(valueEnumeration);
}
} catch (DBException e) {
populateValues(Collections.<DBDLabelValuePair>emptyList());
return GeneralUtils.makeExceptionStatus(e);
}
return Status.OK_STATUS;
}
@Nullable
protected abstract Collection<DBDLabelValuePair> readEnumeration(DBCSession session) throws DBException;
protected void populateValues(@NotNull final Collection<DBDLabelValuePair> values) {
DBeaverUI.asyncExec(new Runnable() {
@Override
public void run() {
loadMultiValueList(values);
}
});
}
}
}