Make cross() function work for all columns (#2456)

* fix #1950

* migrate from join to lookup

* reformat
This commit is contained in:
Lu Liu 2020-03-23 21:48:32 +08:00 committed by GitHub
parent cff88e6267
commit 9ad3b1080f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 212 additions and 300 deletions

View File

@ -1,187 +0,0 @@
/*
Copyright 2010, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.refine;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.refine.expr.ExpressionUtils;
import com.google.refine.expr.HasFieldsListImpl;
import com.google.refine.expr.WrappedRow;
import com.google.refine.model.Column;
import com.google.refine.model.Project;
import com.google.refine.model.Row;
import com.google.refine.util.JoinException;
public class InterProjectModel {
static public class ProjectJoin {
final public long fromProjectID;
final public String fromProjectColumnName;
final public long toProjectID;
final public String toProjectColumnName;
final public Map<Object, List<Integer>> valueToRowIndices =
new HashMap<Object, List<Integer>>();
ProjectJoin(
long fromProjectID,
String fromProjectColumnName,
long toProjectID,
String toProjectColumnName
) {
this.fromProjectID = fromProjectID;
this.fromProjectColumnName = fromProjectColumnName;
this.toProjectID = toProjectID;
this.toProjectColumnName = toProjectColumnName;
}
public HasFieldsListImpl getRows(Object value) {
if (ExpressionUtils.isNonBlankData(value) && valueToRowIndices.containsKey(value)) {
Project toProject = ProjectManager.singleton.getProject(toProjectID);
if (toProject != null) {
HasFieldsListImpl rows = new HasFieldsListImpl();
for (Integer r : valueToRowIndices.get(value)) {
Row row = toProject.rows.get(r);
rows.add(new WrappedRow(toProject, r, row));
}
return rows;
}
}
return null;
}
}
protected Map<String, ProjectJoin> _joins = new HashMap<String, ProjectJoin>();
/**
* Compute the ProjectJoin based on combination key, return the cached one from the HashMap if already computed
*
* @param fromProject
* @param fromColumn
* @param toProject
* @param toColumn
* @return
*/
public ProjectJoin getJoin(Long fromProject, String fromColumn, Long toProject, String toColumn) throws JoinException {
String key = fromProject + ";" + fromColumn + ";" + toProject + ";" + toColumn;
if (!_joins.containsKey(key)) {
ProjectJoin join = new ProjectJoin(
fromProject,
fromColumn,
toProject,
toColumn
);
computeJoin(join);
synchronized (_joins) {
_joins.put(key, join);
}
}
return _joins.get(key);
}
public void flushJoinsInvolvingProject(long projectID) {
synchronized (_joins) {
for (Iterator<Entry<String, ProjectJoin>> it = _joins.entrySet().iterator(); it.hasNext();) {
Entry<String, ProjectJoin> entry = it.next();
ProjectJoin join = entry.getValue();
if (join.fromProjectID == projectID || join.toProjectID == projectID) {
it.remove();
}
}
}
}
public void flushJoinsInvolvingProjectColumn(long projectID, String columnName) {
synchronized (_joins) {
for (Iterator<Entry<String, ProjectJoin>> it = _joins.entrySet().iterator(); it.hasNext();) {
Entry<String, ProjectJoin> entry = it.next();
ProjectJoin join = entry.getValue();
if (join.fromProjectID == projectID && join.fromProjectColumnName.equals(columnName) ||
join.toProjectID == projectID && join.toProjectColumnName.equals(columnName)) {
it.remove();
}
}
}
}
protected void computeJoin(ProjectJoin join) throws JoinException {
if (join.fromProjectID < 0 || join.toProjectID < 0) {
return;
}
Project fromProject = ProjectManager.singleton.getProject(join.fromProjectID);
ProjectMetadata fromProjectMD = ProjectManager.singleton.getProjectMetadata(join.fromProjectID);
Project toProject = ProjectManager.singleton.getProject(join.toProjectID);
ProjectMetadata toProjectMD = ProjectManager.singleton.getProjectMetadata(join.toProjectID);
// split this test to check each one and throw an appropriate error
if (fromProject == null || toProject == null) {
return;
}
Column fromColumn = fromProject.columnModel.getColumnByName(join.fromProjectColumnName);
Column toColumn = toProject.columnModel.getColumnByName(join.toProjectColumnName);
if (fromColumn == null) {
throw new JoinException("Unable to find column " + join.fromProjectColumnName + " in project " + fromProjectMD.getName());
}
if (toColumn == null) {
throw new JoinException("Unable to find column " + join.toProjectColumnName + " in project " + toProjectMD.getName());
}
for (Row fromRow : fromProject.rows) {
Object value = fromRow.getCellValue(fromColumn.getCellIndex());
if (ExpressionUtils.isNonBlankData(value) && !join.valueToRowIndices.containsKey(value)) {
join.valueToRowIndices.put(value, new ArrayList<Integer>());
}
}
int count = toProject.rows.size();
for (int r = 0; r < count; r++) {
Row toRow = toProject.rows.get(r);
Object value = toRow.getCellValue(toColumn.getCellIndex());
if (ExpressionUtils.isNonBlankData(value) && join.valueToRowIndices.containsKey(value)) {
join.valueToRowIndices.get(value).add(r);
}
}
}
}

