This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch calcite-schema-ddl
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git

commit dbbeea8d75449272e1bb008bfe9bc096c7930d4d
Author: Bertil Chapuis <[email protected]>
AuthorDate: Mon Apr 14 17:09:04 2025 +0200

    Add schema for shapefiles
---
 .../calcite/shapefile/ShapefileSchema.java         | 155 +++++++++++++++++++++
 .../calcite/shapefile/ShapefileSchemaTest.java     | 154 ++++++++++++++++++++
 2 files changed, 309 insertions(+)

diff --git 
a/baremaps-calcite/src/main/java/org/apache/baremaps/calcite/shapefile/ShapefileSchema.java
 
b/baremaps-calcite/src/main/java/org/apache/baremaps/calcite/shapefile/ShapefileSchema.java
new file mode 100644
index 000000000..e66887b74
--- /dev/null
+++ 
b/baremaps-calcite/src/main/java/org/apache/baremaps/calcite/shapefile/ShapefileSchema.java
@@ -0,0 +1,155 @@
+/*
+ * 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
+ * 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.calcite.shapefile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.impl.AbstractSchema;
+
+/**
+ * A Calcite schema implementation for shapefile data. This schema provides 
access to shapefiles
+ * through the Apache Calcite framework for SQL querying.
+ */
+public class ShapefileSchema extends AbstractSchema {
+
+  private final File directory;
+  private final Map<String, Table> tableMap;
+  private final RelDataTypeFactory typeFactory;
+
+  /**
+   * Constructs a ShapefileSchema with the specified directory.
+   *
+   * @param directory the directory containing shapefiles
+   * @param typeFactory the type factory to use for creating tables
+   * @throws IOException if an I/O error occurs
+   */
+  public ShapefileSchema(File directory, RelDataTypeFactory typeFactory) 
throws IOException {
+    this.directory = Objects.requireNonNull(directory, "Directory cannot be 
null");
+    this.typeFactory = Objects.requireNonNull(typeFactory, "Type factory 
cannot be null");
+    this.tableMap = new HashMap<>();
+
+    // Process files in the directory
+    File[] files = directory.listFiles((dir, name) -> 
+        name.toLowerCase().endsWith(".shp"));
+    
+    if (files != null) {
+      for (File file : files) {
+        // Extract the base name without extension (e.g., "countries" from 
"countries.shp")
+        String fileName = file.getName();
+        String tableName = fileName;
+        
+        // Remove all extensions (e.g., "countries.shp" -> "countries")
+        while (tableName.contains(".")) {
+          int lastDotIndex = tableName.lastIndexOf('.');
+          if (lastDotIndex > 0) {
+            tableName = tableName.substring(0, lastDotIndex);
+          } else {
+            break;
+          }
+        }
+        
+        // Create the table with the file reference
+        tableMap.put(tableName, createTable(file));
+      }
+    }
+  }
+
+  /**
+   * Constructs a ShapefileSchema with a single file.
+   *
+   * @param file the shapefile
+   * @param typeFactory the type factory to use for creating tables
+   * @throws IOException if an I/O error occurs
+   */
+  public ShapefileSchema(File file, RelDataTypeFactory typeFactory, boolean 
isDirectory) throws IOException {
+    if (isDirectory) {
+      // If isDirectory is true, treat the file as a directory
+      this.directory = Objects.requireNonNull(file, "Directory cannot be 
null");
+      this.typeFactory = Objects.requireNonNull(typeFactory, "Type factory 
cannot be null");
+      this.tableMap = new HashMap<>();
+
+      // Process files in the directory
+      File[] files = file.listFiles((dir, name) -> 
+          name.toLowerCase().endsWith(".shp"));
+      
+      if (files != null) {
+        for (File shapeFile : files) {
+          // Extract the base name without extension (e.g., "countries" from 
"countries.shp")
+          String fileName = shapeFile.getName();
+          String tableName = fileName;
+          
+          // Remove all extensions (e.g., "countries.shp" -> "countries")
+          while (tableName.contains(".")) {
+            int lastDotIndex = tableName.lastIndexOf('.');
+            if (lastDotIndex > 0) {
+              tableName = tableName.substring(0, lastDotIndex);
+            } else {
+              break;
+            }
+          }
+          
+          // Create the table with the file reference
+          tableMap.put(tableName, createTable(shapeFile));
+        }
+      }
+    } else {
+      // If isDirectory is false, treat the file as a single file
+      this.directory = Objects.requireNonNull(file, "File cannot be null");
+      this.typeFactory = Objects.requireNonNull(typeFactory, "Type factory 
cannot be null");
+      this.tableMap = new HashMap<>();
+
+      // Extract the base name without extension (e.g., "countries" from 
"countries.shp")
+      String fileName = file.getName();
+      String tableName = fileName;
+      
+      // Remove all extensions (e.g., "countries.shp" -> "countries")
+      while (tableName.contains(".")) {
+        int lastDotIndex = tableName.lastIndexOf('.');
+        if (lastDotIndex > 0) {
+          tableName = tableName.substring(0, lastDotIndex);
+        } else {
+          break;
+        }
+      }
+      
+      // Create the table with the file reference
+      tableMap.put(tableName, createTable(file));
+    }
+  }
+
+  /**
+   * Creates a table for the given file.
+   *
+   * @param file the shapefile
+   * @return the created table
+   * @throws IOException if an I/O error occurs
+   */
+  private Table createTable(File file) throws IOException {
+    return new ShapefileTable(file);
+  }
+
+  @Override
+  protected Map<String, Table> getTableMap() {
+    return tableMap;
+  }
+} 
\ No newline at end of file
diff --git 
a/baremaps-calcite/src/test/java/org/apache/baremaps/calcite/shapefile/ShapefileSchemaTest.java
 
b/baremaps-calcite/src/test/java/org/apache/baremaps/calcite/shapefile/ShapefileSchemaTest.java
new file mode 100644
index 000000000..fb534dd0e
--- /dev/null
+++ 
b/baremaps-calcite/src/test/java/org/apache/baremaps/calcite/shapefile/ShapefileSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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
+ * 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.calcite.shapefile;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import org.apache.baremaps.testing.TestFiles;
+import org.apache.calcite.config.CalciteConnectionConfig;
+import org.apache.calcite.config.CalciteConnectionConfigImpl;
+import org.apache.calcite.config.CalciteConnectionProperty;
+import org.apache.calcite.jdbc.CalciteConnection;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.SchemaPlus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Test class for the ShapefileSchema implementation.
+ */
+public class ShapefileSchemaTest {
+
+  @TempDir
+  Path tempDir;
+
+  @BeforeEach
+  public void setup() throws SQLException, IOException {
+    // Create a temporary directory for test files
+    Path testDir = tempDir.resolve("shapefile-test");
+    Files.createDirectories(testDir);
+
+    // Copy the sample shapefile to the test directory
+    Path sourceFile = TestFiles.POINT_SHP;
+    Path targetFile = testDir.resolve("point.shp");
+    Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+
+    // Copy the associated .dbf file
+    Path sourceDbf = sourceFile.resolveSibling("point.dbf");
+    Path targetDbf = targetFile.resolveSibling("point.dbf");
+    Files.copy(sourceDbf, targetDbf, StandardCopyOption.REPLACE_EXISTING);
+
+    // Copy the associated .shx file
+    Path sourceShx = sourceFile.resolveSibling("point.shx");
+    Path targetShx = targetFile.resolveSibling("point.shx");
+    Files.copy(sourceShx, targetShx, StandardCopyOption.REPLACE_EXISTING);
+  }
+
+  @Test
+  public void testSchemaCreation() throws SQLException, IOException {
+    // Create a connection to Calcite
+    Connection connection = DriverManager.getConnection("jdbc:calcite:");
+    CalciteConnection calciteConnection = 
connection.unwrap(CalciteConnection.class);
+    SchemaPlus rootSchema = calciteConnection.getRootSchema();
+
+    // Create and register the shapefile schema
+    ShapefileSchema schema = new 
ShapefileSchema(tempDir.resolve("shapefile-test").toFile(), 
calciteConnection.getTypeFactory());
+    rootSchema.add("shapefile", schema);
+
+    // Verify that the schema contains the expected table
+    assertTrue(rootSchema.getSubSchemaNames().contains("shapefile"));
+    
assertTrue(rootSchema.getSubSchema("shapefile").getTableNames().contains("point"));
+
+    connection.close();
+  }
+
+  @Test
+  public void testSqlQuery() throws SQLException, IOException {
+    // Create a connection to Calcite
+    Connection connection = DriverManager.getConnection("jdbc:calcite:");
+    CalciteConnection calciteConnection = 
connection.unwrap(CalciteConnection.class);
+    SchemaPlus rootSchema = calciteConnection.getRootSchema();
+
+    // Create and register the shapefile schema
+    ShapefileSchema schema = new 
ShapefileSchema(tempDir.resolve("shapefile-test").toFile(), 
calciteConnection.getTypeFactory());
+    rootSchema.add("shapefile", schema);
+
+    // Execute a simple SQL query - use lowercase for schema and table names
+    try (Statement statement = connection.createStatement()) {
+      ResultSet resultSet = statement.executeQuery(
+          "SELECT * FROM \"shapefile\".\"point\"");
+
+      // Verify that we get results
+      assertTrue(resultSet.next());
+      
+      // Verify that the result set has the expected columns
+      // Note: The actual column names will depend on the sample shapefile
+      assertNotNull(resultSet.getMetaData());
+      assertTrue(resultSet.getMetaData().getColumnCount() > 0);
+    }
+
+    connection.close();
+  }
+
+  @Test
+  public void testSingleFileSchema() throws SQLException, IOException {
+    // Create a connection to Calcite
+    Connection connection = DriverManager.getConnection("jdbc:calcite:");
+    CalciteConnection calciteConnection = 
connection.unwrap(CalciteConnection.class);
+    SchemaPlus rootSchema = calciteConnection.getRootSchema();
+
+    // Get the sample shapefile
+    File sampleFile = 
tempDir.resolve("shapefile-test").resolve("point.shp").toFile();
+
+    // Create and register the shapefile schema with a single file
+    ShapefileSchema schema = new ShapefileSchema(sampleFile, 
calciteConnection.getTypeFactory(), false);
+    rootSchema.add("single", schema);
+
+    // Verify that the schema contains the expected table
+    assertTrue(rootSchema.getSubSchemaNames().contains("single"));
+    
assertTrue(rootSchema.getSubSchema("single").getTableNames().contains("point"));
+
+    // Execute a simple SQL query - use lowercase for schema and table names
+    try (Statement statement = connection.createStatement()) {
+      ResultSet resultSet = statement.executeQuery(
+          "SELECT * FROM \"single\".\"point\"");
+
+      // Verify that we get results
+      assertTrue(resultSet.next());
+      
+      // Verify that the result set has the expected columns
+      assertNotNull(resultSet.getMetaData());
+      assertTrue(resultSet.getMetaData().getColumnCount() > 0);
+    }
+
+    connection.close();
+  }
+} 
\ No newline at end of file

Reply via email to