This is an automated email from the ASF dual-hosted git repository.
bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git
The following commit(s) were added to refs/heads/main by this push:
new 39a8abf6b Add a csv data store (#901)
39a8abf6b is described below
commit 39a8abf6b9ca2371a9dc5d48bef2bfae28370b9e
Author: Bertil Chapuis <[email protected]>
AuthorDate: Thu Oct 31 15:43:29 2024 +0100
Add a csv data store (#901)
* Add a csv datastore
* Add tests that parses csv files and a sample geonames file
* Format the source code
* Fix issues identified by codeql and sonar
---
.../apache/baremaps/storage/csv/CsvDataStore.java | 98 ++++++++++
.../apache/baremaps/storage/csv/CsvDataTable.java | 201 +++++++++++++++++++++
.../storage/csv/CsvDataTableGeonamesTest.java | 126 +++++++++++++
.../baremaps/storage/csv/CsvDataTableTest.java | 195 ++++++++++++++++++++
.../org/apache/baremaps/testing/TestFiles.java | 3 +
5 files changed, 623 insertions(+)
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/csv/CsvDataStore.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/csv/CsvDataStore.java
new file mode 100644
index 000000000..93b0624a9
--- /dev/null
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/csv/CsvDataStore.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.apache.baremaps.storage.csv;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import org.apache.baremaps.data.storage.DataSchema;
+import org.apache.baremaps.data.storage.DataStore;
+import org.apache.baremaps.data.storage.DataStoreException;
+import org.apache.baremaps.data.storage.DataTable;
+
+/**
+ * A DataStore implementation that manages a single CSV file.
+ */
+public class CsvDataStore implements DataStore {
+
+ private final String tableName;
+ private final DataSchema schema;
+ private final CsvDataTable dataTable;
+
+ /**
+ * Constructs a CsvDataStore with the specified table name, schema, and CSV
file.
+ *
+ * @param tableName the name of the table
+ * @param schema the data schema defining the structure
+ * @param csvFile the CSV file to read data from
+ * @param hasHeader whether the CSV file includes a header row
+ * @param separator the character used to separate columns in the CSV file
+ * @throws IOException if an I/O error occurs
+ */
+ public CsvDataStore(String tableName, DataSchema schema, File csvFile,
boolean hasHeader,
+ char separator) throws IOException {
+ this.tableName = tableName;
+ this.schema = schema;
+ this.dataTable = new CsvDataTable(schema, csvFile, hasHeader, separator);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<String> list() throws DataStoreException {
+ return Collections.singletonList(tableName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public DataTable get(String name) throws DataStoreException {
+ if (this.tableName.equals(name)) {
+ return dataTable;
+ } else {
+ throw new DataStoreException("Table '" + name + "' not found.");
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void add(DataTable table) throws DataStoreException {
+ throw new UnsupportedOperationException("Adding tables is not supported in
CsvDataStore.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void add(String name, DataTable table) throws DataStoreException {
+ throw new UnsupportedOperationException("Adding tables is not supported in
CsvDataStore.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void remove(String name) throws DataStoreException {
+ throw new UnsupportedOperationException("Removing tables is not supported
in CsvDataStore.");
+ }
+}
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/storage/csv/CsvDataTable.java
b/baremaps-core/src/main/java/org/apache/baremaps/storage/csv/CsvDataTable.java
new file mode 100644
index 000000000..033578a69
--- /dev/null
+++
b/baremaps-core/src/main/java/org/apache/baremaps/storage/csv/CsvDataTable.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.apache.baremaps.storage.csv;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.apache.baremaps.data.storage.*;
+import org.locationtech.jts.io.WKTReader;
+
+/**
+ * A DataTable implementation that reads data from a CSV file using Jackson.
+ */
+public class CsvDataTable implements DataTable {
+
+ private final DataSchema schema;
+ private final File csvFile;
+ private final CsvSchema csvSchema;
+ private final long size;
+
+ /**
+ * Constructs a CsvDataTable with the specified schema, CSV file, header
presence, and separator.
+ *
+ * @param schema the data schema defining the structure
+ * @param csvFile the CSV file to read data from
+ * @param hasHeader whether the CSV file includes a header row
+ * @param separator the character used to separate columns in the CSV file
+ * @throws IOException if an I/O error occurs
+ */
+ public CsvDataTable(DataSchema schema, File csvFile, boolean hasHeader, char
separator)
+ throws IOException {
+ this.schema = schema;
+ this.csvFile = csvFile;
+ this.csvSchema = buildCsvSchema(schema, hasHeader, separator);
+ this.size = calculateSize();
+ }
+
+ /**
+ * Builds the CsvSchema for Jackson based on the provided DataSchema, header
presence, and
+ * separator.
+ *
+ * @param dataSchema the data schema
+ * @param hasHeader whether the CSV file includes a header row
+ * @param separator the character used to separate columns
+ * @return the CsvSchema for Jackson
+ */
+ private CsvSchema buildCsvSchema(DataSchema dataSchema, boolean hasHeader,
char separator) {
+ CsvSchema.Builder builder = CsvSchema.builder();
+ for (DataColumn column : dataSchema.columns()) {
+ builder.addColumn(column.name());
+ }
+ return
builder.setUseHeader(hasHeader).setColumnSeparator(separator).build();
+ }
+
+ /**
+ * Calculates the number of rows in the CSV file.
+ *
+ * @return the number of rows
+ * @throws IOException if an I/O error occurs
+ */
+ private long calculateSize() throws IOException {
+ try (var parser = new CsvMapper().readerFor(Map.class)
+ .with(csvSchema)
+ .createParser(csvFile)) {
+ long rowCount = 0;
+ while (parser.nextToken() != null) {
+ if (parser.currentToken() == JsonToken.START_OBJECT) {
+ rowCount++;
+ }
+ }
+ return rowCount;
+ }
+ }
+
+ @Override
+ public DataSchema schema() {
+ return schema;
+ }
+
+ @Override
+ public boolean add(DataRow row) {
+ throw new UnsupportedOperationException("Adding rows is not supported.");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("Clearing rows is not supported.");
+ }
+
+ @Override
+ public long size() {
+ return size;
+ }
+
+ @Override
+ public Iterator<DataRow> iterator() {
+ try {
+ CsvMapper csvMapper = new CsvMapper();
+ JsonParser parser = csvMapper.readerFor(Map.class)
+ .with(csvSchema)
+ .createParser(csvFile);
+
+ Iterator<Map<String, String>> csvIterator =
csvMapper.readerFor(Map.class)
+ .with(csvSchema)
+ .readValues(parser);
+
+ return new Iterator<>() {
+ @Override
+ public boolean hasNext() {
+ return csvIterator.hasNext();
+ }
+
+ @Override
+ public DataRow next() {
+ Map<String, String> csvRow = csvIterator.next();
+ DataRow dataRow = schema.createRow();
+
+ for (int i = 0; i < schema.columns().size(); i++) {
+ DataColumn column = schema.columns().get(i);
+ String columnName = column.name();
+ String value = csvRow.get(columnName);
+
+ if (value != null) {
+ Object parsedValue = parseValue(column, value);
+ dataRow.set(i, parsedValue);
+ } else {
+ dataRow.set(i, null);
+ }
+ }
+ return dataRow;
+ }
+ };
+
+ } catch (IOException e) {
+ throw new DataStoreException("Error reading CSV file", e);
+ }
+ }
+
+ /**
+ * Parses the string value from the CSV according to the column type.
+ *
+ * @param column the data column
+ * @param value the string value from the CSV
+ * @return the parsed value
+ */
+ private Object parseValue(DataColumn column, String value) {
+ DataColumn.Type type = column.type();
+ try {
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+ return switch (type) {
+ case STRING -> value;
+ case INTEGER -> Integer.parseInt(value);
+ case LONG -> Long.parseLong(value);
+ case FLOAT -> Float.parseFloat(value);
+ case DOUBLE -> Double.parseDouble(value);
+ case BOOLEAN -> Boolean.parseBoolean(value);
+ case GEOMETRY, POINT, LINESTRING, POLYGON, MULTIPOINT,
MULTILINESTRING, MULTIPOLYGON,
+ GEOMETRYCOLLECTION -> {
+ WKTReader reader = new WKTReader();
+ yield reader.read(value);
+ }
+ default -> throw new IllegalArgumentException("Unsupported column
type: " + type);
+ };
+ } catch (Exception e) {
+ throw new DataStoreException("Error parsing value for column " +
column.name(), e);
+ }
+ }
+
+ @Override
+ public Spliterator<DataRow> spliterator() {
+ return Spliterators.spliteratorUnknownSize(iterator(),
Spliterator.ORDERED);
+ }
+
+ @Override
+ public Stream<DataRow> stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+}
diff --git
a/baremaps-core/src/test/java/org/apache/baremaps/storage/csv/CsvDataTableGeonamesTest.java
b/baremaps-core/src/test/java/org/apache/baremaps/storage/csv/CsvDataTableGeonamesTest.java
new file mode 100644
index 000000000..5fa787b3e
--- /dev/null
+++
b/baremaps-core/src/test/java/org/apache/baremaps/storage/csv/CsvDataTableGeonamesTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.apache.baremaps.storage.csv;
+
+import static org.apache.baremaps.testing.TestFiles.GEONAMES_CSV;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.baremaps.data.storage.*;
+import org.junit.jupiter.api.*;
+import org.locationtech.jts.geom.Point;
+
+class CsvDataTableGeonamesTest {
+
+ @Test
+ void testGeonamesCsvDataTable() throws IOException {
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("id", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("name", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("asciiname", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("alternatenames", DataColumn.Cardinality.OPTIONAL,
+ DataColumn.Type.STRING),
+ new DataColumnFixed("latitude", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.DOUBLE),
+ new DataColumnFixed("longitude", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.DOUBLE),
+ new DataColumnFixed("feature_class", DataColumn.Cardinality.OPTIONAL,
+ DataColumn.Type.STRING),
+ new DataColumnFixed("feature_code", DataColumn.Cardinality.OPTIONAL,
+ DataColumn.Type.STRING),
+ new DataColumnFixed("country_code", DataColumn.Cardinality.OPTIONAL,
+ DataColumn.Type.STRING),
+ new DataColumnFixed("cc2", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("admin1_code", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("admin2_code", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("admin3_code", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("admin4_code", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("population", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.LONG),
+ new DataColumnFixed("elevation", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("dem", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("timezone", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("modification_date",
DataColumn.Cardinality.OPTIONAL,
+ DataColumn.Type.STRING));
+ DataSchema schema = new DataSchemaImpl("geonames", columns);
+
+ boolean hasHeader = false;
+ char separator = '\t';
+ DataTable dataTable = new CsvDataTable(schema, GEONAMES_CSV.toFile(),
hasHeader, separator);
+
+ assertEquals(5, dataTable.size(), "DataTable should have 5 rows.");
+
+ int rowCount = 0;
+ for (DataRow row : dataTable) {
+ rowCount++;
+
+ // Extract values
+ Integer id = (Integer) row.get("id");
+ String name = (String) row.get("name");
+ Double latitude = (Double) row.get("latitude");
+ Double longitude = (Double) row.get("longitude");
+
+ // Perform assertions for each row
+ assertNotNull(id, "ID should not be null.");
+ assertNotNull(name, "Name should not be null.");
+ assertNotNull(latitude, "Latitude should not be null.");
+ assertNotNull(longitude, "Longitude should not be null.");
+
+ switch (id) {
+ case 1:
+ assertEquals("HEIG", name);
+ assertEquals(1.111, latitude);
+ assertEquals(1.111, longitude);
+ break;
+ case 2:
+ assertEquals("Yverdon-les-bains", name);
+ assertEquals(2.222, latitude);
+ assertEquals(2.222, longitude);
+ break;
+ case 3:
+ assertEquals("Route de Cheseaux 1", name);
+ assertEquals(3.333, latitude);
+ assertEquals(3.333, longitude);
+ break;
+ case 4:
+ assertEquals("Switzerland", name);
+ assertEquals(4.444, latitude);
+ assertEquals(4.444, longitude);
+ break;
+ case 5:
+ assertEquals("Switzerland", name);
+ assertEquals(47.00016, latitude);
+ assertEquals(8.01427, longitude);
+ break;
+ default:
+ fail("Unexpected ID: " + id);
+ }
+
+ Point point = createPoint(longitude, latitude);
+ assertNotNull(point, "Point geometry should not be null.");
+ }
+ assertEquals(5, rowCount, "Row count should be 5.");
+ }
+
+ private Point createPoint(Double longitude, Double latitude) {
+ if (longitude != null && latitude != null) {
+ return new org.locationtech.jts.geom.GeometryFactory()
+ .createPoint(new org.locationtech.jts.geom.Coordinate(longitude,
latitude));
+ } else {
+ return null;
+ }
+ }
+}
diff --git
a/baremaps-core/src/test/java/org/apache/baremaps/storage/csv/CsvDataTableTest.java
b/baremaps-core/src/test/java/org/apache/baremaps/storage/csv/CsvDataTableTest.java
new file mode 100644
index 000000000..ede62effb
--- /dev/null
+++
b/baremaps-core/src/test/java/org/apache/baremaps/storage/csv/CsvDataTableTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you 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.apache.baremaps.storage.csv;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.apache.baremaps.data.storage.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.locationtech.jts.geom.Geometry;
+
+class CsvDataTableTest {
+
+ private File tempCsvFile;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ Path tempCsvFilePath = Files.createTempFile("test", ".csv");
+ tempCsvFile = tempCsvFilePath.toFile();
+ tempCsvFile.deleteOnExit();
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (tempCsvFile.exists()) {
+ tempCsvFile.delete();
+ }
+ }
+
+ @Test
+ void testCsvWithHeaderAndCommaSeparator() throws IOException {
+ String csvContent = """
+ id,name,geom
+ 1,PointA,"POINT(1 1)"
+ 2,PointB,"POINT(2 2)"
+ """;
+ Files.writeString(tempCsvFile.toPath(), csvContent);
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("id", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("name", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("geom", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.GEOMETRY));
+ DataSchema schema = new DataSchemaImpl("test_table", columns);
+ DataTable dataTable = new CsvDataTable(schema, tempCsvFile, true, ',');
+ assertEquals(2, dataTable.size());
+ int rowCount = 0;
+ for (DataRow row : dataTable) {
+ rowCount++;
+ Integer id = (Integer) row.get("id");
+ String name = (String) row.get("name");
+ Geometry geometry = (Geometry) row.get("geom");
+ assertNotNull(id);
+ assertNotNull(name);
+ assertNotNull(geometry);
+ assertEquals("Point" + (rowCount == 1 ? "A" : "B"), name);
+ assertEquals("POINT (" + rowCount + " " + rowCount + ")",
geometry.toText());
+ }
+ assertEquals(2, rowCount);
+ }
+
+ @Test
+ void testCsvWithoutHeaderAndSemicolonSeparator() throws IOException {
+ String csvContent = """
+ 1;PointA;"POINT(1 1)"
+ 2;PointB;"POINT(2 2)"
+ """;
+ Files.writeString(tempCsvFile.toPath(), csvContent);
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("column1", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("column2", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING),
+ new DataColumnFixed("column3", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.GEOMETRY));
+ DataSchema schema = new DataSchemaImpl("test_table", columns);
+ DataTable dataTable = new CsvDataTable(schema, tempCsvFile, false, ';');
+ assertEquals(2, dataTable.size());
+ int rowCount = 0;
+ for (DataRow row : dataTable) {
+ rowCount++;
+ Integer id = (Integer) row.get("column1");
+ String name = (String) row.get("column2");
+ Geometry geometry = (Geometry) row.get("column3");
+
+ // Verify data
+ assertNotNull(id);
+ assertNotNull(name);
+ assertNotNull(geometry);
+
+ assertEquals("Point" + (rowCount == 1 ? "A" : "B"), name);
+ assertEquals("POINT (" + rowCount + " " + rowCount + ")",
geometry.toText());
+ }
+ assertEquals(2, rowCount);
+ }
+
+ @Test
+ void testCsvWithDifferentDataTypes() throws IOException {
+ String csvContent = """
+ int_col,double_col,bool_col,string_col
+ 1,1.1,true,Hello
+ 2,2.2,false,World
+ """;
+ Files.writeString(tempCsvFile.toPath(), csvContent);
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("int_col", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("double_col", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.DOUBLE),
+ new DataColumnFixed("bool_col", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.BOOLEAN),
+ new DataColumnFixed("string_col", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.STRING));
+ DataSchema schema = new DataSchemaImpl("test_table", columns);
+ DataTable dataTable = new CsvDataTable(schema, tempCsvFile, true, ',');
+ assertEquals(2, dataTable.size());
+ int rowCount = 0;
+ for (DataRow row : dataTable) {
+ rowCount++;
+ Integer intCol = (Integer) row.get("int_col");
+ Double doubleCol = (Double) row.get("double_col");
+ Boolean boolCol = (Boolean) row.get("bool_col");
+ String stringCol = (String) row.get("string_col");
+
+ // Verify data
+ assertEquals(rowCount, intCol);
+ assertEquals(rowCount * 1.1, doubleCol);
+ assertEquals(rowCount == 1, boolCol);
+ assertEquals(rowCount == 1 ? "Hello" : "World", stringCol);
+ }
+ assertEquals(2, rowCount);
+ }
+
+ @Test
+ void testCsvWithInvalidData() throws IOException {
+ String csvContent = """
+ id,name
+ abc,TestName
+ """;
+ Files.writeString(tempCsvFile.toPath(), csvContent);
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("id", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("name", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING));
+ DataSchema schema = new DataSchemaImpl("test_table", columns);
+ DataTable dataTable = new CsvDataTable(schema, tempCsvFile, true, ',');
+ assertThrows(RuntimeException.class, () -> {
+ for (DataRow row : dataTable) {
+ // This line should throw an exception because abc is not a valid
integer
+ row.values();
+ }
+ });
+ }
+
+ @Test
+ void testAddAndClearUnsupportedOperations() throws IOException {
+ String csvContent = "";
+ Files.writeString(tempCsvFile.toPath(), csvContent);
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("id", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("name", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING));
+ DataSchema schema = new DataSchemaImpl("test_table", columns);
+ DataTable dataTable = new CsvDataTable(schema, tempCsvFile, true, ',');
+ assertThrows(UnsupportedOperationException.class, () ->
dataTable.add(null));
+ assertThrows(UnsupportedOperationException.class, dataTable::clear);
+ }
+
+ @Test
+ void testSizeCalculation() throws IOException {
+ String csvContent = """
+ id,name
+ 1,Name1
+ 2,Name2
+ 3,Name3
+ """;
+ Files.writeString(tempCsvFile.toPath(), csvContent);
+ List<DataColumn> columns = List.of(
+ new DataColumnFixed("id", DataColumn.Cardinality.REQUIRED,
DataColumn.Type.INTEGER),
+ new DataColumnFixed("name", DataColumn.Cardinality.OPTIONAL,
DataColumn.Type.STRING));
+ DataSchema schema = new DataSchemaImpl("test_table", columns);
+ DataTable dataTable = new CsvDataTable(schema, tempCsvFile, true, ',');
+ assertEquals(3, dataTable.size());
+ }
+}
diff --git
a/baremaps-testing/src/main/java/org/apache/baremaps/testing/TestFiles.java
b/baremaps-testing/src/main/java/org/apache/baremaps/testing/TestFiles.java
index 4e7e2e2a9..2b284166e 100644
--- a/baremaps-testing/src/main/java/org/apache/baremaps/testing/TestFiles.java
+++ b/baremaps-testing/src/main/java/org/apache/baremaps/testing/TestFiles.java
@@ -76,6 +76,9 @@ public class TestFiles {
public static final Path TILEJSON_JSON =
resolve("baremaps-testing/data/tilesets/tilejson.json");
+ public static final Path GEONAMES_CSV =
+ resolve("baremaps-testing/data/geonames/sample.txt");
+
/* The geometries of the osm-sample/sample.osm.xml file */
private static final GeometryFactory GEOMETRY_FACTORY = new
GeometryFactory();