View File

@ -0,0 +1,124 @@
package com.google.refine;
import com.google.refine.expr.ExpressionUtils;
import com.google.refine.expr.HasFieldsListImpl;
import com.google.refine.expr.WrappedRow;
import com.google.refine.model.Column;
import com.google.refine.model.Project;
import com.google.refine.model.Row;
import com.google.refine.util.LookupException;
import java.util.*;
/**
* Manage the cache of project's lookups.
*
* @author Lu Liu
*/
public class LookupCacheManager {
protected final Map<String, ProjectLookup> _lookups = new HashMap<>();
/**
* Computes the ProjectLookup based on combination key,
* returns the cached one from the HashMap if already computed.
*
* @param targetProject the project to look up
* @param targetColumn the column of the target project to look up
* @return a {@link ProjectLookup} instance of the lookup result
*/
public ProjectLookup getLookup(long targetProject, String targetColumn) throws LookupException {
String key = targetProject + ";" + targetColumn;
if (!_lookups.containsKey(key)) {
ProjectLookup lookup = new ProjectLookup(targetProject, targetColumn);
computeLookup(lookup);
synchronized (_lookups) {
_lookups.put(key, lookup);
}
}
return _lookups.get(key);
}
public void flushLookupsInvolvingProject(long projectID) {
synchronized (_lookups) {
for (Iterator<Map.Entry<String, ProjectLookup>> it = _lookups.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, ProjectLookup> entry = it.next();
ProjectLookup lookup = entry.getValue();
if (lookup.targetProjectID == projectID) {
it.remove();
}
}
}
}
public void flushLookupsInvolvingProjectColumn(long projectID, String columnName) {
synchronized (_lookups) {
for (Iterator<Map.Entry<String, ProjectLookup>> it = _lookups.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, ProjectLookup> entry = it.next();
ProjectLookup lookup = entry.getValue();
if (lookup.targetProjectID == projectID && lookup.targetColumnName.equals(columnName)) {
it.remove();
}
}
}
}
protected void computeLookup(ProjectLookup lookup) throws LookupException {
if (lookup.targetProjectID < 0) {
return;
}
Project targetProject = ProjectManager.singleton.getProject(lookup.targetProjectID);
ProjectMetadata targetProjectMetadata = ProjectManager.singleton.getProjectMetadata(lookup.targetProjectID);
if (targetProject == null) {
return;
}
Column targetColumn = targetProject.columnModel.getColumnByName(lookup.targetColumnName);
if (targetColumn == null) {
throw new LookupException("Unable to find column " + lookup.targetColumnName + " in project " + targetProjectMetadata.getName());
}
// We can't use for-each here, because we'll need the row index when creating WrappedRow
int count = targetProject.rows.size();
for (int r = 0; r < count; r++) {
Row targetRow = targetProject.rows.get(r);
Object value = targetRow.getCellValue(targetColumn.getCellIndex());
if (ExpressionUtils.isNonBlankData(value)) {
lookup.valueToRowIndices.putIfAbsent(value, new ArrayList<>());
lookup.valueToRowIndices.get(value).add(r);
}
}
}
static public class ProjectLookup {
final public long targetProjectID;
final public String targetColumnName;
final public Map<Object, List<Integer>> valueToRowIndices = new HashMap<>();
ProjectLookup(long targetProjectID, String targetColumnName) {
this.targetProjectID = targetProjectID;
this.targetColumnName = targetColumnName;
}
public HasFieldsListImpl getRows(Object value) {
if (ExpressionUtils.isNonBlankData(value) && valueToRowIndices.containsKey(value)) {
Project targetProject = ProjectManager.singleton.getProject(targetProjectID);
if (targetProject != null) {
HasFieldsListImpl rows = new HasFieldsListImpl();
for (Integer r : valueToRowIndices.get(value)) {
Row row = targetProject.rows.get(r);
rows.add(new WrappedRow(targetProject, r, row));
}
return rows;
}
}
return null;
}
}
}

