/*
* 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.search.data;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.DBeaverCore;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeConstraint;
import org.jkiss.dbeaver.model.data.DBDDataFilter;
import org.jkiss.dbeaver.model.data.DBDDataReceiver;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.impl.AbstractExecutionSource;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.navigator.DBNModel;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSEntityAttribute;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.ArrayUtils;
import org.jkiss.utils.CommonUtils;
import java.math.BigDecimal;
import java.util.*;
public class SearchDataQuery implements ISearchQuery {
private static final Log log = Log.getLog(SearchDataQuery.class);
private final SearchDataParams params;
private SearchDataResult searchResult;
private SearchDataQuery(SearchDataParams params)
{
this.params = params;
}
@Override
public String getLabel()
{
return params.getSearchString();
}
@Override
public boolean canRerun() {
return true;
}
@Override
public boolean canRunInBackground() {
return true;
}
@Override
public ISearchResult getSearchResult() {
if (searchResult == null) {
searchResult = new SearchDataResult(this);
}
return searchResult;
}
@Override
public IStatus run(IProgressMonitor monitor) throws OperationCanceledException {
try {
String searchString = params.getSearchString();
//monitor.subTask("Collect tables");
Set<DBPDataSource> dataSources = new HashSet<>();
for (DBSDataContainer searcher : params.sources) {
dataSources.add(searcher.getDataSource());
}
// Search
DBNModel dbnModel = DBeaverCore.getInstance().getNavigatorModel();
monitor.beginTask(
"Search \"" + searchString + "\" in " + params.sources.size() + " table(s) / " + dataSources.size() + " database(s)",
params.sources.size());
try {
for (DBSDataContainer dataContainer : params.sources) {
if (monitor.isCanceled()) {
break;
}
String objectName = DBUtils.getObjectFullName(dataContainer, DBPEvaluationContext.DML);
DBNDatabaseNode node = dbnModel.findNode(dataContainer);
if (node == null) {
log.warn("Can't find tree node for object \"" + objectName + "\"");
continue;
}
monitor.subTask(objectName);
DBPDataSource dataSource = dataContainer.getDataSource();
if (dataSource == null) {
log.warn("Object \"" + objectName + "\" not connected");
continue;
}
SearchTableMonitor searchMonitor = new SearchTableMonitor();
try (DBCSession session = DBUtils.openUtilSession(searchMonitor, dataSource, "Search rows in " + objectName)) {
TestDataReceiver dataReceiver = new TestDataReceiver(searchMonitor);
findRows(session, dataContainer, dataReceiver);
if (dataReceiver.rowCount > 0) {
SearchDataObject object = new SearchDataObject(node, dataReceiver.rowCount, dataReceiver.filter);
searchResult.addObjects(Collections.singletonList(object));
}
}
monitor.worked(1);
}
} finally {
monitor.done();
}
return Status.OK_STATUS;
} catch (DBException e) {
return GeneralUtils.makeExceptionStatus(e);
}
}
private DBCStatistics findRows(
@NotNull DBCSession session,
@NotNull DBSDataContainer dataContainer,
@NotNull TestDataReceiver dataReceiver) throws DBCException
{
DBSEntity entity;
if (dataContainer instanceof DBSEntity) {
entity = (DBSEntity) dataContainer;
} else {
log.warn("Data container " + dataContainer + " isn't entity");
return null;
}
try {
List<DBDAttributeConstraint> constraints = new ArrayList<>();
for (DBSEntityAttribute attribute : CommonUtils.safeCollection(entity.getAttributes(session.getProgressMonitor()))) {
if (params.fastSearch) {
if (!DBUtils.isIndexedAttribute(session.getProgressMonitor(), attribute)) {
continue;
}
}
if (DBUtils.isPseudoAttribute(attribute) || DBUtils.isHiddenObject(attribute)) {
continue;
}
DBCLogicalOperator[] supportedOperators = DBUtils.getAttributeOperators(attribute);
DBCLogicalOperator operator;
Object value;
switch (attribute.getDataKind()) {
case BOOLEAN:
continue;
case NUMERIC:
if (!params.searchNumbers) {
continue;
}
if (!ArrayUtils.contains(supportedOperators, DBCLogicalOperator.EQUALS)) {
continue;
}
operator = DBCLogicalOperator.EQUALS;
try {
value = new Integer(params.searchString);
} catch (NumberFormatException e) {
try {
value = new Long(params.searchString);
} catch (NumberFormatException e1) {
try {
value = new Double(params.searchString);
} catch (NumberFormatException e2) {
try {
value = new BigDecimal(params.searchString);
} catch (Exception e3) {
// Not a number
continue;
}
}
}
}
break;
case CONTENT:
case BINARY:
if (!params.searchLOBs) {
continue;
}
case STRING:
if (attribute.getMaxLength() > 0 && attribute.getMaxLength() < params.searchString.length()) {
continue;
}
if (ArrayUtils.contains(supportedOperators, DBCLogicalOperator.LIKE)) {
operator = DBCLogicalOperator.LIKE;
value = "%" + params.searchString + "%";
} else if (ArrayUtils.contains(supportedOperators, DBCLogicalOperator.EQUALS)) {
operator = DBCLogicalOperator.EQUALS;
value = params.searchString;
} else {
continue;
}
break;
default: {
// Try to convert string to attribute type
// On success search by exact match
if (!ArrayUtils.contains(supportedOperators, DBCLogicalOperator.EQUALS)) {
continue;
}
String typeName = attribute.getTypeName();
if (typeName.equals(DBConstants.TYPE_NAME_UUID) || typeName.equals(DBConstants.TYPE_NAME_UUID2)) {
try {
UUID uuid = UUID.fromString(params.searchString);
operator = DBCLogicalOperator.EQUALS;
value = uuid.toString();
} catch (Exception e) {
// No a UUID
continue;
}
} else {
continue;
}
}
}
DBDAttributeConstraint constraint = new DBDAttributeConstraint(attribute, constraints.size());
constraint.setOperator(operator);
constraint.setValue(value);
constraint.setVisible(true);
constraints.add(constraint);
}
if (constraints.isEmpty()) {
return null;
}
dataReceiver.filter = new DBDDataFilter(constraints);
dataReceiver.filter.setAnyConstraint(true);
DBCExecutionSource searchSource = new AbstractExecutionSource(dataContainer, session.getExecutionContext(), this);
return dataContainer.readData(searchSource, session, dataReceiver, dataReceiver.filter, -1, -1, 0);
} catch (DBException e) {
throw new DBCException("Error finding rows", e);
}
}
public static SearchDataQuery createQuery(SearchDataParams params)
throws DBException
{
return new SearchDataQuery(params);
}
private class SearchTableMonitor extends VoidProgressMonitor {
private volatile boolean canceled;
private SearchTableMonitor() {
}
@Override
public boolean isCanceled() {
return canceled;
}
}
private class TestDataReceiver implements DBDDataReceiver {
private SearchTableMonitor searchMonitor;
private int rowCount = 0;
private DBDDataFilter filter;
public TestDataReceiver(SearchTableMonitor searchMonitor) {
this.searchMonitor = searchMonitor;
}
@Override
public void fetchStart(DBCSession session, DBCResultSet resultSet, long offset, long maxRows) throws DBCException {
}
@Override
public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException {
rowCount++;
if (rowCount >= params.maxResults) {
searchMonitor.canceled = true;
}
}
@Override
public void fetchEnd(DBCSession session, DBCResultSet resultSet) throws DBCException {
}
@Override
public void close() {
}
}
}