Skip to content

Commit 62a73b5

Browse files
Work on Foreign Keys
1 parent d82788f commit 62a73b5

25 files changed

+410
-202
lines changed

docs/spec-compliance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Documents compliance with the [specifications](https://frictionlessdata.io/specs
4545
- Both type and format are optional: in a field descriptor, the absence of a type property indicates that the field
4646
is of the type "string", and the absence of a format property indicates that the field's type format is "default".
4747

48-
- implemented in Fild#fromJson(String) as `String type = fieldDef.has(JSON_KEY_TYPE)
48+
- implemented in `Field#fromJson(String) as `String type = fieldDef.has(JSON_KEY_TYPE)
4949
? fieldDef.getString(JSON_KEY_TYPE) : "string";` and `field.format = (!StringUtils.isEmpty(format))
5050
? format.trim() : FIELD_FORMAT_DEFAULT`;
5151

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>io.frictionlessdata</groupId>
55
<artifactId>tableschema-java</artifactId>
6-
<version>0.7.5-SNAPSHOT</version>
6+
<version>0.7.6-SNAPSHOT</version>
77
<packaging>jar</packaging>
88
<issueManagement>
99
<url>https://github.com/frictionlessdata/tableschema-java/issues</url>

src/main/java/io/frictionlessdata/tableschema/Table.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public Table(Collection<String[]> data, String[] headers, Schema schema) {
8383
validate();
8484
}
8585

86+
/**
87+
* Constructor for a Table from a {@link BeanTableDataSource} instance holding a collection
88+
* of instances of a certain Bean class. The Schema is inferred from the Bean class.
89+
* @param dataSource the input data
90+
*/
8691
public static Table fromSource(BeanTableDataSource dataSource) {
8792
Table table = new Table();
8893
table.dataSource = dataSource;
@@ -146,25 +151,25 @@ public static Table fromSource(File dataSource, File basePath) {
146151

147152
/**
148153
* Create Table using either a CSV or JSON array-containing string and without either a Schema or a CSVFormat.
149-
* @param dataSource the CSV or JSON content for the Table
154+
* @param data the CSV or JSON content for the Table
150155
*/
151-
public static Table fromSource(String dataSource) {
156+
public static Table fromSource(String data) {
152157
Table table = new Table();
153-
table.dataSource = TableDataSource.fromSource(dataSource);
158+
table.dataSource = TableDataSource.fromSource(data);
154159
return table;
155160
}
156161

157162
/**
158163
* Create Table using either a CSV or JSON array-containing string and with a Schema and a CSVFormat.
159-
* @param dataSource the CSV or JSON content for the Table
164+
* @param data the CSV or JSON content for the Table
160165
* @param schema table schema. Can be `null`
161166
* @param format The expected CSVFormat if dataSource is a CSV-containing InputStream; ignored for JSON data.
162167
* Can be `null`
163168
*/
164-
public static Table fromSource(String dataSource, Schema schema, CSVFormat format) {
169+
public static Table fromSource(String data, Schema schema, CSVFormat format) {
165170
Table table = new Table();
166171
table.schema = schema;
167-
table.dataSource = TableDataSource.fromSource(dataSource);
172+
table.dataSource = TableDataSource.fromSource(data);
168173
if (null != format) {
169174
table.setCsvFormat(format);
170175
}
@@ -450,7 +455,7 @@ private void writeCsv(Writer out, CSVFormat format, String[] sortedHeaders) {
450455
? format
451456
: TableDataSource.getDefaultCsvFormat();
452457

453-
locFormat = locFormat.builder().setHeader(sortedHeaders).build();
458+
locFormat = locFormat.builder().setHeader(sortedHeaders).get();
454459
CSVPrinter csvPrinter = new CSVPrinter(out, locFormat);
455460

456461
String[] headers = getHeaders();

src/main/java/io/frictionlessdata/tableschema/exception/ConstraintsException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
* @author pechorin
1111
*/
12-
public class ConstraintsException extends TableSchemaException {
12+
public class ConstraintsException extends ValidationException {
1313

1414
/**
1515
* Constructs an instance of <code>ConstraintsException</code> with the

src/main/java/io/frictionlessdata/tableschema/exception/ForeignKeyException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
*
55
*/
6-
public class ForeignKeyException extends TableSchemaException {
6+
public class ForeignKeyException extends ValidationException {
77

88
/**
99
* Constructs an instance of <code>ForeignKeyException</code> with the

src/main/java/io/frictionlessdata/tableschema/exception/PrimaryKeyException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.frictionlessdata.tableschema.exception;
22

3-
public class PrimaryKeyException extends TableSchemaException {
3+
public class PrimaryKeyException extends ValidationException {
44

55
public PrimaryKeyException(String msg) {
66
super(msg);

src/main/java/io/frictionlessdata/tableschema/exception/TableValidationException.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*
55
*
66
*/
7-
public class TableValidationException extends TableSchemaException {
7+
public class TableValidationException extends ValidationException {
88

99

1010
/**
@@ -17,12 +17,4 @@ public TableValidationException(String msg) {
1717
super(msg);
1818
}
1919

20-
/**
21-
* Constructs an instance of <code>TableSchemaException</code> by wrapping a Throwable
22-
*
23-
* @param t the wrapped exception.
24-
*/
25-
public TableValidationException(Throwable t) {
26-
super(t);
27-
}
2820
}

src/main/java/io/frictionlessdata/tableschema/exception/ValidationException.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
import java.util.stream.Collectors;
99

1010
public class ValidationException extends TableSchemaException {
11-
1211
List<ValidationMessage> validationMessages = new ArrayList<>();
13-
List<String> otherMessages = new ArrayList<>();
12+
List<Exception> wrappedExceptions = new ArrayList<>();
1413

1514
public ValidationException(String msg) {
1615
super(msg);
1716
}
1817

1918
public ValidationException(Exception ex) {
20-
String message = ex.getClass()+": "+ex.getMessage();
19+
wrappedExceptions.add(ex);
2120
}
2221

2322
public ValidationException(FormalSchemaValidator schema, Collection<ValidationMessage> messages) {
@@ -32,19 +31,30 @@ public ValidationException(String message, String schemaName, Collection<Validat
3231

3332
public ValidationException(String schemaName, Collection<ValidationException> exceptions) {
3433
this(String.format("%s: %s", schemaName, "validation failed: "));
35-
otherMessages.addAll(exceptions
36-
.stream().map((Throwable::getMessage)).collect(Collectors.toList()));
34+
wrappedExceptions.addAll(exceptions);
3735
final Set<ValidationMessage> messages = new LinkedHashSet<>();
3836
exceptions.forEach((m) -> {
39-
messages.addAll(m.validationMessages);
37+
if (m instanceof ValidationException) {
38+
messages.addAll(((ValidationException)m).validationMessages);
39+
}
4040
});
4141
this.validationMessages.addAll(messages);
4242
}
4343

44+
public List<Exception> getWrappedExceptions() {
45+
return wrappedExceptions;
46+
}
47+
48+
public List<ValidationMessage> getValidationMessages() {
49+
return validationMessages;
50+
}
51+
4452
public List<Object> getMessages() {
4553
List<Object> retVal = new ArrayList<>();
4654
retVal.addAll(validationMessages);
47-
retVal.addAll(otherMessages);
55+
for (Exception ex : wrappedExceptions) {
56+
retVal.add(ex.getMessage());
57+
}
4858
return retVal;
4959
}
5060
}

src/main/java/io/frictionlessdata/tableschema/fk/ForeignKey.java

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package io.frictionlessdata.tableschema.fk;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4-
import com.fasterxml.jackson.databind.JsonNode;
5-
import com.fasterxml.jackson.databind.node.ArrayNode;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
65
import io.frictionlessdata.tableschema.Table;
76
import io.frictionlessdata.tableschema.exception.ForeignKeyException;
7+
import io.frictionlessdata.tableschema.exception.ValidationException;
88
import io.frictionlessdata.tableschema.util.JsonUtil;
99

1010
import java.util.*;
@@ -15,55 +15,43 @@
1515
public class ForeignKey {
1616
private static final String JSON_KEY_FIELDS = "fields";
1717
private static final String JSON_KEY_REFERENCE = "reference";
18-
18+
19+
@JsonProperty(JSON_KEY_FIELDS)
1920
private Object fields = null;
21+
22+
@JsonProperty(JSON_KEY_REFERENCE)
2023
private Reference reference = null;
2124

22-
private boolean strictValidation = false;
23-
private final ArrayList<Exception> errors = new ArrayList<>();
24-
25-
public ForeignKey(){
26-
}
25+
private boolean strictValidation = true;
26+
private final ArrayList<ValidationException> errors = new ArrayList<>();
2727

28-
public ForeignKey(boolean strict){
29-
this();
30-
this.strictValidation = strict;
31-
}
28+
public ForeignKey(){}
3229

3330
public ForeignKey(Object fields, Reference reference, boolean strict) throws ForeignKeyException{
3431
this.fields = fields;
3532
this.reference = reference;
3633
this.strictValidation = strict;
3734
this.validate();
3835
}
39-
40-
public ForeignKey(String json, boolean strict) throws ForeignKeyException{
41-
JsonNode fkJsonObject = JsonUtil.getInstance().readValue(json);
42-
this.strictValidation = strict;
43-
44-
if(fkJsonObject.has(JSON_KEY_FIELDS)){
45-
if(fkJsonObject.get(JSON_KEY_FIELDS).isArray()) {
46-
fields = fkJsonObject.get(JSON_KEY_FIELDS);
47-
} else {
48-
fields = fkJsonObject.get(JSON_KEY_FIELDS).asText();
49-
}
50-
}
51-
52-
if(fkJsonObject.has(JSON_KEY_REFERENCE)){
53-
JsonNode refJsonObject = fkJsonObject.get(JSON_KEY_REFERENCE);
54-
reference = new Reference(refJsonObject.toString(), strict);
55-
}
56-
57-
validate();
58-
}
59-
36+
6037
public void setFields(Object fields){
6138
this.fields = fields;
6239
}
63-
40+
6441
public <Any> Any getFields(){
6542
return (Any)this.fields;
6643
}
44+
45+
@JsonIgnore
46+
public List<String> getFieldNames(){
47+
if (this.fields instanceof String){
48+
return Collections.singletonList((String)this.fields);
49+
} else if (this.fields instanceof Collection){
50+
return new ArrayList<>( (Collection<String>)this.fields);
51+
} else {
52+
throw new IllegalArgumentException("Invalid fields type in reference: "+this.fields.getClass().getName());
53+
}
54+
}
6755

6856
public void setReference(Reference reference){
6957
this.reference = reference;
@@ -75,38 +63,51 @@ public Reference getReference(){
7563

7664
public final void validate() throws ForeignKeyException{
7765
ForeignKeyException fke = null;
78-
66+
7967
if(this.fields == null || this.reference == null){
8068
fke = new ForeignKeyException("A foreign key must have the fields and reference properties.");
8169

82-
}else if(!(this.fields instanceof String) && !(this.fields instanceof ArrayNode)){
70+
}else if(!(this.fields instanceof String) && !(this.fields instanceof Collection)){
8371
fke = new ForeignKeyException("The foreign key's fields property must be a string or an array.");
8472

85-
}else if(this.fields instanceof ArrayNode && !(this.reference.getFields() instanceof ArrayNode)){
73+
}else if(this.fields instanceof Collection && !(this.reference.getFields() instanceof Collection)){
8674
fke = new ForeignKeyException("The reference's fields property must be an array if the outer fields is an array.");
8775

8876
}else if(this.fields instanceof String && !(this.reference.getFields() instanceof String)){
8977
fke = new ForeignKeyException("The reference's fields property must be a string if the outer fields is a string.");
9078

91-
}else if(this.fields instanceof ArrayNode && this.reference.getFields() instanceof ArrayNode){
92-
ArrayNode fkFields = (ArrayNode)fields;
93-
ArrayNode refFields = reference.getFields();
79+
}else if(this.fields instanceof Collection && this.reference.getFields() instanceof Collection){
80+
Collection<?> fkFields = (Collection<?>)fields;
81+
Collection<?> refFields = reference.getFields();
9482

9583
if(fkFields.size() != refFields.size()){
9684
fke = new ForeignKeyException("The reference's fields property must be an array of the same length as that of the outer fields' array.");
9785
}
9886
}
9987

88+
if (null != reference) {
89+
reference.validate();
90+
}
91+
10092
if(fke != null){
10193
if(this.strictValidation){
10294
throw fke;
10395
}else{
10496
errors.add(fke);
97+
if (null != reference) {
98+
errors.addAll(reference.getErrors());
99+
}
105100
}
106101
}
107102

108103
}
109104

105+
/**
106+
* validate the foreign key against the table. We only can validate self-referencing FKs, as we
107+
* do not have access to the tables in different resources of a datapackages in the tableschema library.
108+
* @param table the table to validate against
109+
* @throws ForeignKeyException if the foreign key is violated
110+
*/
110111
public final void validate(Table table) throws ForeignKeyException{
111112
validate();
112113

@@ -117,10 +118,11 @@ public final void validate(Table table) throws ForeignKeyException{
117118
if (fields instanceof String) {
118119
fieldNames.add((String) fields);
119120
foreignFieldNames.add(reference.getFields());
120-
} else if (fields instanceof ArrayNode) {
121-
for (int i = 0; i < ((ArrayNode) fields).size(); i++) {
122-
fieldNames.add(((ArrayNode) fields).get(i).asText());
123-
foreignFieldNames.add(((ArrayNode) reference.getFields()).get(i).asText());
121+
} else if (fields instanceof Collection) {
122+
List<String> lFields = getFieldNames();
123+
for (int i = 0; i < lFields.size(); i++) {
124+
fieldNames.add(lFields.get(i));
125+
foreignFieldNames.add(reference.getFieldNames().get(i));
124126
}
125127
}
126128
Iterator<Object> iterator = table.iterator(true, false, false, false);
@@ -135,16 +137,19 @@ public final void validate(Table table) throws ForeignKeyException{
135137
}
136138
}
137139
}
138-
}
140+
} else {
141+
throw new UnsupportedOperationException("Foreign key references across package resources are not supported");
142+
}
139143

140144
}
141145

142146
@JsonIgnore
143147
public String getJson(){
144148
return JsonUtil.getInstance().serialize(this);
145149
}
146-
147-
public List<Exception> getErrors(){
150+
151+
@JsonIgnore
152+
public ArrayList<ValidationException> getErrors(){
148153
return errors;
149154
}
150155

0 commit comments

Comments
 (0)