View File

@ -84,9 +84,9 @@ public abstract class ProjectManager {
final static Logger logger = LoggerFactory.getLogger("ProjectManager");
/**
* What caches the joins between projects.
* What caches the lookups of projects.
*/
transient protected InterProjectModel _interProjectModel = new InterProjectModel();
transient protected LookupCacheManager _lookupCacheManager = new LookupCacheManager();
/**
* Flag for heavy operations like creating or importing projects. Workspace saves are skipped while it's set.
@ -345,14 +345,13 @@ public abstract class ProjectManager {
}
/**
* Gets the InterProjectModel from memory
* Gets the LookupCacheManager from memory
*/
@JsonIgnore
public InterProjectModel getInterProjectModel() {
return _interProjectModel;
public LookupCacheManager getLookupCacheManager() {
return _lookupCacheManager;
}
/**
* Gets the project metadata from memory
* Requires that the metadata has already been loaded from the data store

View File

@ -35,73 +35,62 @@ package com.google.refine.expr.functions;
import java.util.Properties;
import com.google.refine.InterProjectModel.ProjectJoin;
import com.google.refine.LookupCacheManager.ProjectLookup;
import com.google.refine.ProjectManager;
import com.google.refine.expr.EvalError;
import com.google.refine.expr.WrappedCell;
import com.google.refine.grel.ControlFunctionRegistry;
import com.google.refine.grel.Function;
import com.google.refine.model.Project;
import com.google.refine.util.GetProjectIDException;
import com.google.refine.util.JoinException;
import com.google.refine.util.LookupException;
public class Cross implements Function {
@Override
public Object call(Properties bindings, Object[] args) {
if (args.length == 3) {
// 1st argument can take either value or cell(for backward compatibility)
Object v = args[0];
Object toProjectName = args[1];
Object toColumnName = args[2];
Long toProjectID;
ProjectJoin join;
if (v != null &&
( v instanceof String || v instanceof WrappedCell ) &&
toProjectName != null && toProjectName instanceof String &&
toColumnName != null && toColumnName instanceof String) {
Object targetProjectName = args[1];
Object targetColumnName = args[2];
long targetProjectID;
ProjectLookup lookup;
if (v != null &&
(v instanceof String || v instanceof WrappedCell) &&
targetProjectName != null && targetProjectName instanceof String &&
targetColumnName != null && targetColumnName instanceof String) {
try {
toProjectID = ProjectManager.singleton.getProjectID((String) toProjectName);
} catch (GetProjectIDException e){
targetProjectID = ProjectManager.singleton.getProjectID((String) targetProjectName);
} catch (GetProjectIDException e) {
return new EvalError(e.getMessage());
}
// add a try/catch here - error should bubble up from getInterProjectModel.computeJoin once that's modified
try {
join = ProjectManager.singleton.getInterProjectModel().getJoin(
// getJoin(Long fromProject, String fromColumn, Long toProject, String toColumn) {
// source project name
(Long) ((Project) bindings.get("project")).id,
// source column name
(String) bindings.get("columnName"),
// target project name
toProjectID,
// target column name
(String) toColumnName
);
} catch (JoinException e) {
lookup = ProjectManager.singleton.getLookupCacheManager().getLookup(targetProjectID, (String) targetColumnName);
} catch (LookupException e) {
return new EvalError(e.getMessage());
}
if(v instanceof String) {
return join.getRows(v);
if (v instanceof String) {
return lookup.getRows(v);
} else {
return join.getRows(((WrappedCell) v).cell.value);
return lookup.getRows(((WrappedCell) v).cell.value);
}
}
}
return new EvalError(ControlFunctionRegistry.getFunctionName(this) + " expects a string or cell, a project name to join with, and a column name in that project");
}
@Override
public String getDescription() {
return "join with another project by column";
}
@Override
public String getParams() {
return "cell c or string value, string projectName, string columnName";
}
@Override
public String getReturns() {
return "array";

View File

@ -45,7 +45,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.refine.InterProjectModel;
import com.google.refine.model.recon.ReconConfig;
import com.google.refine.util.ParsingUtilities;
@ -122,8 +121,8 @@ public class Column {
* <p>
* If you are modifying something that requires this to be called, you
* probably also need to call
* {@link InterProjectModel#flushJoinsInvolvingProjectColumn(long, String)}.
* e.g. ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, column.getName())
* {@link com.google.refine.LookupCacheManager#flushLookupsInvolvingProjectColumn(long, String)}
* e.g. ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, column.getName())
*/
public void clearPrecomputes() {
if (_precomputes != null) {

View File

@ -105,7 +105,7 @@ public class Project {
logger.warn("Error signaling overlay model before disposing", e);
}
}
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProject(this.id);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProject(this.id);
// The rest of the project should get garbage collected when we return.
}

View File

@ -64,7 +64,7 @@ public class CellChange implements Change {
Column column = project.columnModel.getColumnByCellIndex(cellIndex);
column.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, column.getName());
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, column.getName());
}
@Override
@ -73,7 +73,7 @@ public class CellChange implements Change {
Column column = project.columnModel.getColumnByCellIndex(cellIndex);
column.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, column.getName());
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, column.getName());
}
@Override

View File

@ -55,7 +55,7 @@ public class ColumnRenameChange extends ColumnChange {
@Override
public void apply(Project project) {
synchronized (project) {
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _oldColumnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _oldColumnName);
project.columnModel.getColumnByName(_oldColumnName).setName(_newColumnName);
project.columnModel.update();
}
@ -64,7 +64,7 @@ public class ColumnRenameChange extends ColumnChange {
@Override
public void revert(Project project) {
synchronized (project) {
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _newColumnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _newColumnName);
project.columnModel.getColumnByName(_newColumnName).setName(_oldColumnName);
project.columnModel.update();
}

View File

@ -125,7 +125,7 @@ public class ColumnSplitChange implements Change {
project.columnModel.allocateNewCellIndex();
}
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _columnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _columnName);
_column = project.columnModel.getColumnByName(_columnName);
_columnIndex = project.columnModel.getColumnIndexByName(_columnName);
@ -240,7 +240,7 @@ public class ColumnSplitChange implements Change {
for (int i = 0; i < _columnNames.size(); i++) {
project.columnModel.columns.remove(_columnIndex + 1);
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _columnNames.get(i));
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _columnNames.get(i));
}
project.columnModel.columnGroups.clear();

View File

@ -94,7 +94,7 @@ public class MassCellChange implements Change {
if (_commonColumnName != null) {
Column column = project.columnModel.getColumnByName(_commonColumnName);
column.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _commonColumnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _commonColumnName);
}
if (_updateRowContextDependencies) {
@ -115,7 +115,7 @@ public class MassCellChange implements Change {
if (_commonColumnName != null) {
Column column = project.columnModel.getColumnByName(_commonColumnName);
column.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _commonColumnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _commonColumnName);
}
if (_updateRowContextDependencies) {

View File

@ -82,7 +82,7 @@ public class MassReconChange implements Change {
// skip the flushing if already done
String columnName = project.columnModel.getColumnByCellIndex(c).getName();
if (!flushedColumn.contains(columnName)) {
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id,
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id,
columnName);
flushedColumn.add(columnName);
}

View File

@ -62,7 +62,7 @@ public class MassRowChange implements Change {
project.rows.addAll(_newRows);
project.columnModel.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProject(project.id);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProject(project.id);
project.update();
}

View File

@ -81,7 +81,7 @@ public class MassRowColumnChange implements Change {
project.rows.clear();
project.rows.addAll(_newRows);
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProject(project.id);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProject(project.id);
project.update();
}
@ -99,7 +99,7 @@ public class MassRowColumnChange implements Change {
project.rows.clear();
project.rows.addAll(_oldRows);
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProject(project.id);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProject(project.id);
project.update();
}

View File

@ -109,7 +109,7 @@ public class ReconChange extends MassCellChange {
column.setReconStats(_newReconStats);
column.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _commonColumnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _commonColumnName);
}
}
@ -123,7 +123,7 @@ public class ReconChange extends MassCellChange {
column.setReconStats(_oldReconStats);
column.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProjectColumn(project.id, _commonColumnName);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProjectColumn(project.id, _commonColumnName);
}
}

