Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/duckdb: POC duck db node api #17

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions webapp/app/api/db-example/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { fetchExample } from "@/app/lib/data";

// an api route fetching data
export async function GET() {
try {
const reader = await fetchExample();
return Response.json({
status: "OK",
rows: reader.getRowObjectsJson(),
columnNames: reader.columnNames(),
columnTypes: reader.columnTypes(),
count: reader.columnCount,
});
} catch (error) {
console.error("Error while retrieving data:", error);
return Response.json({ error }, { status: 500 });
}
}
3 changes: 3 additions & 0 deletions webapp/app/duckdb-example/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return "Loading...";
}
57 changes: 57 additions & 0 deletions webapp/app/duckdb-example/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { fetchExample } from "../lib/data";

export default async function Page() {
//using api route
// try {

// const response = await fetch("http://localhost:3001/api/db-example", { cache: "no-store" })
// const results = response.json();
// } catch (err) {
// console.error("Error fetching DB status:", err)
// }

// using directly the data layer
const reader = await fetchExample();

return (
<div className="flex items-center justify-center min-h-screen bg-gray-100 p-4">
<div className="w-full max-w-5xl overflow-x-auto bg-white shadow-lg rounded-2xl p-6">
<table className="w-full border-collapse border border-gray-300">
<thead>
<tr className="bg-gray-200 text-gray-700">
<th className="border border-gray-300 px-4 py-2">Row</th>
{Array.from({ length: reader.columnCount }, (_, i) => (
<th key={i} className="border border-gray-300 px-4 py-2">
{reader.columnName(i)}
</th>
))}
</tr>
</thead>
<tbody>
{Object.entries(reader.getRows()).map(([key, value]) => (
<tr key={key} className="hover:bg-gray-100">
<td className="border border-gray-300 px-4 py-2 font-semibold">
{key}
</td>
{Array.from({ length: reader.columnCount }, (_, i) => (
<td key={i} className="border border-gray-300 px-4 py-2">
{/* Affichage par type - exemple avec des méthodes propre à certains types => pas d'erreur, le typage semble bon */}
{/* {value[i] != null &&
((reader.columnType(i).typeId === DuckDBTypeId.VARCHAR &&
String(value[i]).slice(0, 3)) ||
(reader.columnType(i).typeId === DuckDBTypeId.BIGINT &&
(value[i] as bigint) * BigInt(100000)) ||
(reader.columnType(i).typeId === DuckDBTypeId.DOUBLE &&
(value[i] as number)?.toExponential()))} */}
{/* Affichage simple */}
{String(value[i])}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
34 changes: 34 additions & 0 deletions webapp/app/lib/data.ts
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Les tables que j'utilise sont celles de la database du projet, celles que tu précises sont dans quel database ? J'ai peut être loupé une info, est ce qu'elle serait dispo sur un bucket ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depuis le merge de la PR #9, les tables ont été renommées. Si tu rebuild la database (ou juste si tu la télécharges puisqu'il n'y a plus besoin de la build en local), tu verras le changement. Attention, c'était un breaking change, tu dois la delete avant de rebuild.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah oui merci !!

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import db from "./duckdb";

/**
* Rows are still read in chunks of 2048 elements.
*/
const ROW_TARGET_COUNT = 1000;

export async function fetchExample() {
try {
const connection = await db.connect();

const result = await connection.runAndReadUntil(
"SELECT * from edc_resultats",
ROW_TARGET_COUNT
);

// Example of query with a group by :
// const result = await connection.runAndReadUntil(
// "SELECT qualitparam, count(*) from edc_resultats group by qualitparam",
// ROW_TARGET_COUNT //(Rows are read in chunks of 2048.)
// );

// Example of a prepared statement :
// const prepared = await connection.prepare(
// "SELECT qualitparam, count(*) from edc_resultats where qualitparam = $1 group by qualitparam"
// );
// prepared.bindVarchar(1, "O");
// const result = await prepared.runAndReadUntil(ROW_TARGET_COUNT);
return result;
} catch (error) {
console.error("Database Error:", error);
throw new Error("Failed to fetch example rows.");
}
}
21 changes: 21 additions & 0 deletions webapp/app/lib/duckdb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { DuckDBInstance } from "@duckdb/node-api";

import fs from "fs";
import path from "path";

// Access the database file one level above the current project directory
const dbFilePath = path.join(process.cwd(), "../database", "data.duckdb");
// Check if the file exists
if (!fs.existsSync(dbFilePath)) {
throw new Error("Database file not found");
}

//TODO: need to handle hot reload in dev mode
console.log("Create DB instance...");
// next build needs access to the file and can't if not using read_only because the file gets locked - @see https://duckdb.org/docs/connect/concurrency
// it may be possible without it if we use the database differently or configure next (maybe ?), but as we are only reading in the db it should be better like this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je confirme qu'être en read-only est bien pour notre use case

const db = await DuckDBInstance.create(dbFilePath, {
access_mode: "READ_ONLY",
});

export default db;
2 changes: 1 addition & 1 deletion webapp/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
/* config options here */ serverExternalPackages: ["@duckdb/node-api"],
};

export default nextConfig;
81 changes: 81 additions & 0 deletions webapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@duckdb/node-api": "^1.1.3-alpha.12",
"maplibre-gl": "^5.0.1",
"next": "15.1.6",
"react": "^19.0.0",
Expand Down