diff --git a/Cargo.lock b/Cargo.lock index 08a2349266a..76fc6d987e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7050,6 +7050,7 @@ dependencies = [ "derive_more", "duckdb", "error-stack", + "hash-integrations-definition", "pretty_assertions", "proptest", "serde_json", diff --git a/libs/@blockprotocol/type-system/rust/docs/dependency-diagram.mmd b/libs/@blockprotocol/type-system/rust/docs/dependency-diagram.mmd index 94890a37cc3..b2d1e6b2794 100644 --- a/libs/@blockprotocol/type-system/rust/docs/dependency-diagram.mmd +++ b/libs/@blockprotocol/type-system/rust/docs/dependency-diagram.mmd @@ -34,16 +34,17 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10 diff --git a/libs/@local/codec/rust/docs/dependency-diagram.mmd b/libs/@local/codec/rust/docs/dependency-diagram.mmd index 8b9969e549a..68c09c7a0f7 100644 --- a/libs/@local/codec/rust/docs/dependency-diagram.mmd +++ b/libs/@local/codec/rust/docs/dependency-diagram.mmd @@ -39,16 +39,17 @@ graph TD 27[hashql-mir] 28[hashql-syntax-jexpr] 29[hash-integrations-definition] - 30[hash-middleware] - 31[hash-temporal-client] - 32[error-stack] - 33[hash-graph-benches] - 34[hash-graph-integration] - 35[hash-graph-test-data] + 30[hash-integrations-engine] + 31[hash-middleware] + 32[hash-temporal-client] + 33[error-stack] + 34[hash-graph-benches] + 35[hash-graph-integration] + 36[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 35 + 1 -.-> 36 2 -.-> 3 2 --> 21 4 --> 12 @@ -58,8 +59,8 @@ graph TD 5 --> 9 5 --> 24 6 --> 10 - 6 -.-> 30 - 6 --> 30 + 6 -.-> 31 + 6 --> 31 7 --> 1 8 --> 13 9 -.-> 6 @@ -68,11 +69,11 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 31 + 10 --> 32 11 --> 2 12 --> 10 - 13 -.-> 35 - 14 -.-> 35 + 13 -.-> 36 + 14 -.-> 36 15 --> 18 16 --> 2 17 -.-> 1 @@ -82,7 +83,7 @@ graph TD 19 --> 16 21 -.-> 20 21 --> 20 - 21 --> 32 + 21 --> 33 22 -.-> 23 23 --> 25 23 --> 28 @@ -94,8 +95,9 @@ graph TD 28 --> 22 28 --> 24 29 --> 1 - 30 --> 1 + 30 --> 29 31 --> 1 - 33 -.-> 4 - 34 -.-> 9 - 35 --> 10 + 32 --> 1 + 34 -.-> 4 + 35 -.-> 9 + 36 --> 10 diff --git a/libs/@local/codegen/docs/dependency-diagram.mmd b/libs/@local/codegen/docs/dependency-diagram.mmd index bc363c14cae..ce79f4ce4f9 100644 --- a/libs/@local/codegen/docs/dependency-diagram.mmd +++ b/libs/@local/codegen/docs/dependency-diagram.mmd @@ -37,15 +37,16 @@ graph TD 25[hashql-mir] 26[hashql-syntax-jexpr] 27[hash-integrations-definition] - 28[hash-middleware] - 29[hash-temporal-client] - 30[hash-graph-benches] - 31[hash-graph-integration] - 32[hash-graph-test-data] + 28[hash-integrations-engine] + 29[hash-middleware] + 30[hash-temporal-client] + 31[hash-graph-benches] + 32[hash-graph-integration] + 33[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 32 + 1 -.-> 33 2 -.-> 3 4 --> 12 4 --> 17 @@ -54,8 +55,8 @@ graph TD 5 --> 9 5 --> 22 6 --> 10 - 6 -.-> 28 - 6 --> 28 + 6 -.-> 29 + 6 --> 29 7 --> 1 8 --> 13 9 -.-> 6 @@ -64,11 +65,11 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 29 + 10 --> 30 11 --> 2 12 --> 10 - 13 -.-> 32 - 14 -.-> 32 + 13 -.-> 33 + 14 -.-> 33 15 --> 18 16 --> 2 17 -.-> 1 @@ -87,8 +88,9 @@ graph TD 26 --> 20 26 --> 22 27 --> 1 - 28 --> 1 + 28 --> 27 29 --> 1 - 30 -.-> 4 - 31 -.-> 9 - 32 --> 10 + 30 --> 1 + 31 -.-> 4 + 32 -.-> 9 + 33 --> 10 diff --git a/libs/@local/graph/authorization/rust/docs/dependency-diagram.mmd b/libs/@local/graph/authorization/rust/docs/dependency-diagram.mmd index f6e60ea0ade..836d869eb07 100644 --- a/libs/@local/graph/authorization/rust/docs/dependency-diagram.mmd +++ b/libs/@local/graph/authorization/rust/docs/dependency-diagram.mmd @@ -34,16 +34,17 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10 diff --git a/libs/@local/graph/store/rust/docs/dependency-diagram.mmd b/libs/@local/graph/store/rust/docs/dependency-diagram.mmd index 31af0d97e9b..4bfb8e6ae34 100644 --- a/libs/@local/graph/store/rust/docs/dependency-diagram.mmd +++ b/libs/@local/graph/store/rust/docs/dependency-diagram.mmd @@ -34,16 +34,17 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10 diff --git a/libs/@local/graph/temporal-versioning/docs/dependency-diagram.mmd b/libs/@local/graph/temporal-versioning/docs/dependency-diagram.mmd index 2218648527a..01cb9e911c5 100644 --- a/libs/@local/graph/temporal-versioning/docs/dependency-diagram.mmd +++ b/libs/@local/graph/temporal-versioning/docs/dependency-diagram.mmd @@ -34,16 +34,17 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10 diff --git a/libs/@local/graph/types/docs/dependency-diagram.mmd b/libs/@local/graph/types/docs/dependency-diagram.mmd index c9ec59fba95..ab6c50e4554 100644 --- a/libs/@local/graph/types/docs/dependency-diagram.mmd +++ b/libs/@local/graph/types/docs/dependency-diagram.mmd @@ -34,16 +34,17 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10 diff --git a/libs/@local/harpc/types/docs/dependency-diagram.mmd b/libs/@local/harpc/types/docs/dependency-diagram.mmd index 2dc6b0e75c0..c281d0dfea0 100644 --- a/libs/@local/harpc/types/docs/dependency-diagram.mmd +++ b/libs/@local/harpc/types/docs/dependency-diagram.mmd @@ -39,15 +39,16 @@ graph TD 27[hashql-mir] 28[hashql-syntax-jexpr] 29[hash-integrations-definition] - 30[hash-middleware] - 31[hash-temporal-client] - 32[hash-graph-benches] - 33[hash-graph-integration] - 34[hash-graph-test-data] + 30[hash-integrations-engine] + 31[hash-middleware] + 32[hash-temporal-client] + 33[hash-graph-benches] + 34[hash-graph-integration] + 35[hash-graph-test-data] 0 --> 3 0 --> 4 1 --> 10 - 1 -.-> 34 + 1 -.-> 35 2 --> 21 3 --> 11 3 --> 17 @@ -56,8 +57,8 @@ graph TD 4 --> 8 4 --> 24 5 --> 9 - 5 -.-> 30 - 5 --> 30 + 5 -.-> 31 + 5 --> 31 6 --> 1 7 --> 12 8 -.-> 5 @@ -66,11 +67,11 @@ graph TD 8 --> 13 9 --> 6 9 --> 12 - 9 --> 31 + 9 --> 32 10 --> 2 11 --> 9 - 12 -.-> 34 - 13 -.-> 34 + 12 -.-> 35 + 13 -.-> 35 14 --> 18 15 --> 20 16 --> 2 @@ -94,8 +95,9 @@ graph TD 28 --> 22 28 --> 24 29 --> 1 - 30 --> 1 + 30 --> 29 31 --> 1 - 32 -.-> 3 - 33 -.-> 8 - 34 --> 9 + 32 --> 1 + 33 -.-> 3 + 34 -.-> 8 + 35 --> 9 diff --git a/libs/@local/harpc/wire-protocol/docs/dependency-diagram.mmd b/libs/@local/harpc/wire-protocol/docs/dependency-diagram.mmd index 1fdb422e981..b63354590f6 100644 --- a/libs/@local/harpc/wire-protocol/docs/dependency-diagram.mmd +++ b/libs/@local/harpc/wire-protocol/docs/dependency-diagram.mmd @@ -38,16 +38,17 @@ graph TD 26[hashql-mir] 27[hashql-syntax-jexpr] 28[hash-integrations-definition] - 29[hash-middleware] - 30[hash-temporal-client] - 31[error-stack] - 32[hash-graph-benches] - 33[hash-graph-integration] - 34[hash-graph-test-data] + 29[hash-integrations-engine] + 30[hash-middleware] + 31[hash-temporal-client] + 32[error-stack] + 33[hash-graph-benches] + 34[hash-graph-integration] + 35[hash-graph-test-data] 0 --> 3 0 --> 4 1 --> 10 - 1 -.-> 34 + 1 -.-> 35 2 --> 20 3 --> 11 3 --> 16 @@ -56,8 +57,8 @@ graph TD 4 --> 8 4 --> 23 5 --> 9 - 5 -.-> 29 - 5 --> 29 + 5 -.-> 30 + 5 --> 30 6 --> 1 7 --> 12 8 -.-> 5 @@ -66,11 +67,11 @@ graph TD 8 --> 13 9 --> 6 9 --> 12 - 9 --> 30 + 9 --> 31 10 --> 2 11 --> 9 - 12 -.-> 34 - 13 -.-> 34 + 12 -.-> 35 + 13 -.-> 35 14 --> 17 15 --> 2 16 -.-> 1 @@ -80,7 +81,7 @@ graph TD 18 --> 15 20 -.-> 19 20 --> 19 - 20 --> 31 + 20 --> 32 21 -.-> 22 22 --> 24 22 --> 27 @@ -92,8 +93,9 @@ graph TD 27 --> 21 27 --> 23 28 --> 1 - 29 --> 1 + 29 --> 28 30 --> 1 - 32 -.-> 3 - 33 -.-> 8 - 34 --> 9 + 31 --> 1 + 33 -.-> 3 + 34 -.-> 8 + 35 --> 9 diff --git a/libs/@local/integrations/definition/docs/dependency-diagram.mmd b/libs/@local/integrations/definition/docs/dependency-diagram.mmd index cdbbb94b882..642cdc3af77 100644 --- a/libs/@local/integrations/definition/docs/dependency-diagram.mmd +++ b/libs/@local/integrations/definition/docs/dependency-diagram.mmd @@ -19,22 +19,24 @@ graph TD 8[harpc-wire-protocol] 9[hash-integrations-definition] class 9 root - 10[hash-temporal-client] - 11[error-stack] - 12[hash-graph-test-data] + 10[hash-integrations-engine] + 11[hash-temporal-client] + 12[error-stack] + 13[hash-graph-test-data] 0 --> 5 - 0 -.-> 12 + 0 -.-> 13 1 -.-> 2 1 --> 8 3 --> 0 4 --> 3 4 --> 6 - 4 --> 10 + 4 --> 11 5 --> 1 - 6 -.-> 12 + 6 -.-> 13 8 -.-> 7 8 --> 7 - 8 --> 11 + 8 --> 12 9 --> 0 - 10 --> 0 - 12 --> 4 + 10 --> 9 + 11 --> 0 + 13 --> 4 diff --git a/libs/@local/integrations/engine/Cargo.toml b/libs/@local/integrations/engine/Cargo.toml index 77d8d2fb781..169ce41ea2d 100644 --- a/libs/@local/integrations/engine/Cargo.toml +++ b/libs/@local/integrations/engine/Cargo.toml @@ -9,7 +9,8 @@ authors.workspace = true [dependencies] # Public workspace dependencies -error-stack = { workspace = true, public = true, features = ["std"] } +error-stack = { workspace = true, public = true, features = ["std"] } +hash-integrations-definition = { workspace = true, public = true } # Public third-party dependencies duckdb = { workspace = true, public = true, features = ["bundled", "json"] } diff --git a/libs/@local/integrations/engine/docs/dependency-diagram.mmd b/libs/@local/integrations/engine/docs/dependency-diagram.mmd index 34dc22ca164..7ba7f28f333 100644 --- a/libs/@local/integrations/engine/docs/dependency-diagram.mmd +++ b/libs/@local/integrations/engine/docs/dependency-diagram.mmd @@ -8,7 +8,35 @@ graph TD %% --> : Normal dependency %% -.-> : Dev dependency %% ---> : Build dependency - 0[hash-integrations-engine] - class 0 root - 1[error-stack] - 0 --> 1 + 0[type-system] + 1[hash-codec] + 2[hash-codegen] + 3[hash-graph-authorization] + 4[hash-graph-store] + 5[hash-graph-temporal-versioning] + 6[hash-graph-types] + 7[harpc-types] + 8[harpc-wire-protocol] + 9[hash-integrations-definition] + 10[hash-integrations-engine] + class 10 root + 11[hash-temporal-client] + 12[error-stack] + 13[hash-graph-test-data] + 0 --> 5 + 0 -.-> 13 + 1 -.-> 2 + 1 --> 8 + 3 --> 0 + 4 --> 3 + 4 --> 6 + 4 --> 11 + 5 --> 1 + 6 -.-> 13 + 8 -.-> 7 + 8 --> 7 + 8 --> 12 + 9 --> 0 + 10 --> 9 + 11 --> 0 + 13 --> 4 diff --git a/libs/@local/integrations/engine/docs/task-dependencies.json b/libs/@local/integrations/engine/docs/task-dependencies.json index 5a2d69a8b0d..e36859a5e10 100644 --- a/libs/@local/integrations/engine/docs/task-dependencies.json +++ b/libs/@local/integrations/engine/docs/task-dependencies.json @@ -1,7 +1,8 @@ { "package": "hash-integrations-engine", "dependencies": [ - "error-stack" + "error-stack", + "hash-integrations-definition" ], "tasks": { "doc:dependency-diagram": { diff --git a/libs/@local/integrations/engine/src/workspace/connection.rs b/libs/@local/integrations/engine/src/workspace/connection.rs index 9a8bafd751d..d49a5b6825a 100644 --- a/libs/@local/integrations/engine/src/workspace/connection.rs +++ b/libs/@local/integrations/engine/src/workspace/connection.rs @@ -6,8 +6,9 @@ use std::{ path::{Path, PathBuf}, }; -use duckdb::{Config, types::Value}; +use duckdb::{Config, Statement, params_from_iter, types::Value}; use error_stack::{Report, ResultExt as _}; +use hash_integrations_definition::Variables; use tokio::sync::mpsc; use crate::{ @@ -38,6 +39,7 @@ pub(super) struct Connection { duckdb: duckdb::Connection, files: WorkspaceFiles, disk_limit: Option, + variables: Variables, /// An exclusive lock on the workspace's lock file, held until the database is closed. lock: File, } @@ -132,7 +134,7 @@ impl Connection { } /// Opens the database at `path` with the limits in `config` and its settings locked. - pub(super) fn open(path: PathBuf, config: &WorkspaceConfig) -> Result> { + pub(super) fn open(path: PathBuf, config: WorkspaceConfig) -> Result> { let files = WorkspaceFiles::new(path); let lock = Self::lock(&files)?; @@ -181,6 +183,7 @@ impl Connection { duckdb, files, disk_limit: config.disk_limit, + variables: config.variables, lock, }; match opened.disk_usage().change_context(OpenError::DiskUsage)? { @@ -207,6 +210,27 @@ impl Connection { Select::check(sql, &serialized) } + /// Returns the value of each parameter of `statement`, in the order of their indices. + /// + /// DuckDB numbers each parameter name once, and compares names without regard to ASCII case. + fn parameter_values( + &self, + statement: &Statement<'_>, + ) -> Result, Report> { + (1..=statement.parameter_count()) + .map(|index| { + let name = statement + .parameter_name(index) + .map_err(StatementError::from_duckdb)?; + self.variables.get(&name).ok_or_else(|| { + Report::new(StatementError::UnknownParameter { + name: name.into_boxed_str(), + }) + }) + }) + .collect() + } + #[tracing::instrument(level = "debug", skip_all)] fn query(&self, sql: &str) -> Result> { let select = self.select(sql)?; @@ -214,7 +238,10 @@ impl Connection { .duckdb .prepare(select.as_str()) .map_err(StatementError::from_duckdb)?; - let results = statement.query([]).map_err(StatementError::from_duckdb)?; + let values = self.parameter_values(&statement)?; + let results = statement + .query(params_from_iter(values)) + .map_err(StatementError::from_duckdb)?; if let Some(executed) = results.as_ref() { for index in 0..executed.column_count() { @@ -253,13 +280,17 @@ impl Connection { fn create_table(&self, table: &str, sql: &str) -> Result<(), Report> { let select = self.select(sql)?; // `select` is one statement, so the whole text is one statement as well. - self.duckdb + let mut statement = self + .duckdb .prepare(&format!( "CREATE OR REPLACE TABLE {} AS {}", Identifier::new(table), select.as_str() )) - .and_then(|mut statement| statement.execute([])) + .map_err(StatementError::from_duckdb)?; + let values = self.parameter_values(&statement)?; + statement + .execute(params_from_iter(values)) .map_err(StatementError::from_duckdb)?; self.check_disk_limit() } @@ -399,7 +430,7 @@ impl Connection { /// unwinds, so nothing it holds is used after the panic. pub(super) fn serve( path: PathBuf, - config: &WorkspaceConfig, + config: WorkspaceConfig, ready: Reply, OpenError>, commands: &mut mpsc::Receiver<(RequestId, Command)>, ) { diff --git a/libs/@local/integrations/engine/src/workspace/error.rs b/libs/@local/integrations/engine/src/workspace/error.rs index 1ab25e51fd6..591ebf2d0c9 100644 --- a/libs/@local/integrations/engine/src/workspace/error.rs +++ b/libs/@local/integrations/engine/src/workspace/error.rs @@ -179,6 +179,8 @@ pub enum StatementError { StatementCount { count: usize }, #[display("the query calls table function `{name}`, which is not allowed")] TableFunction { name: Box }, + #[display("the query reads parameter `${name}`, which is not set")] + UnknownParameter { name: Box }, /// `data_type` is the column's type as the `duckdb` crate receives it, in Arrow's notation. #[display("column `{column}` has type `{data_type}`, which the workspace cannot read")] UnsupportedType { diff --git a/libs/@local/integrations/engine/src/workspace/mod.rs b/libs/@local/integrations/engine/src/workspace/mod.rs index e36fbfc278d..641528b7cf1 100644 --- a/libs/@local/integrations/engine/src/workspace/mod.rs +++ b/libs/@local/integrations/engine/src/workspace/mod.rs @@ -11,6 +11,7 @@ use core::{error::Error, num::NonZeroUsize}; use std::{path::PathBuf, thread}; use error_stack::{Report, ResultExt as _}; +use hash_integrations_definition::Variables; use tokio::sync::mpsc; pub use self::{ @@ -35,6 +36,8 @@ pub struct WorkspaceConfig { /// The number of requests that can wait while another runs. Defaults to 64. /// Callers wait for queue space when it is full. pub queue_capacity: NonZeroUsize, + /// The values of the `$NAME` parameters that queries read. + pub variables: Variables, /// The most memory DuckDB uses. A query that needs more spills to the temporary directory, /// or fails if it cannot. pub memory_limit: Option, @@ -58,6 +61,7 @@ impl Default for WorkspaceConfig { Self { queue_capacity: NonZeroUsize::new(64) .expect("the default queue capacity should be nonzero"), + variables: Variables::default(), memory_limit: None, temp_directory_limit: None, disk_limit: None, @@ -90,8 +94,8 @@ enum Command { /// /// A dedicated thread owns the database's only connection and runs queries in the order they /// are sent. A query is a single `SELECT` statement, and the only table functions it can call are -/// `range`, `generate_series` and `unnest`. Queries cannot open files, load extensions or change -/// settings. +/// `range`, `generate_series` and `unnest`. Queries read [`WorkspaceConfig::variables`] as +/// `$NAME` parameters, and cannot open files, load extensions or change settings. /// [`WorkspaceConfig::queue_capacity`] bounds the queue; callers wait for space when it is full. /// /// Only one workspace at a time can open a database. It holds a lock on `.lock` until @@ -142,7 +146,7 @@ impl Workspace { .name("integrations-workspace".to_owned()) .spawn(move || { let _entered = span.entered(); - Connection::serve(path, &config, ready, &mut receiver); + Connection::serve(path, config, ready, &mut receiver); }) .change_context(OpenError::Spawn)?; @@ -182,6 +186,8 @@ impl Workspace { /// - [`NotSelect`] if DuckDB does not read `sql` as a `SELECT` query /// - [`StatementCount`] if `sql` is not exactly one statement /// - [`TableFunction`] if the query calls a table function that is not allowed + /// - [`UnknownParameter`] if the query reads a parameter that [`WorkspaceConfig::variables`] + /// does not set /// - [`UnsupportedType`] if a column has a type whose values cannot be read /// - [`InvalidQuery`] if DuckDB cannot run the query as written /// - [`InvalidData`] if a value in the rows causes an error @@ -194,6 +200,7 @@ impl Workspace { /// [`NotSelect`]: StatementError::NotSelect /// [`StatementCount`]: StatementError::StatementCount /// [`TableFunction`]: StatementError::TableFunction + /// [`UnknownParameter`]: StatementError::UnknownParameter /// [`UnsupportedType`]: StatementError::UnsupportedType /// [`InvalidQuery`]: StatementError::InvalidQuery /// [`InvalidData`]: StatementError::InvalidData @@ -216,6 +223,8 @@ impl Workspace { /// - [`NotSelect`] if DuckDB does not read `sql` as a `SELECT` query /// - [`StatementCount`] if `sql` is not exactly one statement /// - [`TableFunction`] if the query calls a table function that is not allowed + /// - [`UnknownParameter`] if the query reads a parameter that [`WorkspaceConfig::variables`] + /// does not set /// - [`InvalidQuery`] if DuckDB cannot run the query as written /// - [`InvalidData`] if a value in the rows causes an error /// - [`Failed`] if DuckDB fails for another reason @@ -227,6 +236,7 @@ impl Workspace { /// [`NotSelect`]: StatementError::NotSelect /// [`StatementCount`]: StatementError::StatementCount /// [`TableFunction`]: StatementError::TableFunction + /// [`UnknownParameter`]: StatementError::UnknownParameter /// [`InvalidQuery`]: StatementError::InvalidQuery /// [`InvalidData`]: StatementError::InvalidData /// [`Failed`]: StatementError::Failed diff --git a/libs/@local/integrations/engine/tests/workspace.rs b/libs/@local/integrations/engine/tests/workspace.rs index 544d0963304..655580a216a 100644 --- a/libs/@local/integrations/engine/tests/workspace.rs +++ b/libs/@local/integrations/engine/tests/workspace.rs @@ -1,6 +1,11 @@ #![expect(missing_docs, reason = "integration tests")] -use core::{future::poll_fn, num::NonZeroUsize, pin::Pin, task::Poll, time::Duration}; +extern crate alloc; + +use alloc::collections::BTreeMap; +use core::{ + future::poll_fn, num::NonZeroUsize, pin::Pin, str::FromStr as _, task::Poll, time::Duration, +}; use std::{ fs, path::{Path, PathBuf}, @@ -8,6 +13,7 @@ use std::{ use duckdb::types::{TimeUnit, Value}; use error_stack::Report; +use hash_integrations_definition::{VariableName, Variables}; use hash_integrations_engine::{ ByteSize, DataErrorType, OpenError, QueryErrorType, SnapshotError, StatementError, Workspace, WorkspaceConfig, @@ -748,3 +754,76 @@ async fn disk_limit_checkpoints_before_refusing() { "the last table should be kept" ); } + +#[tokio::test] +async fn query_binds_parameters() { + let directory = TempDir::new().expect("should create a directory"); + let variables = Variables::try_from(BTreeMap::from( + [ + ("MIN_SEATS", "300"), + ("Excluded", "'); DROP TABLE flights; --"), + ] + .map(|(name, value)| { + ( + VariableName::from_str(name).expect("should be a valid variable name"), + value.into(), + ) + }), + )) + .expect("the variables should be valid"); + let workspace = open_with( + &directory, + WorkspaceConfig { + variables, + ..WorkspaceConfig::default() + }, + ) + .await; + + workspace + .create_table( + "flights".into(), + "SELECT * FROM (VALUES ('BA117', 214), ('LH400', 364)) AS flights (code, seats) WHERE \ + seats >= $min_seats::INTEGER - 100" + .into(), + ) + .await + .expect("the table should be created"); + let rows = workspace + .query( + "SELECT code, $EXCLUDED AS excluded FROM flights WHERE seats > $MIN_SEATS::INTEGER \ + AND code <> $excluded" + .into(), + ) + .await + .expect("the query should run"); + + assert_eq!( + rows.iter().collect::>(), + [[ + Value::Text("LH400".to_owned()), + Value::Text("'); DROP TABLE flights; --".to_owned()), + ]], + "each parameter should be bound as text, matched without regard to case" + ); +} + +#[tokio::test] +async fn query_refuses_unknown_parameter() { + let directory = TempDir::new().expect("should create a directory"); + let workspace = open(&directory).await; + + for (sql, name) in [("SELECT $MISSING", "MISSING"), ("SELECT ?", "1")] { + let report = workspace + .query(sql.into()) + .await + .expect_err("a parameter without a value should be refused"); + assert!( + matches!( + report.current_context(), + StatementError::UnknownParameter { name: found } if &**found == name + ), + "`{sql}` should be refused for parameter `{name}`, not by {report:?}" + ); + } +} diff --git a/libs/@local/temporal-client/docs/dependency-diagram.mmd b/libs/@local/temporal-client/docs/dependency-diagram.mmd index 2b573047cdc..e6bf5e8dfab 100644 --- a/libs/@local/temporal-client/docs/dependency-diagram.mmd +++ b/libs/@local/temporal-client/docs/dependency-diagram.mmd @@ -33,17 +33,18 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - class 26 root - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + class 27 root + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10 diff --git a/tests/graph/test-data/rust/docs/dependency-diagram.mmd b/tests/graph/test-data/rust/docs/dependency-diagram.mmd index b3f0d9b9c02..05238d870ee 100644 --- a/tests/graph/test-data/rust/docs/dependency-diagram.mmd +++ b/tests/graph/test-data/rust/docs/dependency-diagram.mmd @@ -33,17 +33,18 @@ graph TD 22[hashql-mir] 23[hashql-syntax-jexpr] 24[hash-integrations-definition] - 25[hash-middleware] - 26[hash-temporal-client] - 27[error-stack] - 28[hash-graph-benches] - 29[hash-graph-integration] - 30[hash-graph-test-data] - class 30 root + 25[hash-integrations-engine] + 26[hash-middleware] + 27[hash-temporal-client] + 28[error-stack] + 29[hash-graph-benches] + 30[hash-graph-integration] + 31[hash-graph-test-data] + class 31 root 0 --> 4 0 --> 5 1 --> 11 - 1 -.-> 30 + 1 -.-> 31 2 -.-> 3 2 --> 17 4 --> 12 @@ -52,8 +53,8 @@ graph TD 4 --> 23 5 --> 9 6 --> 10 - 6 -.-> 25 - 6 --> 25 + 6 -.-> 26 + 6 --> 26 7 --> 1 8 --> 13 9 -.-> 6 @@ -62,16 +63,16 @@ graph TD 9 --> 14 10 --> 7 10 --> 13 - 10 --> 26 + 10 --> 27 11 --> 2 12 --> 10 - 13 -.-> 30 - 14 -.-> 30 + 13 -.-> 31 + 14 -.-> 31 15 -.-> 1 15 --> 16 17 -.-> 16 17 --> 16 - 17 --> 27 + 17 --> 28 18 -.-> 19 19 --> 20 19 --> 23 @@ -81,8 +82,9 @@ graph TD 22 --> 21 23 --> 18 24 --> 1 - 25 --> 1 + 25 --> 24 26 --> 1 - 28 -.-> 4 - 29 -.-> 9 - 30 --> 10 + 27 --> 1 + 29 -.-> 4 + 30 -.-> 9 + 31 --> 10