View File

@ -72,7 +72,7 @@ public class RowRemovalChange implements Change {
}
project.columnModel.clearPrecomputes();
ProjectManager.singleton.getInterProjectModel().flushJoinsInvolvingProject(project.id);
ProjectManager.singleton.getLookupCacheManager().flushLookupsInvolvingProject(project.id);
project.update();
}

View File

@ -1,53 +0,0 @@
/*
Copyright 2019, Owen Stephens
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* The name of the contributor may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.refine.util;
/**
* Thrown when there is an error on a project Join.
*/
public class JoinException extends Exception {
private static final long serialVersionUID = 1553348086L;
/**
* Default JoinException format exception.
*/
public JoinException() { super(); }
/**
* JoinException exception.
*
* @param message error message
*/
public JoinException(String message) { super(message); }
}

View File

@ -0,0 +1,19 @@
package com.google.refine.util;
/**
* Thrown when there is an error on a project lookup.
*/
public class LookupException extends Exception {
/**
* Default LookupException format exception.
*/
public LookupException() { super(); }
/**
* LookupException exception.
*
* @param message error message
*/
public LookupException(String message) { super(message); }
}

View File

@ -133,7 +133,29 @@ public class CrossTests extends RefineTest {
Assert.assertEquals(((EvalError) invoke("cross", "mary", projectName, nonExistentColumn)).message,
"Unable to find column " + nonExistentColumn + " in project " + projectName);
}
@Test
public void crossFunctionSameColumnTest() throws Exception {
Project project = (Project) bindings.get("project");
Cell c = project.rows.get(0).cells.get(1);
WrappedCell lookup = new WrappedCell(project, "recipient", c);
Row row = ((Row)((WrappedRow) ((HasFieldsListImpl) invoke("cross", lookup, "My Address Book", "friend")).get(0)).row);
String address = row.getCell(1).value.toString();
Assert.assertEquals(address, "50 Broadway Ave.");
}
@Test
// cross result shouldn't depend on the based column in "bindings" when the first argument is a WrappedCell instance
public void crossFunctionDifferentColumnTest() throws Exception {
Project project = (Project) bindings.get("project");
bindings.put("columnName", "gift"); // change the based column
Cell c = project.rows.get(0).cells.get(1);
WrappedCell lookup = new WrappedCell(project, "recipient", c);
Row row = ((Row)((WrappedRow) ((HasFieldsListImpl) invoke("cross", lookup, "My Address Book", "friend")).get(0)).row);
String address = row.getCell(1).value.toString();
Assert.assertEquals(address, "50 Broadway Ave.");
}
@Test
public void crossFunctionOneToOneTest() throws Exception {
Row row = ((Row)((WrappedRow) ((HasFieldsListImpl) invoke("cross", "mary", "My Address Book", "friend")).get(0)).row);