diff --git a/persistence-modules/duckdb/pom.xml b/persistence-modules/duckdb/pom.xml
new file mode 100644
index 0000000000..9ce4205ee6
--- /dev/null
+++ b/persistence-modules/duckdb/pom.xml
@@ -0,0 +1,42 @@
+
+ 4.0.0
+
+ org.baeldung
+ duckdb
+ duckdb
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 9
+ 9
+
+
+
+
+ 0.0.1-SNAPSHOT
+
+
+ com.baeldung
+ persistence-modules
+ 1.0.0-SNAPSHOT
+
+
+
+ UTF-8
+ 0.10.0
+
+
+
+
+ org.duckdb
+ duckdb_jdbc
+ ${duckdb.version}
+
+
+
+
diff --git a/persistence-modules/duckdb/src/test/java/com/baeldung/DuckDbAccess.java b/persistence-modules/duckdb/src/test/java/com/baeldung/DuckDbAccess.java
new file mode 100644
index 0000000000..47c70a2256
--- /dev/null
+++ b/persistence-modules/duckdb/src/test/java/com/baeldung/DuckDbAccess.java
@@ -0,0 +1,198 @@
+package com.baeldung;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+
+class DuckDbAccessIntegrationTest {
+
+ private Connection conn = null;
+ private Statement stmt = null;
+
+ @BeforeAll
+ static void setupOnce() throws Exception {
+ Class.forName("org.duckdb.DuckDBDriver");
+ }
+
+ @BeforeEach
+ void setup() throws SQLException {
+ conn = DriverManager.getConnection("jdbc:duckdb:");
+ stmt = conn.createStatement();
+ }
+
+ @Test
+ void whenQueryCurrentDate_thenReturnToday() throws SQLException {
+ ResultSet rs = stmt.executeQuery("SELECT current_date");
+ Date currentDate = rs.next() ? rs.getDate(1) : null;
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ Date expectedDate = calendar.getTime();
+
+ assertThat(currentDate).isEqualTo(expectedDate);
+ }
+
+ @Test
+ void whenReadCsv_thenReturnHeaderAndData() throws SQLException {
+ String filePath = getResourceAbsolutePath("/customer.csv");
+ String query = String.format("SELECT * FROM read_csv('%s')", filePath);
+
+ ResultSet rs = stmt.executeQuery(query);
+ ResultSetMetaData metadata = rs.getMetaData();
+
+ List actualHeaderNames = new ArrayList<>();
+ for (int n=1; n<=metadata.getColumnCount(); n++) {
+ actualHeaderNames.add(metadata.getColumnLabel(n));
+ }
+
+ // Then
+ List expectedHeaderNames = List.of("CustomerId", "FirstName", "LastName", "Gender");
+ assertThat(actualHeaderNames).isEqualTo(expectedHeaderNames);
+
+ int rowCount = 0;
+ while (rs.next()) {
+ rowCount++;
+ }
+ assertThat(rowCount).isEqualTo(10);
+ }
+
+ @Test
+ void whenImportByCsv_thenReturnCorrectRowCount() throws SQLException {
+ // When
+ String filePath = getResourceAbsolutePath("/customer.csv");
+ String query = String.format("CREATE TABLE customer AS SELECT * FROM read_csv('%s')", filePath);
+ stmt.executeUpdate(query);
+
+ // Then
+ assertThat(getTableRowCount(conn, "customer")).isEqualTo(10);
+ }
+
+ @Test
+ void whenImportByJson_thenReturnCorrectRowCount() throws SQLException {
+ // When
+ String filePath = getResourceAbsolutePath("/product.json");
+ String query = String.format("CREATE TABLE product AS SELECT * FROM read_json('%s')", filePath);
+ stmt.executeUpdate(query);
+
+ // Then
+ assertThat(getTableRowCount(conn, "product")).isEqualTo(3);
+ }
+
+ @Test
+ void whenImportByInsert_thenReturnCorrectRowCount() throws SQLException {
+ // When
+ stmt.executeUpdate("CREATE TABLE purchase(customerId BIGINT, productId BIGINT)");
+
+ String query = "INSERT INTO purchase(customerId, productId) VALUES (?,?)";
+ try (PreparedStatement pStmt = conn.prepareStatement(query)) {
+
+ pStmt.setInt(1, 101);
+ pStmt.setInt(2, 1);
+ pStmt.addBatch();
+
+ pStmt.setInt(1, 101);
+ pStmt.setInt(2, 2);
+ pStmt.addBatch();
+
+ pStmt.setInt(1, 102);
+ pStmt.setInt(2, 2);
+ pStmt.addBatch();
+
+ pStmt.executeBatch();
+ }
+
+ // Then
+ assertThat(getTableRowCount(conn, "purchase")).isEqualTo(3);
+ }
+
+ @Test
+ void whenQueryWithJoin_thenReturnCorrectCount() throws SQLException {
+ String customerFilePath = getResourceAbsolutePath("/customer.csv");
+ String productFilePath = getResourceAbsolutePath("/product.json");
+ whenImportByInsert_thenReturnCorrectRowCount();
+
+ String query = String.format("SELECT C.firstName, C.lastName, P.productName " +
+ "FROM read_csv('%s') AS C, read_json('%s') AS P, purchase S " +
+ "WHERE S.customerId = C.customerId " +
+ "AND S.productId = P.productId ",
+ customerFilePath, productFilePath);
+
+ int count = 0;
+ ResultSet rs = stmt.executeQuery(query);
+ while (rs.next()) {
+ count++;
+ }
+
+ assertThat(count).isEqualTo(3);
+ }
+
+ @Test
+ void whenExportData_thenFileIsCreated() throws IOException, SQLException {
+ createPurchaseView(conn);
+
+ File tempFile = File.createTempFile("temp", "");
+ String exportFilePath = tempFile.getAbsolutePath();
+ String query = String.format("COPY purchase_view TO '%s'", exportFilePath);
+ stmt.executeUpdate(query);
+
+ assertThat(tempFile.length()).isGreaterThan(0);
+ tempFile.delete();
+ }
+
+ @AfterEach
+ void tearDown() throws SQLException {
+ stmt.close();
+ conn.close();
+ }
+
+ private String getResourceAbsolutePath(String name) {
+ return this.getClass().getResource(name).getPath().replaceFirst("/", "");
+ }
+
+ private int getTableRowCount(Connection conn, String tableName) throws SQLException {
+ try (Statement stmt = conn.createStatement()) {
+ ResultSet rs = stmt.executeQuery(String.format("SELECT COUNT(*) FROM %s", tableName));
+ return (rs.next()) ? rs.getInt(1) : 0;
+ }
+ }
+
+ private void createPurchaseView(Connection conn) throws SQLException {
+ whenImportByCsv_thenReturnCorrectRowCount();
+ whenImportByJson_thenReturnCorrectRowCount();
+ whenImportByInsert_thenReturnCorrectRowCount();
+
+ String query = "CREATE VIEW purchase_view AS " +
+ "SELECT P.productName, COUNT(*) AS purchaseCount " +
+ "FROM customer C, product P, purchase S " +
+ "WHERE S.customerId = C.customerId " +
+ "AND S.productId = P.productId " +
+ "GROUP BY P.productName " +
+ "ORDER BY COUNT(*) DESC ";
+
+ try (Statement stmt = conn.createStatement()) {
+ stmt.executeUpdate(query);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/persistence-modules/duckdb/src/test/resources/customer.csv b/persistence-modules/duckdb/src/test/resources/customer.csv
new file mode 100644
index 0000000000..f0da8f569d
--- /dev/null
+++ b/persistence-modules/duckdb/src/test/resources/customer.csv
@@ -0,0 +1,11 @@
+CustomerId,FirstName,LastName,Gender
+101,John,Smith,Male
+102,Sarah,Jones,Female
+103,Michael,Johnson,Male
+104,Emily,Davis,Female
+105,David,Brown,Male
+106,Emma,Williams,Female
+107,Alexander,Miller,Male
+108,Samantha,Anderson,Female
+109,Matthew,Taylor,Male
+110,Olivia,Thompson,Female
\ No newline at end of file
diff --git a/persistence-modules/duckdb/src/test/resources/product.json b/persistence-modules/duckdb/src/test/resources/product.json
new file mode 100644
index 0000000000..ae3b47de28
--- /dev/null
+++ b/persistence-modules/duckdb/src/test/resources/product.json
@@ -0,0 +1,17 @@
+[
+ {
+ "productId": 1,
+ "productName":"EZ Curl Bar",
+ "category": "Sports Equipment"
+ },
+ {
+ "productId": 2,
+ "productName": "7' Barbell",
+ "category": "Sports Equipment"
+ },
+ {
+ "productId": 3,
+ "productName": "Single Mouthguard - Black",
+ "category": "Sports Equipment"
+ }
+]
\ No newline at end of file
diff --git a/persistence-modules/pom.xml b/persistence-modules/pom.xml
index 7512e99441..16893b825f 100644
--- a/persistence-modules/pom.xml
+++ b/persistence-modules/pom.xml
@@ -24,6 +24,7 @@
core-java-persistence-2
core-java-persistence-3
couchbase
+ duckdb
elasticsearch
flyway
flyway-repair