feat: add PostgreSQL driver with Postgres effect
Implements full PostgreSQL support through the Postgres effect: - connect(connStr): Connect to PostgreSQL database - close(conn): Close connection - execute(conn, sql): Execute INSERT/UPDATE/DELETE, return affected rows - query(conn, sql): Execute SELECT, return all rows as records - queryOne(conn, sql): Execute SELECT, return first row as Option - beginTx(conn): Start transaction - commit(conn): Commit transaction - rollback(conn): Rollback transaction Includes: - Connection tracking with connection IDs - Row mapping to Lux records with field access - Transaction support - Example: examples/postgres_demo.lux - Documentation in docs/guide/11-databases.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
501
Cargo.lock
generated
501
Cargo.lock
generated
@@ -2,6 +2,18 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -14,12 +26,29 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.89"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -32,12 +61,27 @@ version = "2.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.1"
|
version = "3.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -107,6 +151,15 @@ version = "0.8.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.15"
|
version = "0.5.15"
|
||||||
@@ -122,6 +175,27 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -170,6 +244,24 @@ version = "3.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-streaming-iterator"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -236,6 +328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -270,6 +363,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -277,6 +371,16 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -285,7 +389,19 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -320,6 +436,15 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -335,12 +460,30 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
version = "0.5.12"
|
version = "0.5.12"
|
||||||
@@ -575,6 +718,27 @@ version = "0.2.182"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libsqlite3-sys"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -587,6 +751,15 @@ version = "0.8.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.29"
|
||||||
@@ -625,8 +798,10 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"lsp-server",
|
"lsp-server",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"rand",
|
"postgres",
|
||||||
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rusqlite",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -635,6 +810,16 @@ dependencies = [
|
|||||||
"tiny_http",
|
"tiny_http",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md-5"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -654,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -696,6 +881,24 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-foundation"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-system-configuration"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
|
||||||
|
dependencies = [
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -746,12 +949,54 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -770,6 +1015,49 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres"
|
||||||
|
version = "0.19.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7c48ece1c6cda0db61b058c1721378da76855140e9214339fa1317decacb176"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tokio-postgres",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres-protocol"
|
||||||
|
version = "0.6.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ee9dd5fe15055d2b6806f4736aa0c9637217074e224bbec46d4041b91bb9491"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"hmac",
|
||||||
|
"md-5",
|
||||||
|
"memchr",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"sha2",
|
||||||
|
"stringprep",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postgres-types"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b858f82211e84682fecd373f68e1ceae642d8d751a1ebd13f33de6257b3e20"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"postgres-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -839,8 +1127,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha 0.9.0",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -850,7 +1148,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -862,13 +1170,31 @@ dependencies = [
|
|||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.27"
|
version = "0.11.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -902,6 +1228,20 @@ dependencies = [
|
|||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"fallible-iterator 0.3.0",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@@ -921,7 +1261,7 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -967,6 +1307,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.6.0"
|
version = "3.6.0"
|
||||||
@@ -1062,12 +1408,29 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@@ -1106,6 +1469,23 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stringprep"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
"unicode-properties",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.115"
|
version = "2.0.115"
|
||||||
@@ -1210,6 +1590,21 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.49.0"
|
version = "1.49.0"
|
||||||
@@ -1234,6 +1629,32 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-postgres"
|
||||||
|
version = "0.7.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcea47c8f71744367793f16c2db1f11cb859d28f436bdb4ca9193eb1f787ee42"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"fallible-iterator 0.2.0",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
|
"phf",
|
||||||
|
"pin-project-lite",
|
||||||
|
"postgres-protocol",
|
||||||
|
"postgres-types",
|
||||||
|
"rand 0.9.2",
|
||||||
|
"socket2 0.6.2",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"whoami",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.18"
|
version = "0.7.18"
|
||||||
@@ -1278,12 +1699,39 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-properties"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -1333,6 +1781,12 @@ version = "0.2.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -1348,6 +1802,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.14.7+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||||
|
dependencies = [
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.2+wasi-0.2.9"
|
version = "1.0.2+wasi-0.2.9"
|
||||||
@@ -1366,6 +1829,15 @@ dependencies = [
|
|||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasite"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
|
||||||
|
dependencies = [
|
||||||
|
"wasi 0.14.7+wasi-0.2.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.108"
|
version = "0.2.108"
|
||||||
@@ -1469,6 +1941,19 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "whoami"
|
||||||
|
version = "2.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6a5b12f9df4f978d2cfdb1bd3bac52433f44393342d7ee9c25f5a1c14c0f45d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"objc2-system-configuration",
|
||||||
|
"wasite",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ serde_json = "1"
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
|
postgres = "0.19"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
450
docs/guide/11-databases.md
Normal file
450
docs/guide/11-databases.md
Normal file
@@ -0,0 +1,450 @@
|
|||||||
|
# Working with Databases
|
||||||
|
|
||||||
|
Lux includes built-in support for SQL databases through the `Sql` effect. This guide covers how to connect to databases, execute queries, handle transactions, and best practices for database operations.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn main(): Unit with {Console, Sql} = {
|
||||||
|
// Open an in-memory SQLite database
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
|
||||||
|
// Create a table
|
||||||
|
Sql.execute(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
|
||||||
|
// Insert data
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('Alice')")
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('Bob')")
|
||||||
|
|
||||||
|
// Query data
|
||||||
|
let users = Sql.query(db, "SELECT * FROM users")
|
||||||
|
Console.print("Found users: " ++ toString(users))
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
Sql.close(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
run main() with {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connecting to Databases
|
||||||
|
|
||||||
|
### In-Memory Database
|
||||||
|
|
||||||
|
For testing and temporary data:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
// Database exists only in memory, lost when closed
|
||||||
|
```
|
||||||
|
|
||||||
|
### File-Based Database
|
||||||
|
|
||||||
|
For persistent storage:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let db = Sql.open("./data/app.db")
|
||||||
|
// Creates file if it doesn't exist
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connection Lifecycle
|
||||||
|
|
||||||
|
Always close connections when done:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn withDatabase<T>(path: String, f: fn(SqlConn): T with Sql): T with Sql = {
|
||||||
|
let db = Sql.open(path)
|
||||||
|
let result = f(db)
|
||||||
|
Sql.close(db)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Executing Queries
|
||||||
|
|
||||||
|
### Non-Returning Queries (INSERT, UPDATE, DELETE, CREATE)
|
||||||
|
|
||||||
|
Use `Sql.execute` for statements that don't return rows:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Create table
|
||||||
|
Sql.execute(db, "CREATE TABLE IF NOT EXISTS posts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)")
|
||||||
|
|
||||||
|
// Insert
|
||||||
|
Sql.execute(db, "INSERT INTO posts (title, content) VALUES ('Hello', 'World')")
|
||||||
|
|
||||||
|
// Update
|
||||||
|
Sql.execute(db, "UPDATE posts SET title = 'Updated' WHERE id = 1")
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
Sql.execute(db, "DELETE FROM posts WHERE id = 1")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queries that Return Rows (SELECT)
|
||||||
|
|
||||||
|
Use `Sql.query` to get all matching rows:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Returns List<SqlRow>
|
||||||
|
let users = Sql.query(db, "SELECT * FROM users")
|
||||||
|
|
||||||
|
// Each row is a record-like structure
|
||||||
|
for user in users {
|
||||||
|
Console.print("User: " ++ user.name)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `Sql.queryOne` to get a single row (or None):
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Returns Option<SqlRow>
|
||||||
|
let maybeUser = Sql.queryOne(db, "SELECT * FROM users WHERE id = 1")
|
||||||
|
|
||||||
|
match maybeUser {
|
||||||
|
Some(user) => Console.print("Found: " ++ user.name),
|
||||||
|
None => Console.print("User not found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transactions
|
||||||
|
|
||||||
|
Transactions ensure multiple operations succeed or fail together.
|
||||||
|
|
||||||
|
### Basic Transaction
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Start transaction
|
||||||
|
Sql.beginTx(db)
|
||||||
|
|
||||||
|
// Do operations
|
||||||
|
Sql.execute(db, "INSERT INTO accounts (name, balance) VALUES ('Alice', 1000)")
|
||||||
|
Sql.execute(db, "INSERT INTO accounts (name, balance) VALUES ('Bob', 1000)")
|
||||||
|
|
||||||
|
// Commit changes
|
||||||
|
Sql.commit(db)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback on Error
|
||||||
|
|
||||||
|
```lux
|
||||||
|
Sql.beginTx(db)
|
||||||
|
|
||||||
|
let result = transferFunds(db, fromId, toId, amount)
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(_) => Sql.commit(db),
|
||||||
|
Err(_) => Sql.rollback(db) // Undo all changes
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transaction Helper
|
||||||
|
|
||||||
|
Here's a pattern for safe transactions:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn transaction<T>(db: SqlConn, f: fn(): T with Sql): Result<T, String> with Sql = {
|
||||||
|
Sql.beginTx(db)
|
||||||
|
|
||||||
|
// Execute the function
|
||||||
|
// In a real implementation, you'd catch errors here
|
||||||
|
let result = f()
|
||||||
|
|
||||||
|
Sql.commit(db)
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working with Results
|
||||||
|
|
||||||
|
Query results are returned as `List<SqlRow>` where each row behaves like a record:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let rows = Sql.query(db, "SELECT id, name, age FROM users")
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
// Access columns by name
|
||||||
|
let id = row.id // Int
|
||||||
|
let name = row.name // String
|
||||||
|
let age = row.age // Int or null
|
||||||
|
|
||||||
|
Console.print("{name} (age {age})")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling NULL values
|
||||||
|
|
||||||
|
NULL values from the database are represented as options:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
let row = Sql.queryOne(db, "SELECT email FROM users WHERE id = 1")
|
||||||
|
|
||||||
|
match row {
|
||||||
|
Some(r) => {
|
||||||
|
match r.email {
|
||||||
|
Some(email) => Console.print("Email: " ++ email),
|
||||||
|
None => Console.print("No email set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Console.print("User not found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SQL Injection Prevention
|
||||||
|
|
||||||
|
**IMPORTANT**: The current API passes SQL strings directly. For production use, always:
|
||||||
|
|
||||||
|
1. Validate and sanitize user input
|
||||||
|
2. Use parameterized queries (when available)
|
||||||
|
3. Never concatenate user input into SQL strings
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// DANGEROUS - Never do this!
|
||||||
|
let query = "SELECT * FROM users WHERE name = '" ++ userInput ++ "'"
|
||||||
|
|
||||||
|
// SAFER - Validate input first
|
||||||
|
fn safeUserLookup(db: SqlConn, userId: Int): Option<SqlRow> with Sql = {
|
||||||
|
// Integers are safe to interpolate
|
||||||
|
Sql.queryOne(db, "SELECT * FROM users WHERE id = " ++ toString(userId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// For strings, escape quotes at minimum
|
||||||
|
fn escapeString(s: String): String = {
|
||||||
|
String.replace(s, "'", "''")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Repository Pattern
|
||||||
|
|
||||||
|
Encapsulate database operations:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
type User = { id: Int, name: String, email: Option<String> }
|
||||||
|
|
||||||
|
fn createUser(db: SqlConn, name: String): Int with Sql = {
|
||||||
|
Sql.execute(db, "INSERT INTO users (name) VALUES ('" ++ escapeString(name) ++ "')")
|
||||||
|
// Get last inserted ID
|
||||||
|
let row = Sql.queryOne(db, "SELECT last_insert_rowid() as id")
|
||||||
|
match row {
|
||||||
|
Some(r) => r.id,
|
||||||
|
None => -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findUserById(db: SqlConn, id: Int): Option<User> with Sql = {
|
||||||
|
let row = Sql.queryOne(db, "SELECT * FROM users WHERE id = " ++ toString(id))
|
||||||
|
match row {
|
||||||
|
Some(r) => Some({ id: r.id, name: r.name, email: r.email }),
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findAllUsers(db: SqlConn): List<User> with Sql = {
|
||||||
|
let rows = Sql.query(db, "SELECT * FROM users ORDER BY name")
|
||||||
|
List.map(rows, fn(r) => { id: r.id, name: r.name, email: r.email })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with In-Memory Database
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn testUserRepository(): Unit with {Test, Sql} = {
|
||||||
|
// Each test gets a fresh database
|
||||||
|
let db = Sql.openMemory()
|
||||||
|
|
||||||
|
// Set up schema
|
||||||
|
Sql.execute(db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
|
||||||
|
// Test
|
||||||
|
let id = createUser(db, "Test User")
|
||||||
|
Test.assertTrue(id > 0, "Should return valid ID")
|
||||||
|
|
||||||
|
let user = findUserById(db, id)
|
||||||
|
Test.assertTrue(Option.isSome(user), "Should find created user")
|
||||||
|
|
||||||
|
Sql.close(db)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handler for Testing (Mock Database)
|
||||||
|
|
||||||
|
The effect system lets you swap database implementations:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Production handler uses real SQLite
|
||||||
|
handler realDatabase(): Sql {
|
||||||
|
// Uses actual rusqlite implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test handler uses in-memory storage
|
||||||
|
handler mockDatabase(): Sql {
|
||||||
|
let storage = ref []
|
||||||
|
|
||||||
|
fn execute(conn, sql) = {
|
||||||
|
// Parse and simulate SQL
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query(conn, sql) = {
|
||||||
|
// Return mock data
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run with mock database in tests
|
||||||
|
run userService() with {
|
||||||
|
Sql -> mockDatabase()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
```lux
|
||||||
|
type SqlConn // Opaque connection handle
|
||||||
|
type SqlRow // Row result with named column access
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operations
|
||||||
|
|
||||||
|
| Function | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `Sql.open(path)` | `String -> SqlConn` | Open database file |
|
||||||
|
| `Sql.openMemory()` | `() -> SqlConn` | Open in-memory database |
|
||||||
|
| `Sql.close(conn)` | `SqlConn -> Unit` | Close connection |
|
||||||
|
| `Sql.execute(conn, sql)` | `(SqlConn, String) -> Int` | Execute statement, return affected rows |
|
||||||
|
| `Sql.query(conn, sql)` | `(SqlConn, String) -> List<SqlRow>` | Query, return all rows |
|
||||||
|
| `Sql.queryOne(conn, sql)` | `(SqlConn, String) -> Option<SqlRow>` | Query, return first row |
|
||||||
|
| `Sql.beginTx(conn)` | `SqlConn -> Unit` | Begin transaction |
|
||||||
|
| `Sql.commit(conn)` | `SqlConn -> Unit` | Commit transaction |
|
||||||
|
| `Sql.rollback(conn)` | `SqlConn -> Unit` | Rollback transaction |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Database operations can fail. In the current implementation, errors cause runtime failures. Future versions will support returning `Result` types:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Future API (not yet implemented)
|
||||||
|
fn safeQuery(db: SqlConn, sql: String): Result<List<SqlRow>, SqlError> with Sql = {
|
||||||
|
Sql.tryQuery(db, sql)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
1. **Batch inserts in transactions** - Much faster than individual inserts:
|
||||||
|
```lux
|
||||||
|
Sql.beginTx(db)
|
||||||
|
for item in items {
|
||||||
|
Sql.execute(db, "INSERT INTO data (value) VALUES (" ++ toString(item) ++ ")")
|
||||||
|
}
|
||||||
|
Sql.commit(db)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Use `queryOne` for single results** - More efficient than `query` when you only need one row
|
||||||
|
|
||||||
|
3. **Create indexes** for frequently queried columns:
|
||||||
|
```lux
|
||||||
|
Sql.execute(db, "CREATE INDEX idx_users_email ON users(email)")
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Close connections** when done to free resources
|
||||||
|
|
||||||
|
## PostgreSQL Support
|
||||||
|
|
||||||
|
Lux also provides native PostgreSQL support through the `Postgres` effect. This is ideal for production applications requiring a full-featured relational database.
|
||||||
|
|
||||||
|
### Connecting to PostgreSQL
|
||||||
|
|
||||||
|
```lux
|
||||||
|
fn main(): Unit with {Console, Postgres} = {
|
||||||
|
// Connect using a connection string
|
||||||
|
let connStr = "host=localhost user=myuser password=mypass dbname=mydb"
|
||||||
|
let conn = Postgres.connect(connStr)
|
||||||
|
|
||||||
|
Console.print("Connected to PostgreSQL!")
|
||||||
|
|
||||||
|
// ... use the connection ...
|
||||||
|
|
||||||
|
Postgres.close(conn)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL Operations
|
||||||
|
|
||||||
|
The PostgreSQL API mirrors the SQLite API:
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Execute non-returning queries
|
||||||
|
Postgres.execute(conn, "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)")
|
||||||
|
Postgres.execute(conn, "INSERT INTO users (name) VALUES ('Alice')")
|
||||||
|
|
||||||
|
// Query multiple rows
|
||||||
|
let users = Postgres.query(conn, "SELECT * FROM users")
|
||||||
|
for user in users {
|
||||||
|
Console.print("User: " ++ user.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query single row
|
||||||
|
match Postgres.queryOne(conn, "SELECT * FROM users WHERE id = 1") {
|
||||||
|
Some(user) => Console.print("Found: " ++ user.name),
|
||||||
|
None => Console.print("Not found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL Transactions
|
||||||
|
|
||||||
|
```lux
|
||||||
|
// Start transaction
|
||||||
|
Postgres.beginTx(conn)
|
||||||
|
|
||||||
|
// Make changes
|
||||||
|
Postgres.execute(conn, "UPDATE accounts SET balance = balance - 100 WHERE id = 1")
|
||||||
|
Postgres.execute(conn, "UPDATE accounts SET balance = balance + 100 WHERE id = 2")
|
||||||
|
|
||||||
|
// Commit or rollback
|
||||||
|
Postgres.commit(conn)
|
||||||
|
// Or: Postgres.rollback(conn)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL API Reference
|
||||||
|
|
||||||
|
| Function | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `Postgres.connect(connStr)` | `String -> Int` | Connect to PostgreSQL, returns connection ID |
|
||||||
|
| `Postgres.close(conn)` | `Int -> Unit` | Close connection |
|
||||||
|
| `Postgres.execute(conn, sql)` | `(Int, String) -> Int` | Execute statement, return affected rows |
|
||||||
|
| `Postgres.query(conn, sql)` | `(Int, String) -> List<Row>` | Query, return all rows |
|
||||||
|
| `Postgres.queryOne(conn, sql)` | `(Int, String) -> Option<Row>` | Query, return first row |
|
||||||
|
| `Postgres.beginTx(conn)` | `Int -> Unit` | Begin transaction |
|
||||||
|
| `Postgres.commit(conn)` | `Int -> Unit` | Commit transaction |
|
||||||
|
| `Postgres.rollback(conn)` | `Int -> Unit` | Rollback transaction |
|
||||||
|
|
||||||
|
### When to Use SQLite vs PostgreSQL
|
||||||
|
|
||||||
|
| Feature | SQLite | PostgreSQL |
|
||||||
|
|---------|--------|------------|
|
||||||
|
| Setup | No setup needed | Requires server |
|
||||||
|
| Deployment | Single file | Server process |
|
||||||
|
| Concurrency | Limited | Excellent |
|
||||||
|
| Scale | Small-medium | Large |
|
||||||
|
| Features | Basic | Full RDBMS |
|
||||||
|
| Use case | Embedded, testing | Production |
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- No connection pooling yet
|
||||||
|
- No prepared statements / parameterized queries yet
|
||||||
|
- Limited type mapping (basic Int, String, Float)
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Effects Guide](./05-effects.md) - Understanding the effect system
|
||||||
|
- [Testing Guide](../testing.md) - Writing tests with mock handlers
|
||||||
|
- [Roadmap](../ROADMAP.md) - Planned database features
|
||||||
185
examples/postgres_demo.lux
Normal file
185
examples/postgres_demo.lux
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// PostgreSQL Database Example
|
||||||
|
//
|
||||||
|
// Demonstrates the Postgres effect for database operations.
|
||||||
|
//
|
||||||
|
// Prerequisites:
|
||||||
|
// - PostgreSQL server running locally
|
||||||
|
// - Database 'testdb' created
|
||||||
|
// - User 'testuser' with password 'testpass'
|
||||||
|
//
|
||||||
|
// To set up:
|
||||||
|
// createdb testdb
|
||||||
|
// psql testdb -c "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT);"
|
||||||
|
//
|
||||||
|
// Run with: lux examples/postgres_demo.lux
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Helper Functions
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn jsonStr(key: String, value: String): String =
|
||||||
|
"\"" + key + "\":\"" + value + "\""
|
||||||
|
|
||||||
|
fn jsonNum(key: String, value: Int): String =
|
||||||
|
"\"" + key + "\":" + toString(value)
|
||||||
|
|
||||||
|
fn jsonObj(content: String): String =
|
||||||
|
"{" + content + "}"
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Database Operations
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Insert a user
|
||||||
|
fn insertUser(connId: Int, name: String, email: String): Int with {Console, Postgres} = {
|
||||||
|
let sql = "INSERT INTO users (name, email) VALUES ('" + name + "', '" + email + "') RETURNING id"
|
||||||
|
Console.print("Inserting user: " + name)
|
||||||
|
match Postgres.queryOne(connId, sql) {
|
||||||
|
Some(row) => {
|
||||||
|
Console.print(" Inserted with ID: " + toString(row.id))
|
||||||
|
row.id
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
Console.print(" Insert failed")
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all users
|
||||||
|
fn getUsers(connId: Int): Unit with {Console, Postgres} = {
|
||||||
|
Console.print("Fetching all users...")
|
||||||
|
let rows = Postgres.query(connId, "SELECT id, name, email FROM users ORDER BY id")
|
||||||
|
Console.print(" Found " + toString(List.length(rows)) + " users:")
|
||||||
|
List.forEach(rows, fn(row: { id: Int, name: String, email: String }): Unit with {Console} => {
|
||||||
|
Console.print(" - " + toString(row.id) + ": " + row.name + " <" + row.email + ">")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user by ID
|
||||||
|
fn getUserById(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
||||||
|
let sql = "SELECT id, name, email FROM users WHERE id = " + toString(id)
|
||||||
|
Console.print("Looking up user " + toString(id) + "...")
|
||||||
|
match Postgres.queryOne(connId, sql) {
|
||||||
|
Some(row) => Console.print(" Found: " + row.name + " <" + row.email + ">"),
|
||||||
|
None => Console.print(" User not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user email
|
||||||
|
fn updateUserEmail(connId: Int, id: Int, newEmail: String): Unit with {Console, Postgres} = {
|
||||||
|
let sql = "UPDATE users SET email = '" + newEmail + "' WHERE id = " + toString(id)
|
||||||
|
Console.print("Updating user " + toString(id) + " email to " + newEmail)
|
||||||
|
let affected = Postgres.execute(connId, sql)
|
||||||
|
Console.print(" Rows affected: " + toString(affected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
fn deleteUser(connId: Int, id: Int): Unit with {Console, Postgres} = {
|
||||||
|
let sql = "DELETE FROM users WHERE id = " + toString(id)
|
||||||
|
Console.print("Deleting user " + toString(id))
|
||||||
|
let affected = Postgres.execute(connId, sql)
|
||||||
|
Console.print(" Rows affected: " + toString(affected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Transaction Example
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn transactionDemo(connId: Int): Unit with {Console, Postgres} = {
|
||||||
|
Console.print("")
|
||||||
|
Console.print("=== Transaction Demo ===")
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
Console.print("Beginning transaction...")
|
||||||
|
Postgres.beginTx(connId)
|
||||||
|
|
||||||
|
// Make some changes
|
||||||
|
insertUser(connId, "TxUser1", "tx1@example.com")
|
||||||
|
insertUser(connId, "TxUser2", "tx2@example.com")
|
||||||
|
|
||||||
|
// Show users before commit
|
||||||
|
Console.print("Users before commit:")
|
||||||
|
getUsers(connId)
|
||||||
|
|
||||||
|
// Commit the transaction
|
||||||
|
Console.print("Committing transaction...")
|
||||||
|
Postgres.commit(connId)
|
||||||
|
|
||||||
|
Console.print("Transaction committed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
fn main(): Unit with {Console, Postgres} = {
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print(" PostgreSQL Demo")
|
||||||
|
Console.print("========================================")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Connect to database
|
||||||
|
Console.print("Connecting to PostgreSQL...")
|
||||||
|
let connStr = "host=localhost user=testuser password=testpass dbname=testdb"
|
||||||
|
let connId = Postgres.connect(connStr)
|
||||||
|
Console.print("Connected! Connection ID: " + toString(connId))
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Create table if not exists
|
||||||
|
Console.print("Creating users table...")
|
||||||
|
Postgres.execute(connId, "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL)")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Clear table for demo
|
||||||
|
Console.print("Clearing existing data...")
|
||||||
|
Postgres.execute(connId, "DELETE FROM users")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Insert some users
|
||||||
|
Console.print("=== Inserting Users ===")
|
||||||
|
let id1 = insertUser(connId, "Alice", "alice@example.com")
|
||||||
|
let id2 = insertUser(connId, "Bob", "bob@example.com")
|
||||||
|
let id3 = insertUser(connId, "Charlie", "charlie@example.com")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Query all users
|
||||||
|
Console.print("=== All Users ===")
|
||||||
|
getUsers(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Query single user
|
||||||
|
Console.print("=== Single User Lookup ===")
|
||||||
|
getUserById(connId, id2)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Update user
|
||||||
|
Console.print("=== Update User ===")
|
||||||
|
updateUserEmail(connId, id2, "bob.new@example.com")
|
||||||
|
getUserById(connId, id2)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
Console.print("=== Delete User ===")
|
||||||
|
deleteUser(connId, id3)
|
||||||
|
getUsers(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Transaction demo
|
||||||
|
transactionDemo(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Final state
|
||||||
|
Console.print("=== Final State ===")
|
||||||
|
getUsers(connId)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Close connection
|
||||||
|
Console.print("Closing connection...")
|
||||||
|
Postgres.close(connId)
|
||||||
|
Console.print("Done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This will fail if PostgreSQL is not running
|
||||||
|
// To test the syntax only, you can comment out the last line
|
||||||
|
let output = run main() with {}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::diagnostics::{Diagnostic, ErrorCode, Severity};
|
use crate::diagnostics::{Diagnostic, ErrorCode, Severity};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use postgres::{Client as PgClient, NoTls};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -615,6 +616,10 @@ pub struct Interpreter {
|
|||||||
sql_connections: RefCell<HashMap<i64, Connection>>,
|
sql_connections: RefCell<HashMap<i64, Connection>>,
|
||||||
/// Next SQL connection ID
|
/// Next SQL connection ID
|
||||||
next_sql_conn_id: RefCell<i64>,
|
next_sql_conn_id: RefCell<i64>,
|
||||||
|
/// PostgreSQL database connections (connection ID -> Client)
|
||||||
|
pg_connections: RefCell<HashMap<i64, PgClient>>,
|
||||||
|
/// Next PostgreSQL connection ID
|
||||||
|
next_pg_conn_id: RefCell<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Results from running tests
|
/// Results from running tests
|
||||||
@@ -657,6 +662,8 @@ impl Interpreter {
|
|||||||
test_results: RefCell::new(TestResults::default()),
|
test_results: RefCell::new(TestResults::default()),
|
||||||
sql_connections: RefCell::new(HashMap::new()),
|
sql_connections: RefCell::new(HashMap::new()),
|
||||||
next_sql_conn_id: RefCell::new(1),
|
next_sql_conn_id: RefCell::new(1),
|
||||||
|
pg_connections: RefCell::new(HashMap::new()),
|
||||||
|
next_pg_conn_id: RefCell::new(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4054,6 +4061,314 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PostgreSQL Effect
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
("Postgres", "connect") => {
|
||||||
|
let conn_str = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.connect requires a connection string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match PgClient::connect(&conn_str, NoTls) {
|
||||||
|
Ok(client) => {
|
||||||
|
let id = *self.next_pg_conn_id.borrow();
|
||||||
|
*self.next_pg_conn_id.borrow_mut() += 1;
|
||||||
|
self.pg_connections.borrow_mut().insert(id, client);
|
||||||
|
Ok(Value::Int(id))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.connect failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "close") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.close requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.pg_connections.borrow_mut().remove(&conn_id).is_some() {
|
||||||
|
Ok(Value::Unit)
|
||||||
|
} else {
|
||||||
|
Err(RuntimeError {
|
||||||
|
message: format!("Postgres.close: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "execute") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.execute requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.execute: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute(&sql, &[]) {
|
||||||
|
Ok(rows) => Ok(Value::Int(rows as i64)),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.execute failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "query") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.query requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.query: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.query(&sql, &[]) {
|
||||||
|
Ok(rows) => {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
let mut record = HashMap::new();
|
||||||
|
for (i, col) in row.columns().iter().enumerate() {
|
||||||
|
let col_name = col.name().to_string();
|
||||||
|
let value: Value = match col.type_().name() {
|
||||||
|
"int4" | "int8" | "int2" => {
|
||||||
|
let v: Option<i64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Int(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"float4" | "float8" => {
|
||||||
|
let v: Option<f64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Float(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"bool" => {
|
||||||
|
let v: Option<bool> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(b) => Value::Bool(b),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"text" | "varchar" | "char" | "bpchar" | "name" => {
|
||||||
|
let v: Option<String> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Try to get as string for other types
|
||||||
|
let v: Option<String> = row.try_get(i).ok().flatten();
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
record.insert(col_name, value);
|
||||||
|
}
|
||||||
|
results.push(Value::Record(record));
|
||||||
|
}
|
||||||
|
Ok(Value::List(results))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.query failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "queryOne") => {
|
||||||
|
let (conn_id, sql) = match (request.args.get(0), request.args.get(1)) {
|
||||||
|
(Some(Value::Int(id)), Some(Value::String(s))) => (*id, s.clone()),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.queryOne requires connection ID and SQL string".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.queryOne: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.query_opt(&sql, &[]) {
|
||||||
|
Ok(Some(row)) => {
|
||||||
|
let mut record = HashMap::new();
|
||||||
|
for (i, col) in row.columns().iter().enumerate() {
|
||||||
|
let col_name = col.name().to_string();
|
||||||
|
let value: Value = match col.type_().name() {
|
||||||
|
"int4" | "int8" | "int2" => {
|
||||||
|
let v: Option<i64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Int(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"float4" | "float8" => {
|
||||||
|
let v: Option<f64> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(n) => Value::Float(n),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"bool" => {
|
||||||
|
let v: Option<bool> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(b) => Value::Bool(b),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"text" | "varchar" | "char" | "bpchar" | "name" => {
|
||||||
|
let v: Option<String> = row.get(i);
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let v: Option<String> = row.try_get(i).ok().flatten();
|
||||||
|
match v {
|
||||||
|
Some(s) => Value::String(s),
|
||||||
|
None => Value::Constructor { name: "None".to_string(), fields: vec![] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
record.insert(col_name, value);
|
||||||
|
}
|
||||||
|
Ok(Value::Constructor {
|
||||||
|
name: "Some".to_string(),
|
||||||
|
fields: vec![Value::Record(record)],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Ok(None) => Ok(Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
}),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.queryOne failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "beginTx") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.beginTx requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.beginTx: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("BEGIN", &[]) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.beginTx failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "commit") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.commit requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.commit: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("COMMIT", &[]) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.commit failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
("Postgres", "rollback") => {
|
||||||
|
let conn_id = match request.args.first() {
|
||||||
|
Some(Value::Int(id)) => *id,
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Postgres.rollback requires a connection ID".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conns = self.pg_connections.borrow_mut();
|
||||||
|
let conn = match conns.get_mut(&conn_id) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err(RuntimeError {
|
||||||
|
message: format!("Postgres.rollback: invalid connection ID {}", conn_id),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
match conn.execute("ROLLBACK", &[]) {
|
||||||
|
Ok(_) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Postgres.rollback failed: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => Err(RuntimeError {
|
_ => Err(RuntimeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Unhandled effect operation: {}.{}",
|
"Unhandled effect operation: {}.{}",
|
||||||
|
|||||||
@@ -1229,7 +1229,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
fn check_function(&mut self, func: &FunctionDecl) {
|
fn check_function(&mut self, func: &FunctionDecl) {
|
||||||
// Validate that all declared effects exist
|
// Validate that all declared effects exist
|
||||||
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test", "Sql"];
|
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test", "Sql", "Postgres"];
|
||||||
for effect in &func.effects {
|
for effect in &func.effects {
|
||||||
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
||||||
let is_defined = self.env.lookup_effect(&effect.name).is_some();
|
let is_defined = self.env.lookup_effect(&effect.name).is_some();
|
||||||
@@ -2010,7 +2010,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Built-in effects are always available
|
// Built-in effects are always available
|
||||||
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test", "Sql"];
|
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process", "Http", "HttpServer", "Test", "Sql", "Postgres"];
|
||||||
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
||||||
|
|
||||||
// Track this effect for inference
|
// Track this effect for inference
|
||||||
|
|||||||
Reference in New Issue
Block a user