Skip to content
Snippets Groups Projects
Unverified Commit cd597a36 authored by Timothy Carambat's avatar Timothy Carambat Committed by GitHub
Browse files

OBDC Support (#1933)


* add possibility to connect to SQL Base by ODBC

---------

Co-authored-by: default avatarsuchaudn <nicolas.suchaud@legrand.fr>
Co-authored-by: default avatarnicho2 <nicho2@laposte.net>
parent 42235fcd
No related branches found
No related tags found
No related merge requests found
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
"Mintplex", "Mintplex",
"moderations", "moderations",
"numpages", "numpages",
"odbc",
"Ollama", "Ollama",
"Oobabooga", "Oobabooga",
"openai", "openai",
......
import PostgreSQLLogo from "./icons/postgresql.png"; import PostgreSQLLogo from "./icons/postgresql.png";
import MySQLLogo from "./icons/mysql.png"; import MySQLLogo from "./icons/mysql.png";
import MSSQLLogo from "./icons/mssql.png"; import MSSQLLogo from "./icons/mssql.png";
import ODBCLogo from "./icons/odbc.png";
import { X } from "@phosphor-icons/react"; import { X } from "@phosphor-icons/react";
export const DB_LOGOS = { export const DB_LOGOS = {
postgresql: PostgreSQLLogo, postgresql: PostgreSQLLogo,
mysql: MySQLLogo, mysql: MySQLLogo,
"sql-server": MSSQLLogo, "sql-server": MSSQLLogo,
odbc: ODBCLogo,
}; };
export default function DBConnection({ connection, onRemove, setHasChanges }) { export default function DBConnection({ connection, onRemove, setHasChanges }) {
......
...@@ -11,6 +11,7 @@ function assembleConnectionString({ ...@@ -11,6 +11,7 @@ function assembleConnectionString({
host = "", host = "",
port = "", port = "",
database = "", database = "",
driver = "",
}) { }) {
if ([username, password, host, database].every((i) => !!i) === false) if ([username, password, host, database].every((i) => !!i) === false)
return `Please fill out all the fields above.`; return `Please fill out all the fields above.`;
...@@ -21,6 +22,9 @@ function assembleConnectionString({ ...@@ -21,6 +22,9 @@ function assembleConnectionString({
return `mysql://${username}:${password}@${host}:${port}/${database}`; return `mysql://${username}:${password}@${host}:${port}/${database}`;
case "sql-server": case "sql-server":
return `mssql://${username}:${password}@${host}:${port}/${database}`; return `mssql://${username}:${password}@${host}:${port}/${database}`;
case "odbc":
if (!driver) return `Please fill out the driver field.`;
return `Driver={${driver}};Server=${host};Port=${port};Database=${database};UID=${username};PWD=${password}`;
default: default:
return null; return null;
} }
...@@ -33,6 +37,7 @@ const DEFAULT_CONFIG = { ...@@ -33,6 +37,7 @@ const DEFAULT_CONFIG = {
host: null, host: null,
port: null, port: null,
database: null, database: null,
driver: null,
}; };
export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
...@@ -48,12 +53,14 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { ...@@ -48,12 +53,14 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
function onFormChange() { function onFormChange() {
const form = new FormData(document.getElementById("sql-connection-form")); const form = new FormData(document.getElementById("sql-connection-form"));
setConfig({ setConfig({
username: form.get("username").trim(), username: form.get("username").trim(),
password: form.get("password"), password: form.get("password"),
host: form.get("host").trim(), host: form.get("host").trim(),
port: form.get("port").trim(), port: form.get("port").trim(),
database: form.get("database").trim(), database: form.get("database").trim(),
driver: form.get("driver")?.trim(),
}); });
} }
...@@ -74,7 +81,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { ...@@ -74,7 +81,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
// to the parent container form so we don't have nested forms. // to the parent container form so we don't have nested forms.
return createPortal( return createPortal(
<ModalWrapper isOpen={isOpen}> <ModalWrapper isOpen={isOpen}>
<div className="relative w-full md:w-1/3 max-w-2xl max-h-full md:mt-8"> <div className="relative w-full md:w-fit max-w-2xl max-h-full md:mt-8">
<div className="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)] max-h-[85vh] overflow-y-scroll no-scroll"> <div className="relative bg-main-gradient rounded-xl shadow-[0_4px_14px_rgba(0,0,0,0.25)] max-h-[85vh] overflow-y-scroll no-scroll">
<div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50"> <div className="flex items-start justify-between p-4 border-b rounded-t border-gray-500/50">
<h3 className="text-xl font-semibold text-white"> <h3 className="text-xl font-semibold text-white">
...@@ -114,7 +121,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { ...@@ -114,7 +121,7 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
<label className="text-white text-sm font-semibold block my-4"> <label className="text-white text-sm font-semibold block my-4">
Select your SQL engine Select your SQL engine
</label> </label>
<div className="grid md:grid-cols-4 gap-4 grid-cols-2"> <div className="flex flex-wrap gap-x-4 gap-y-4">
<DBEngine <DBEngine
provider="postgresql" provider="postgresql"
active={engine === "postgresql"} active={engine === "postgresql"}
...@@ -130,6 +137,11 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { ...@@ -130,6 +137,11 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
active={engine === "sql-server"} active={engine === "sql-server"}
onClick={() => setEngine("sql-server")} onClick={() => setEngine("sql-server")}
/> />
<DBEngine
provider="odbc"
active={engine === "odbc"}
onClick={() => setEngine("odbc")}
/>
</div> </div>
</div> </div>
...@@ -224,6 +236,23 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) { ...@@ -224,6 +236,23 @@ export default function NewSQLConnection({ isOpen, closeModal, onSubmit }) {
spellCheck={false} spellCheck={false}
/> />
</div> </div>
{engine === "odbc" && (
<div className="flex flex-col">
<label className="text-white text-sm font-semibold block mb-3">
Driver
</label>
<input
type="text"
name="driver"
className="border-none bg-zinc-900 text-white placeholder:text-white/20 text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="the driver to use eg: MongoDB ODBC 1.2.0 ANSI Driver"
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
)}
<p className="text-white/40 text-sm"> <p className="text-white/40 text-sm">
{assembleConnectionString({ engine, ...config })} {assembleConnectionString({ engine, ...config })}
</p> </p>
......
frontend/src/pages/Admin/Agents/SQLConnectorSelection/icons/odbc.png

19.2 KiB

...@@ -66,6 +66,7 @@ ...@@ -66,6 +66,7 @@
"mysql2": "^3.9.8", "mysql2": "^3.9.8",
"node-html-markdown": "^1.3.0", "node-html-markdown": "^1.3.0",
"node-llama-cpp": "^2.8.0", "node-llama-cpp": "^2.8.0",
"odbc": "^2.4.8",
"ollama": "^0.5.0", "ollama": "^0.5.0",
"openai": "4.38.5", "openai": "4.38.5",
"pg": "^8.11.5", "pg": "^8.11.5",
...@@ -101,4 +102,4 @@ ...@@ -101,4 +102,4 @@
"nodemon": "^2.0.22", "nodemon": "^2.0.22",
"prettier": "^3.0.3" "prettier": "^3.0.3"
} }
} }
\ No newline at end of file
const odbc = require("odbc");
const UrlPattern = require("url-pattern");
class ODBCConnector {
#connected = false;
database_id = "";
constructor(
config = {
connectionString: null,
}
) {
this.connectionString = config.connectionString;
this._client = null;
this.database_id = this.#parseDatabase();
}
#parseDatabase() {
const regex = /Database=([^;]+)/;
const match = this.connectionString.match(regex);
return match ? match[1] : null;
}
async connect() {
this._client = await odbc.connect(this.connectionString);
this.#connected = true;
return this._client;
}
/**
*
* @param {string} queryString the SQL query to be run
* @returns {import(".").QueryResult}
*/
async runQuery(queryString = "") {
const result = { rows: [], count: 0, error: null };
try {
if (!this.#connected) await this.connect();
const query = await this._client.query(queryString);
result.rows = query;
result.count = query.length;
} catch (err) {
console.log(this.constructor.name, err);
result.error = err.message;
} finally {
await this._client.close();
this.#connected = false;
}
return result;
}
getTablesSql() {
return `SELECT table_name FROM information_schema.tables WHERE table_schema = '${this.database_id}'`;
}
getTableSchemaSql(table_name) {
return `SHOW COLUMNS FROM ${this.database_id}.${table_name};`;
}
}
module.exports.ODBCConnector = ODBCConnector;
...@@ -2,7 +2,7 @@ const { SystemSettings } = require("../../../../../../models/systemSettings"); ...@@ -2,7 +2,7 @@ const { SystemSettings } = require("../../../../../../models/systemSettings");
const { safeJsonParse } = require("../../../../../http"); const { safeJsonParse } = require("../../../../../http");
/** /**
* @typedef {('postgresql'|'mysql'|'sql-server')} SQLEngine * @typedef {('postgresql'|'mysql'|'sql-server'|'odbc')} SQLEngine
*/ */
/** /**
...@@ -36,6 +36,9 @@ function getDBClient(identifier = "", connectionConfig = {}) { ...@@ -36,6 +36,9 @@ function getDBClient(identifier = "", connectionConfig = {}) {
case "sql-server": case "sql-server":
const { MSSQLConnector } = require("./MSSQL"); const { MSSQLConnector } = require("./MSSQL");
return new MSSQLConnector(connectionConfig); return new MSSQLConnector(connectionConfig);
case "odbc":
const { ODBCConnector } = require("./ODBC");
return new ODBCConnector(connectionConfig);
default: default:
throw new Error( throw new Error(
`There is no supported database connector for ${identifier}` `There is no supported database connector for ${identifier}`
......
...@@ -673,7 +673,7 @@ ...@@ -673,7 +673,7 @@
"@langchain/core" "~0.1" "@langchain/core" "~0.1"
js-tiktoken "^1.0.11" js-tiktoken "^1.0.11"
"@mapbox/node-pre-gyp@^1.0.11": "@mapbox/node-pre-gyp@^1.0.11", "@mapbox/node-pre-gyp@^1.0.5":
version "1.0.11" version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
...@@ -1588,7 +1588,7 @@ arrify@^2.0.0: ...@@ -1588,7 +1588,7 @@ arrify@^2.0.0:
resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
async@^3.2.3, async@^3.2.4: async@^3.0.1, async@^3.2.3, async@^3.2.4:
version "3.2.5" version "3.2.5"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
...@@ -4813,6 +4813,11 @@ node-abort-controller@^3.1.1: ...@@ -4813,6 +4813,11 @@ node-abort-controller@^3.1.1:
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
node-addon-api@^3.0.2:
version "3.2.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
node-addon-api@^5.0.0: node-addon-api@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
...@@ -5065,6 +5070,15 @@ octokit@^3.1.0: ...@@ -5065,6 +5070,15 @@ octokit@^3.1.0:
"@octokit/request-error" "^5.0.0" "@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0" "@octokit/types" "^12.0.0"
odbc@^2.4.8:
version "2.4.8"
resolved "https://registry.yarnpkg.com/odbc/-/odbc-2.4.8.tgz#56e34a1cafbaf1c2c53eec229b3a7604f890e3bf"
integrity sha512-W4VkBcr8iSe8hqpp2GoFPybCAJefC7eK837XThJkYCW4tBzyQisqkciwt1UYidU1OpKy1589y9dMN0tStiVB1Q==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.5"
async "^3.0.1"
node-addon-api "^3.0.2"
ollama@^0.5.0: ollama@^0.5.0:
version "0.5.0" version "0.5.0"
resolved "https://registry.yarnpkg.com/ollama/-/ollama-0.5.0.tgz#cb9bc709d4d3278c9f484f751b0d9b98b06f4859" resolved "https://registry.yarnpkg.com/ollama/-/ollama-0.5.0.tgz#cb9bc709d4d3278c9f484f751b0d9b98b06f4859"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment