From 38fd5b6a92e573679509894d5c54a80512f96384 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 13 Jan 2025 14:36:53 -0700 Subject: [PATCH 001/154] Better logging on test timeout (#8208) I have a theory that the default async test runner is causing weird issues with the HTTP code. I don't know what's going on, but writing a custom unit test runner here appears to make it all work. --- edb/testbase/http.py | 4 +++- edb/tools/test/decorators.py | 8 ++++++++ tests/test_http.py | 36 +++++++++++++++++++++--------------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/edb/testbase/http.py b/edb/testbase/http.py index 52a80af9c2e..4e74eb45cc7 100644 --- a/edb/testbase/http.py +++ b/edb/testbase/http.py @@ -502,7 +502,9 @@ def _http_worker(self): def stop(self): self._http_server.shutdown() if self._http_runner is not None: - self._http_runner.join() + self._http_runner.join(timeout=60) + if self._http_runner.is_alive(): + raise RuntimeError("Mock HTTP server failed to stop") self._http_runner = None def __exit__(self, *exc): diff --git a/edb/tools/test/decorators.py b/edb/tools/test/decorators.py index 626a2f4a2a3..ba128bd2592 100644 --- a/edb/tools/test/decorators.py +++ b/edb/tools/test/decorators.py @@ -22,8 +22,10 @@ import asyncio import functools import unittest +import logging +logger = logging.getLogger("edb.server") skip = unittest.skip @@ -66,9 +68,15 @@ async def wrapper(*args, **kwargs): try: await asyncio.wait_for(test_func(*args, **kwargs), timeout) except asyncio.TimeoutError: + logger.error( + f"Test {test_func} failed due to timeout after {timeout}" + "seconds") raise AssertionError( f"Test failed due to timeout after {timeout} seconds") except asyncio.CancelledError as e: + logger.error( + f"Test {test_func} failed due to timeout after {timeout}" + "seconds", e) raise AssertionError( f"Test failed due to timeout after {timeout} seconds", e) return wrapper diff --git a/tests/test_http.py b/tests/test_http.py index 4ee2276d62f..4e253a8514f 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -17,18 +17,34 @@ # import asyncio +import inspect import json import random +import unittest from edb.server import http from edb.testbase import http as http_tb -from edb.testbase import server as tb -from edb.tools.test import async_timeout -class HttpTest(tb.TestCase): +async def async_timeout(coroutine, timeout=5): + return await asyncio.wait_for(coroutine, timeout=timeout) + + +def run_async(coroutine, timeout=5): + with asyncio.Runner(debug=True) as runner: + runner.run(async_timeout(coroutine, timeout)) + + +class BaseHttpAsyncTest(unittest.TestCase): + def __getattribute__(self, name): + attr = super().__getattribute__(name) + if name.startswith("test_") and inspect.iscoroutinefunction(attr): + return lambda: run_async(attr()) + return attr + + +class HttpTest(BaseHttpAsyncTest): def setUp(self): - super().setUp() self.mock_server = http_tb.MockHttpServer() self.mock_server.start() self.base_url = self.mock_server.get_base_url().rstrip("/") @@ -37,9 +53,7 @@ def tearDown(self): if self.mock_server is not None: self.mock_server.stop() self.mock_server = None - super().tearDown() - @async_timeout(timeout=5) async def test_get(self): async with http.HttpClient(100) as client: example_request = ( @@ -64,7 +78,6 @@ async def test_get(self): self.assertEqual(result.status_code, 200) self.assertEqual(result.json(), {"message": "Hello, world!"}) - @async_timeout(timeout=5) async def test_post(self): async with http.HttpClient(100) as client: example_request = ( @@ -89,7 +102,6 @@ async def test_post(self): result.json(), {"message": f"Hello, world! {random_data}"} ) - @async_timeout(timeout=5) async def test_post_with_headers(self): async with http.HttpClient(100) as client: example_request = ( @@ -117,13 +129,11 @@ async def test_post_with_headers(self): ) self.assertEqual(result.headers["X-Test"], "test!") - @async_timeout(timeout=5) async def test_bad_url(self): async with http.HttpClient(100) as client: with self.assertRaisesRegex(Exception, "Scheme"): await client.get("httpx://uh-oh") - @async_timeout(timeout=5) async def test_immediate_connection_drop(self): """Test handling of a connection that is dropped immediately by the server""" @@ -151,7 +161,6 @@ async def mock_drop_server( server.close() await server.wait_closed() - @async_timeout(timeout=5) async def test_streaming_get_with_no_sse(self): async with http.HttpClient(100) as client: example_request = ( @@ -171,8 +180,7 @@ async def test_streaming_get_with_no_sse(self): self.assertEqual(result.json(), "ok") -class HttpSSETest(tb.TestCase): - @async_timeout(timeout=5) +class HttpSSETest(BaseHttpAsyncTest): async def test_immediate_connection_drop_streaming(self): """Test handling of a connection that is dropped immediately by the server""" @@ -200,7 +208,6 @@ async def mock_drop_server( server.close() await server.wait_closed() - @async_timeout(timeout=5) async def test_sse_with_mock_server_client_close(self): """Since the regular mock server doesn't support SSE, we need to test with a real socket. We handle just enough HTTP to get the job done.""" @@ -277,7 +284,6 @@ async def client_task(): assert is_closed - @async_timeout(timeout=5) async def test_sse_with_mock_server_close(self): """Try to close the server-side stream and see if the client detects an end for the iterator. Note that this is technically not correct SSE: From caff8be4d6b3052336627dc25482774f91e04383 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 13 Jan 2025 15:00:02 -0700 Subject: [PATCH 002/154] Use rustls for http fetches (#8201) @msullivan and I tracked down an issue where HTTP tests were causing crashes on aarch64 runners. The culprit turned out to be `openssl-probe` calling `setenv()` in a multithreaded environment. Switch http to rustls + native certs. We still have the probe dep in Cargo.lock, but as long as we don't call the functions that set envvars, we should be fine. This was the original crashing stack trace -- basically something in asyncio was trying to format an error (illegal seek, we believe) at the same time we were initializing the HTTP code `openssl-probe` was attempting to set some OpenSSL envvars. ``` #0 0x0000ffff805a3e90 in getenv () from /chroot/lib64/libc.so.6 #1 0x0000ffff8059c174 in __dcigettext () from /chroot/lib64/libc.so.6 #2 0x0000ffff805f263c in strerror_r () from /chroot/lib64/libc.so.6 #3 0x0000ffff805f254c in strerror () from /chroot/lib64/libc.so.6 #4 0x00000000005bb76c in PyErr_SetFromErrnoWithFilenameObjects () #5 0x00000000004e4c14 in ?? () #6 0x000000000049f66c in PyObject_VectorcallMethod () #7 0x00000000005d21e4 in ?? () #8 0x00000000005d213c in ?? () #9 0x00000000005d1ed4 in ?? () #10 0x00000000004985ec in _PyObject_MakeTpCall () #11 0x00000000004a7734 in _PyEval_EvalFrameDefault () #12 0x000000000049ccb4 in _PyObject_FastCallDictTstate () ``` --- Cargo.lock | 250 ++++++++++++++++++++++++++++--------------- Cargo.toml | 3 + rust/http/Cargo.toml | 14 ++- tests/test_http.py | 1 - 4 files changed, 182 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd6bcdf3124..045f6cf0ecf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.18" @@ -138,6 +153,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429" dependencies = [ + "brotli", "flate2", "futures-core", "memchr", @@ -231,6 +247,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -419,9 +456,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -658,9 +695,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -930,8 +967,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -961,9 +1000,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1110,33 +1149,18 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -1378,23 +1402,6 @@ dependencies = [ "syn", ] -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.29.0" @@ -1557,8 +1564,7 @@ dependencies = [ [[package]] name = "openssl-probe" version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +source = "git+https://github.com/edgedb/openssl-probe/?rev=e5ed593600d1f8128629565d349682f54b3a8b57#e5ed593600d1f8128629565d349682f54b3a8b57" [[package]] name = "openssl-sys" @@ -1805,6 +1811,58 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.3", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.3", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -1932,9 +1990,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "async-compression", "base64", @@ -1948,25 +2006,27 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", + "tower", "tower-service", "url", "wasm-bindgen", @@ -2049,6 +2109,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2078,12 +2144,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -2098,6 +2177,9 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -2148,9 +2230,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", "core-foundation", @@ -2161,9 +2243,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -2417,27 +2499,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "target-lexicon" version = "0.12.16" @@ -2571,16 +2632,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-openssl" version = "0.6.5" @@ -2633,6 +2684,27 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -2924,6 +2996,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wide" version = "0.7.28" diff --git a/Cargo.toml b/Cargo.toml index 50a653d221e..77659050030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,6 @@ lto = true [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(never)'] } + +[patch.crates-io] +openssl-probe = { git = "https://github.com/edgedb/openssl-probe/", rev = "e5ed593600d1f8128629565d349682f54b3a8b57" } diff --git a/rust/http/Cargo.toml b/rust/http/Cargo.toml index 6ffd0c2bba7..62e84e0e1a8 100644 --- a/rust/http/Cargo.toml +++ b/rust/http/Cargo.toml @@ -17,10 +17,22 @@ tokio.workspace = true pyo3_util.workspace = true tracing.workspace = true -reqwest = { version = "0.12", features = ["gzip", "deflate", "stream"] } scopeguard = "1" eventsource-stream = "0.2.3" +# We want to use rustls to avoid setenv issues w/ OpenSSL and the system certs. As long +# as we don't call `openssl_probe::*init*env*()` functions (functions that call setenv +# in a thread-unsafe way), we should be fine. +# +# More details: https://github.com/edgedb/edgedb/pull/8201 +# +# We add these features: +# - http2: to use HTTP/2 +# - charset: to support charset encoding +# - gzip/deflate/brotli: to support compression +# - stream: to support streaming responses +# - rustls-tls-native-roots: to use the native root certificates (rather than WebPKI) +reqwest = { version = "0.12", default-features = false, features = ["http2", "charset", "gzip", "deflate", "brotli", "stream", "rustls-tls-native-roots"] } futures = "0" [dev-dependencies] diff --git a/tests/test_http.py b/tests/test_http.py index 4e253a8514f..7bc7f4b254e 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -350,7 +350,6 @@ async def client_task(): assert events[1].data == 'Event 2' assert events[2].data == 'Event 3' - client_future = asyncio.create_task(client_task()) async with server: client_future = asyncio.create_task(client_task()) await asyncio.wait_for(client_future, timeout=5.0) From 67f480fc3471356fab4cd78f68a68c8eaa04e9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Mon, 13 Jan 2025 23:04:57 +0100 Subject: [PATCH 003/154] Fix SQL native for queries that are not simple selects (#8205) --- edb/pgsql/resolver/__init__.py | 52 ++++++++++++++++++++++++++++++---- edb/server/compiler/sql.py | 13 ++++++++- tests/test_sql_query.py | 18 ++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/edb/pgsql/resolver/__init__.py b/edb/pgsql/resolver/__init__.py index 334bad5c5d2..2a15e457f6b 100644 --- a/edb/pgsql/resolver/__init__.py +++ b/edb/pgsql/resolver/__init__.py @@ -55,7 +55,7 @@ class ResolvedSQL: def resolve( - query: pgast.Base, + query: pgast.Query | pgast.CopyStmt, schema: s_schema.Schema, options: context.Options, ) -> ResolvedSQL: @@ -77,7 +77,14 @@ def resolve( command.init_external_params(query, ctx) top_level_ctes = command.compile_dml(query, ctx=ctx) - resolved = dispatch.resolve(query, ctx=ctx) + resolved: pgast.Base + if isinstance(query, pgast.Query): + resolved, resolved_table = dispatch.resolve_relation(query, ctx=ctx) + elif isinstance(query, pgast.CopyStmt): + resolved = dispatch.resolve(query, ctx=ctx) + resolved_table = None + else: + raise AssertionError() command.fini_external_params(ctx) @@ -119,15 +126,16 @@ def resolve( if options.include_edgeql_io_format_alternative: edgeql_output_format_ast = copy.copy(resolved) - if isinstance(edgeql_output_format_ast, pgast.SelectStmt): - edgeql_output_format_ast.target_list = [ + if e := as_plain_select(edgeql_output_format_ast, resolved_table, ctx): + e.target_list = [ pgast.ResTarget( val=expr.construct_row_expr( - (rt.val for rt in edgeql_output_format_ast.target_list), + (rt.val for rt in e.target_list), ctx=ctx, ) ) ] + edgeql_output_format_ast = e else: edgeql_output_format_ast = None @@ -137,3 +145,37 @@ def resolve( command_complete_tag=command_complete_tag, params=ctx.query_params, ) + + +def as_plain_select( + query: pgast.Base, + table: Optional[context.Table], + ctx: context.ResolverContextLevel, +) -> Optional[pgast.SelectStmt]: + if not isinstance(query, pgast.Query): + return None + assert table + + if ( + isinstance(query, pgast.SelectStmt) + and not query.op + and not query.values + ): + return query + + table.alias = "t" + return pgast.SelectStmt( + from_clause=[ + pgast.RangeSubselect( + subquery=query, + alias=pgast.Alias(aliasname="t"), + ) + ], + target_list=[ + pgast.ResTarget( + name=f'column{index + 1}', + val=expr.resolve_column_kind(table, c.kind, ctx=ctx), + ) + for index, c in enumerate(table.columns) + ], + ) diff --git a/edb/server/compiler/sql.py b/edb/server/compiler/sql.py index 5486e0d7d7c..8787c8e87f0 100644 --- a/edb/server/compiler/sql.py +++ b/edb/server/compiler/sql.py @@ -354,6 +354,10 @@ def _compile_sql( "SQL prepared statements are not supported" ) + if not isinstance(stmt.query, (pgast.Query, pgast.CopyStmt)): + from edb.pgsql import resolver as pg_resolver + pg_resolver.dispatch._raise_unsupported(stmt.query) + # Translate the underlying query. stmt_resolved, stmt_source, _ = resolve_query( stmt.query, schema, tx_state, opts @@ -438,6 +442,13 @@ def _compile_sql( # just ignore unit.query = "DO $$ BEGIN END $$;" elif isinstance(stmt, (pgast.Query, pgast.CopyStmt)): + if ( + protocol_version != defines.POSTGRES_PROTOCOL + and isinstance(stmt, pgast.CopyStmt) + ): + from edb.pgsql import resolver as pg_resolver + pg_resolver.dispatch._raise_unsupported(stmt) + stmt_resolved, stmt_source, edgeql_fmt_src = resolve_query( stmt, schema, tx_state, opts ) @@ -549,7 +560,7 @@ class ResolverOptionsPartial: def resolve_query( - stmt: pgast.Base, + stmt: pgast.Query | pgast.CopyStmt, schema: s_schema.Schema, tx_state: dbstate.SQLTransactionState, opts: ResolverOptionsPartial, diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index bf799d40f5e..eea47eb17d3 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -2853,6 +2853,24 @@ async def test_sql_native_query_21(self): "SELECT 'test' as a", [{"a": "test"}] ) + async def test_sql_native_query_22(self): + await self.assert_sql_query_result( + "SELECT 1 as a UNION SELECT 2 as a", [{"a": 1}, {"a": 2}] + ) + + async def test_sql_native_query_23(self): + await self.assert_sql_query_result( + "VALUES (1, 2)", [{"column1": 1, "column2": 2}] + ) + + async def test_sql_native_query_24(self): + with self.assertRaisesRegex( + edgedb.errors.UnsupportedFeatureError, 'not supported: COPY' + ): + await self.assert_sql_query_result( + 'COPY "Genre" TO STDOUT', [] + ) + class TestSQLQueryNonTransactional(tb.SQLQueryTestCase): From 73625d4aec10645fb107217416ea57843d8cbf90 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 13 Jan 2025 15:36:57 -0700 Subject: [PATCH 004/154] Bump rust deps to resolve security warnings (#8212) We don't hit any vulnerable code paths in these libraries, but this resolves some outstanding vulnerability warnings. --- Cargo.lock | 308 ++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 2 +- 2 files changed, 284 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 045f6cf0ecf..3d95d9a1d55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,6 +604,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "edb-graphql-parser" version = "0.3.0" @@ -1175,14 +1186,143 @@ dependencies = [ "tracing", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1276,6 +1416,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "log" version = "0.4.22" @@ -1736,9 +1882,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebb0c0cc0de9678e53be9ccf8a2ab53045e6e3a8be03393ceccc5e7396ccb40" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" dependencies = [ "cfg-if", "indoc", @@ -1755,9 +1901,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e3ce69c4ec34476534b490e412b871ba03a82e35604c3dfb95fcb6bfb60c09" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" dependencies = [ "once_cell", "target-lexicon", @@ -1765,9 +1911,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09f311c76b36dfd6dd6f7fa6f9f18e7e46a1c937110d283e80b12ba2468a75" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" dependencies = [ "libc", "pyo3-build-config", @@ -1775,9 +1921,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4f74086536d1e1deaff99ec0387481fb3325c82e4e48be0e75ab3d3fcb487a" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1787,9 +1933,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e77dfeb76b32bbf069144a5ea0a36176ab59c8db9ce28732d0f06f096bbfbc8" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" dependencies = [ "heck", "proc-macro2", @@ -1860,7 +2006,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2139,9 +2285,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.14" +version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ "once_cell", "ring", @@ -2424,6 +2570,12 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "statrs" version = "0.17.1" @@ -2499,6 +2651,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -2590,6 +2753,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2784,12 +2957,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -2846,15 +3013,27 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3159,6 +3338,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyhash" version = "0.5.0" @@ -3174,6 +3365,30 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3195,8 +3410,51 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 77659050030..b5e8e1d5260 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ resolver = "2" [workspace.dependencies] -pyo3 = { version = "0.23.1", features = ["extension-module", "serde", "macros"] } +pyo3 = { version = "0.23", features = ["extension-module", "serde", "macros"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "time", "sync", "net", "io-util"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["registry", "env-filter"] } From 18e9fe016ee2262b81c0344566714a536a1c737e Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 9 Jan 2025 21:47:04 +0000 Subject: [PATCH 005/154] Fix docs build --- edb/protocol/__init__.py | 1 - edb/testbase/protocol/test.py | 3 ++- tests/test_protocol.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/edb/protocol/__init__.py b/edb/protocol/__init__.py index b87f06d1ff1..00397b702d5 100644 --- a/edb/protocol/__init__.py +++ b/edb/protocol/__init__.py @@ -24,7 +24,6 @@ from . import messages from . import render_utils -from .protocol import Connection # NoQA from .messages import * # NoQA diff --git a/edb/testbase/protocol/test.py b/edb/testbase/protocol/test.py index be6f2a9f651..81061363c79 100644 --- a/edb/testbase/protocol/test.py +++ b/edb/testbase/protocol/test.py @@ -22,6 +22,7 @@ from edb.testbase import server from edb.protocol import protocol # type: ignore +from edb.protocol.protocol import Connection class ProtocolTestCase(server.DatabaseTestCase): @@ -29,7 +30,7 @@ class ProtocolTestCase(server.DatabaseTestCase): PARALLELISM_GRANULARITY = 'database' BASE_TEST_CLASS = True - con: protocol.Connection + con: Connection def setUp(self): self.con = self.loop.run_until_complete( diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 014eede8c04..5df0d10d83b 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -26,6 +26,7 @@ from edb.server import args as srv_args from edb.server import compiler from edb import protocol +from edb.protocol.protocol import Connection from edb.testbase import server as tb from edb.testbase import connection as tconn from edb.testbase.protocol.test import ProtocolTestCase @@ -44,7 +45,7 @@ async def _execute( data: bool = False, sql: bool = False, cc: protocol.CommandComplete | None = None, - con: protocol.Connection | None = None, + con: Connection | None = None, input_language: protocol.InputLanguage = protocol.InputLanguage.EDGEQL, ) -> None: exec_args = dict( From d79efea1b45ccbc6e8b90b26c499c65a47d9345b Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 13 Jan 2025 14:55:55 -0800 Subject: [PATCH 006/154] Detect crashed test runners and report and exit (#8209) Probably a better job could be done than this, but this should get us a clean failure instead of a long hang. --- edb/tools/test/mproc_fixes.py | 13 ++++++++++ edb/tools/test/runner.py | 47 +++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/edb/tools/test/mproc_fixes.py b/edb/tools/test/mproc_fixes.py index 505ed3c484b..c045bf69130 100644 --- a/edb/tools/test/mproc_fixes.py +++ b/edb/tools/test/mproc_fixes.py @@ -28,6 +28,7 @@ _orig_pool_worker_handler = None +_orig_pool_join_exited_workers = None logger = logging.getLogger(__name__) @@ -92,9 +93,17 @@ def multiprocessing_worker_handler(*args): worker_process.join(timeout=10) +def join_exited_workers(pool): + # Our use case shouldn't have workers exiting really, so we skip + # doing the joins so that we can detect crashes ourselves in the + # test runner.x + pass + + def patch_multiprocessing(debug: bool): global _orig_pool_worker global _orig_pool_worker_handler + global _orig_pool_join_exited_workers if debug: multiprocessing.util.log_to_stderr(logging.DEBUG) @@ -111,3 +120,7 @@ def patch_multiprocessing(debug: bool): # Allow workers some time to shut down gracefully. _orig_pool_worker_handler = multiprocessing.pool.Pool._handle_workers multiprocessing.pool.Pool._handle_workers = multiprocessing_worker_handler + + _orig_pool_join_exited_workers = ( + multiprocessing.pool.Pool._join_exited_workers) + multiprocessing.pool.Pool._join_exited_workers = join_exited_workers diff --git a/edb/tools/test/runner.py b/edb/tools/test/runner.py index 08889b317ae..21fd87e380e 100644 --- a/edb/tools/test/runner.py +++ b/edb/tools/test/runner.py @@ -166,15 +166,17 @@ def _run(self, test, result): getattr(result, '_moduleSetUpFailed', False)): return + result.annotate_test(test, { + 'py-hash-secret': py_hash_secret, + 'py-random-seed': py_random_seed, + 'runner-pid': os.getpid(), + }) + start = time.monotonic() test.run(result) elapsed = time.monotonic() - start result.record_test_stats(test, {'running-time': elapsed}) - result.annotate_test(test, { - 'py-hash-secret': py_hash_secret, - 'py-random-seed': py_random_seed, - }) result._testRunEntered = False return result @@ -374,6 +376,28 @@ def run(self, result): try: ar.get(timeout=0.1) except multiprocessing.TimeoutError: + # multiprocessing doesn't handle processes + # crashing very well, so we check ourselves + # (having disabled its own child pruning in + # mproc_fixes) + # + # TODO: Should we look into using + # concurrent.futures.ProcessPoolExecutor + # instead? + for p in pool._pool: + if p.exitcode: + tmsg = '' + if isinstance(result, ParallelTextTestResult): + test = result.current_pids.get(p.pid) + tmsg = f' while running {test}' + print( + f"ERROR: Test worker {p.pid} crashed with " + f"exit code {p.exitcode}{tmsg}", + file=sys.stderr, + ) + sys.stderr.flush() + os._exit(1) + if self.stop_requested: break else: @@ -744,6 +768,7 @@ def __init__(self, *, stream, verbosity, warnings, tests, self.warnings = [] self.notImplemented = [] self.currently_running = {} + self.current_pids = {} # An index of all seen warnings to keep track # of repeated warnings. self._warnings = {} @@ -775,7 +800,14 @@ def report_still_running(self): for test, start in self.currently_running.items(): running_for = now - start if running_for > 5.0: - still_running[test] = running_for + key = str(test) + if ( + test in self.test_annotations + and (pid := self.test_annotations[test].get('runner-pid')) + ): + key = f'{key} (pid={pid})' + + still_running[key] = running_for if still_running: self.ren.report_still_running(still_running) @@ -800,6 +832,11 @@ def startTest(self, test): self.currently_running[test] = time.monotonic() self.ren.report_start( test, currently_running=list(self.currently_running)) + if ( + test in self.test_annotations + and (pid := self.test_annotations[test].get('runner-pid')) + ): + self.current_pids[pid] = test def addSuccess(self, test): super().addSuccess(test) From 11bbdbc291826128cca1479c679fb67aa0567389 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 13 Jan 2025 14:56:10 -0800 Subject: [PATCH 007/154] Implict limit for SQL over native (#8206) (#8213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aljaž Mur Eržen --- edb/pgsql/resolver/__init__.py | 18 ++++++++++++++++++ edb/pgsql/resolver/context.py | 3 +++ edb/server/compiler/compiler.py | 1 + edb/server/compiler/sql.py | 6 ++++++ edb/testbase/server.py | 2 ++ tests/test_sql_query.py | 8 ++++++++ 6 files changed, 38 insertions(+) diff --git a/edb/pgsql/resolver/__init__.py b/edb/pgsql/resolver/__init__.py index 2a15e457f6b..3008bc31bf2 100644 --- a/edb/pgsql/resolver/__init__.py +++ b/edb/pgsql/resolver/__init__.py @@ -86,6 +86,9 @@ def resolve( else: raise AssertionError() + if limit := ctx.options.implicit_limit: + resolved = apply_implicit_limit(resolved, limit, resolved_table, ctx) + command.fini_external_params(ctx) if top_level_ctes: @@ -179,3 +182,18 @@ def as_plain_select( for index, c in enumerate(table.columns) ], ) + + +def apply_implicit_limit( + expr: pgast.Base, + limit: int, + table: Optional[context.Table], + ctx: context.ResolverContextLevel, +) -> pgast.Base: + e = as_plain_select(expr, table, ctx) + if not e: + return expr + + if e.limit_count is None: + e.limit_count = pgast.NumericConstant(val=str(limit)) + return e diff --git a/edb/pgsql/resolver/context.py b/edb/pgsql/resolver/context.py index 149c83c473a..4e823c77ef9 100644 --- a/edb/pgsql/resolver/context.py +++ b/edb/pgsql/resolver/context.py @@ -64,6 +64,9 @@ class Options: # DisableNormalization to recompile the query without normalization. normalized_params: List[int] + # Apply a limit to the number of rows in the top-level query + implicit_limit: Optional[int] + @dataclass(kw_only=True) class Scope: diff --git a/edb/server/compiler/compiler.py b/edb/server/compiler/compiler.py index 56e2fd6da53..1206bbcba82 100644 --- a/edb/server/compiler/compiler.py +++ b/edb/server/compiler/compiler.py @@ -2665,6 +2665,7 @@ def compile_sql_as_unit_group( disambiguate_column_names=True, backend_runtime_params=ctx.backend_runtime_params, protocol_version=ctx.protocol_version, + implicit_limit=ctx.implicit_limit, ) qug = dbstate.QueryUnitGroup( diff --git a/edb/server/compiler/sql.py b/edb/server/compiler/sql.py index 8787c8e87f0..3b265387234 100644 --- a/edb/server/compiler/sql.py +++ b/edb/server/compiler/sql.py @@ -80,6 +80,7 @@ def compile_sql( disambiguate_column_names: bool, backend_runtime_params: pg_params.BackendRuntimeParams, protocol_version: defines.ProtocolVersion, + implicit_limit: Optional[int] = None, ) -> List[dbstate.SQLQueryUnit]: def _try( q: str, normalized_params: List[int] @@ -101,6 +102,7 @@ def _try( backend_runtime_params=backend_runtime_params, protocol_version=protocol_version, normalized_params=normalized_params, + implicit_limit=implicit_limit, ) normalized_params = list(source.extra_type_oids()) @@ -207,6 +209,7 @@ def _compile_sql( backend_runtime_params: pg_params.BackendRuntimeParams, protocol_version: defines.ProtocolVersion, normalized_params: List[int], + implicit_limit: Optional[int] = None, ) -> List[dbstate.SQLQueryUnit]: opts = ResolverOptionsPartial( query_str=query_str, @@ -219,6 +222,7 @@ def _compile_sql( ), disambiguate_column_names=disambiguate_column_names, normalized_params=normalized_params, + implicit_limit=implicit_limit, ) # orig_stmts are the statements prior to constant extraction @@ -557,6 +561,7 @@ class ResolverOptionsPartial: include_edgeql_io_format_alternative: Optional[bool] disambiguate_column_names: bool normalized_params: List[int] + implicit_limit: Optional[int] def resolve_query( @@ -606,6 +611,7 @@ def resolve_query( ), disambiguate_column_names=opts.disambiguate_column_names, normalized_params=opts.normalized_params, + implicit_limit=opts.implicit_limit, ) resolved = pg_resolver.resolve(stmt, schema, options) source = pg_codegen.generate(resolved.ast, with_source_map=True) diff --git a/edb/testbase/server.py b/edb/testbase/server.py index e14dc6ae933..ed2f28aabe1 100644 --- a/edb/testbase/server.py +++ b/edb/testbase/server.py @@ -1248,6 +1248,7 @@ async def assert_sql_query_result( query, exp_result, *, + implicit_limit=0, msg=None, sort=None, variables=None, @@ -1263,6 +1264,7 @@ async def assert_sql_query_result( await self.assert_query_result( query, exp_result, + implicit_limit=implicit_limit, msg=msg, sort=sort, variables=variables, diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index eea47eb17d3..ee017d6324f 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -2871,6 +2871,14 @@ async def test_sql_native_query_24(self): 'COPY "Genre" TO STDOUT', [] ) + async def test_sql_native_query_25(self): + # implict limit + await self.assert_sql_query_result( + 'VALUES (1), (2), (3), (4), (5), (6), (7)', + [{'column1': 1}, {'column1': 2}, {'column1': 3}], + implicit_limit=3, + ) + class TestSQLQueryNonTransactional(tb.SQLQueryTestCase): From 6e8aa49a75fc8e083c23802902fc06dbe7d87efb Mon Sep 17 00:00:00 2001 From: Fantix King Date: Tue, 14 Jan 2025 14:18:06 -0500 Subject: [PATCH 008/154] test: bootstrap without testmode for multitenant test (#8216) --- edb/server/args.py | 2 ++ edb/testbase/server.py | 8 +++++++- tests/test_server_ops.py | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/edb/server/args.py b/edb/server/args.py index 820a261fdf3..a20af128f03 100644 --- a/edb/server/args.py +++ b/edb/server/args.py @@ -1552,6 +1552,8 @@ def parse_args(**kwargs: Any): if kwargs['compiler_pool_mode'] is not CompilerPoolMode.MultiTenant: abort("must use --compiler-pool-mode=fixed_multi_tenant " "in multi-tenant mode") + if kwargs['testmode']: + abort("cannot use --testmode in multi-tenant mode") bootstrap_script_text: Optional[str] if kwargs['bootstrap_command_file']: diff --git a/edb/testbase/server.py b/edb/testbase/server.py index ed2f28aabe1..c57a293b245 100644 --- a/edb/testbase/server.py +++ b/edb/testbase/server.py @@ -2394,6 +2394,7 @@ def __init__( extra_args: Optional[List[str]] = None, net_worker_mode: Optional[str] = None, password: Optional[str] = None, + testmode: bool = True, ) -> None: self.bind_addrs = bind_addrs self.auto_shutdown_after = auto_shutdown_after @@ -2431,6 +2432,7 @@ def __init__( self.extra_args = extra_args self.net_worker_mode = net_worker_mode self.password = password + self.testmode = testmode async def wait_for_server_readiness(self, stream: asyncio.StreamReader): while True: @@ -2480,13 +2482,15 @@ async def __aenter__(self): cmd = [ sys.executable, '-m', 'edb.server.main', '--port', 'auto', - '--testmode', '--emit-server-status', f'fd://{status_w.fileno()}', '--compiler-pool-size', str(self.compiler_pool_size), '--tls-cert-mode', str(self.tls_cert_mode), '--jose-key-mode', 'generate', ] + if self.testmode: + cmd.extend(['--testmode']) + if self.compiler_pool_mode is not None: cmd.extend(('--compiler-pool-mode', self.compiler_pool_mode.value)) @@ -2763,6 +2767,7 @@ def start_edgedb_server( default_branch: Optional[str] = None, net_worker_mode: Optional[str] = None, force_new: bool = False, # True for ignoring multitenant config env + testmode: bool = True, ): if (not devmode.is_in_dev_mode() or adjacent_to) and not runstate_dir: if backend_dsn or adjacent_to: @@ -2844,6 +2849,7 @@ def start_edgedb_server( default_branch=default_branch, net_worker_mode=net_worker_mode, password=password, + testmode=testmode, ) diff --git a/tests/test_server_ops.py b/tests/test_server_ops.py index f156bd1dd7d..a3f1958640b 100644 --- a/tests/test_server_ops.py +++ b/tests/test_server_ops.py @@ -1520,6 +1520,7 @@ async def _init_pg_cluster(self, path): runstate_dir=runstate_dir, backend_dsn=f'postgres:///?user=postgres&host={path}', reset_auth=True, + testmode=False, auto_shutdown_after=1, ) as sd: connect_args = { @@ -1576,6 +1577,7 @@ async def test_server_ops_multi_tenant(self): runstate_dir=runstate_dir, multitenant_config=conf_file.name, max_allowed_connections=None, + testmode=False, http_endpoint_security=args.ServerEndpointSecurityMode.Optional, ) async with srv as sd: From 6deae4749c5aaead7c9a24bb519925bbb3d5bc04 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 14 Jan 2025 12:32:38 -0800 Subject: [PATCH 009/154] Implement \(expr)-style string interpolation (#8210) `\(` is currently rejected inside string literals, so there is no compatability issue. Expressions in the `\(expr)` blocks get automatically cast to str. The implement is based on the lexer producing `STRINTERPSTART`, `STRINTERPCONT`, and `STRINTERPEND` tokens that are parsed by the parser. The lexer needs to be a little stateful, unfortunately, to track what kind of opening quote opened the string and to track the nesting level of parenthesis. Fixes #272. --- .../edgeql-parser-python/src/normalize.rs | 2 +- edb/edgeql-parser/src/helpers/strings.rs | 4 +- edb/edgeql-parser/src/parser.rs | 4 + edb/edgeql-parser/src/tokenizer.rs | 78 ++++++++++++++++++- edb/edgeql-parser/src/validation.rs | 4 +- edb/edgeql/ast.py | 10 +++ edb/edgeql/codegen.py | 10 +++ edb/edgeql/compiler/expr.py | 24 ++++++ edb/edgeql/parser/grammar/expressions.py | 34 ++++++++ edb/edgeql/parser/grammar/tokens.py | 12 +++ edb/edgeql/quote.py | 28 +++---- edb/edgeql/tracer.py | 13 ++++ tests/test_edgeql_expressions.py | 24 ++++++ tests/test_edgeql_syntax.py | 3 - 14 files changed, 226 insertions(+), 24 deletions(-) diff --git a/edb/edgeql-parser/edgeql-parser-python/src/normalize.rs b/edb/edgeql-parser/edgeql-parser-python/src/normalize.rs index b9690776a32..9f580588498 100644 --- a/edb/edgeql-parser/edgeql-parser-python/src/normalize.rs +++ b/edb/edgeql-parser/edgeql-parser-python/src/normalize.rs @@ -222,7 +222,7 @@ fn is_operator(token: &Token) -> bool { DecimalConst | FloatConst | IntConst | BigIntConst | BinStr | Parameter | ParameterAndType | Str | BacktickName | Keyword(_) | Ident | Substitution | EOI | Epsilon | StartBlock | StartExtension | StartFragment | StartMigration - | StartSDLDocument => false, + | StartSDLDocument | StrInterpStart | StrInterpCont | StrInterpEnd => false, } } diff --git a/edb/edgeql-parser/src/helpers/strings.rs b/edb/edgeql-parser/src/helpers/strings.rs index 10fe0ec1e91..fccfd1da6b5 100644 --- a/edb/edgeql-parser/src/helpers/strings.rs +++ b/edb/edgeql-parser/src/helpers/strings.rs @@ -73,7 +73,9 @@ pub fn unquote_string(value: &str) -> Result, UnquoteError> { .map_err(UnquoteError)?; Ok(value[msize..value.len() - msize].into()) } else { - Ok(_unquote_string(&value[1..value.len() - 1]) + let end_trim = if value.ends_with("\\(") { 2 } else { 1 }; + + Ok(_unquote_string(&value[1..value.len() - end_trim]) .map_err(UnquoteError)? .into()) } diff --git a/edb/edgeql-parser/src/parser.rs b/edb/edgeql-parser/src/parser.rs index f7306c2a336..905d34381cf 100644 --- a/edb/edgeql-parser/src/parser.rs +++ b/edb/edgeql-parser/src/parser.rs @@ -750,6 +750,10 @@ fn get_token_kind(token_name: &str) -> Kind { "PARAMETERANDTYPE" => ParameterAndType, "SUBSTITUTION" => Substitution, + "STRINTERPSTART" => StrInterpStart, + "STRINTERPCONT" => StrInterpCont, + "STRINTERPEND" => StrInterpEnd, + _ => { let mut token_name = token_name.to_lowercase(); diff --git a/edb/edgeql-parser/src/tokenizer.rs b/edb/edgeql-parser/src/tokenizer.rs index 4d8bf5be787..289a24e5884 100644 --- a/edb/edgeql-parser/src/tokenizer.rs +++ b/edb/edgeql-parser/src/tokenizer.rs @@ -114,8 +114,13 @@ pub enum Kind { FloatConst, IntConst, BigIntConst, - BinStr, // b"xx", b'xx' - Str, // "xx", 'xx', r"xx", r'xx', $$xx$$ + BinStr, // b"xx", b'xx' + Str, // "xx", 'xx', r"xx", r'xx', $$xx$$ + + StrInterpStart, // "xx\(, 'xx\( + StrInterpCont, // )xx\( + StrInterpEnd, // )xx", )xx' + BacktickName, // `xx` Substitution, // \(name) @@ -147,6 +152,15 @@ pub struct Tokenizer<'a> { dot: bool, next_state: Option<(usize, TokenStub<'a>, usize, Pos, Pos)>, keyword_buf: String, + // We maintain a stack of the starting string characters and + // parentheses nesting level for all our open string + // interpolations, since we need to match the correct one when + // closing them. + str_interp_stack: Vec<(String, usize)>, + // The number of currently open parentheses. If we see a close + // paren when there are no open parens *and* we are inside a + // string inerpolation, we close it. + open_parens: usize, } #[derive(Clone, Debug, PartialEq)] @@ -196,6 +210,8 @@ impl<'a> Tokenizer<'a> { // Current max keyword length is 10, but we're reserving some // space keyword_buf: String::with_capacity(MAX_KEYWORD_LENGTH), + str_interp_stack: Vec::new(), + open_parens: 0, }; me.skip_whitespace(); me @@ -212,6 +228,9 @@ impl<'a> Tokenizer<'a> { dot: false, next_state: None, keyword_buf: String::with_capacity(MAX_KEYWORD_LENGTH), + // XXX: If we are in the middle of an interpolated string we will have trouble + str_interp_stack: Vec::new(), + open_parens: 0, }; me.skip_whitespace(); me @@ -240,6 +259,8 @@ impl<'a> Tokenizer<'a> { } fn read_token(&mut self) -> Option, Pos), Error>> { + use self::Kind::*; + // This quickly resets the stream one token back // (the most common reset that used quite often) if let Some((at, tok, off, end, next)) = self.next_state { @@ -256,6 +277,25 @@ impl<'a> Tokenizer<'a> { Err(e) => return Some(Err(e)), }; + match kind { + StrInterpStart => { + let start = self.buf[self.off..].chars().next()?; + self.str_interp_stack.push((start.into(), self.open_parens)); + } + StrInterpEnd => { + self.str_interp_stack.pop(); + } + OpenParen => { + self.open_parens += 1; + } + CloseParen => { + if self.open_parens > 0 { + self.open_parens -= 1; + } + } + _ => {} + } + // note we may want to get rid of "update_position" here as it's // faster to update 'as you go', but this is easier to get right first self.update_position(len); @@ -387,7 +427,12 @@ impl<'a> Tokenizer<'a> { '=' => Ok((Eq, 1)), ',' => Ok((Comma, 1)), '(' => Ok((OpenParen, 1)), - ')' => Ok((CloseParen, 1)), + ')' => match self.str_interp_stack.last() { + Some((delim, paren_count)) if *paren_count == self.open_parens => { + self.parse_string_interp_cont(delim) + } + _ => Ok((CloseParen, 1)), + }, '[' => Ok((OpenBracket, 1)), ']' => Ok((CloseBracket, 1)), '{' => Ok((OpenBrace, 1)), @@ -621,7 +666,7 @@ impl<'a> Tokenizer<'a> { } fn parse_string( - &mut self, + &self, quote_off: usize, raw: bool, binary: bool, @@ -652,6 +697,7 @@ impl<'a> Tokenizer<'a> { while let Some((idx, c)) = iter.next() { match c { '\\' if !raw => match iter.next() { + Some((idx, '(')) => return Ok((Kind::StrInterpStart, quote_off + idx + 1)), // skip any next char, even quote Some((_, _)) => continue, None => break, @@ -667,6 +713,30 @@ impl<'a> Tokenizer<'a> { ))); } + fn parse_string_interp_cont(&self, end: &str) -> Result<(Kind, usize), Error> { + let quote_off = 1; + let mut iter = self.buf[self.off + quote_off..].char_indices(); + + while let Some((idx, c)) = iter.next() { + match c { + '\\' => match iter.next() { + Some((idx, '(')) => return Ok((Kind::StrInterpCont, quote_off + idx + 1)), + // skip any next char, even quote + Some((_, _)) => continue, + None => break, + }, + _ if self.buf[self.off + quote_off + idx..].starts_with(end) => { + return Ok((Kind::StrInterpEnd, quote_off + idx + end.len())) + } + _ => check_prohibited(c, true)?, + } + } + return Err(Error::new(format_args!( + "unterminated string with interpolations, quoted by `{}`", + end, + ))); + } + fn parse_number(&mut self) -> Result<(Kind, usize), Error> { #[derive(PartialEq, PartialOrd)] enum Break { diff --git a/edb/edgeql-parser/src/validation.rs b/edb/edgeql-parser/src/validation.rs index b1bd57f1228..d2644dc1d71 100644 --- a/edb/edgeql-parser/src/validation.rs +++ b/edb/edgeql-parser/src/validation.rs @@ -206,7 +206,9 @@ pub fn parse_value(token: &Token) -> Result, String> { return unquote_bytes(text).map(Value::Bytes).map(Some); } - Str => unquote_string(text).map_err(|s| s.to_string())?.to_string(), + Str | StrInterpStart | StrInterpEnd | StrInterpCont => { + unquote_string(text).map_err(|s| s.to_string())?.to_string() + } BacktickName => text[1..text.len() - 1].replace("``", "`"), Ident | Keyword(_) => text.to_string(), Substitution => text[2..text.len() - 1].to_string(), diff --git a/edb/edgeql/ast.py b/edb/edgeql/ast.py index 2be8df29688..4c7bc4d3cad 100644 --- a/edb/edgeql/ast.py +++ b/edb/edgeql/ast.py @@ -251,6 +251,16 @@ class FunctionCall(Expr): window: typing.Optional[WindowSpec] = None +class StrInterpFragment(Base): + expr: Expr + suffix: str + + +class StrInterp(Expr): + prefix: str + interpolations: list[StrInterpFragment] + + class BaseConstant(Expr): """Constant (a literal value).""" __abstract_node__ = True diff --git a/edb/edgeql/codegen.py b/edb/edgeql/codegen.py index 6e48e657540..c131325c776 100644 --- a/edb/edgeql/codegen.py +++ b/edb/edgeql/codegen.py @@ -455,6 +455,16 @@ def visit_GlobalExpr(self, node: qlast.GlobalExpr) -> None: self._write_keywords('GLOBAL ') self.visit(node.name) + def visit_StrInterp(self, node: qlast.StrInterp) -> None: + self.write("'") + self.write(edgeql_quote.escape_string(node.prefix)) + for fragment in node.interpolations: + self.write("\\(") + self.visit(fragment.expr) + self.write(")") + self.write(edgeql_quote.escape_string(fragment.suffix)) + self.write("'") + def visit_UnaryOp(self, node: qlast.UnaryOp) -> None: op = str(node.op).upper() self.write(op) diff --git a/edb/edgeql/compiler/expr.py b/edb/edgeql/compiler/expr.py index ea89bf2b93e..30da0b9f1af 100644 --- a/edb/edgeql/compiler/expr.py +++ b/edb/edgeql/compiler/expr.py @@ -160,6 +160,30 @@ def compile_IsOp(expr: qlast.IsOp, *, ctx: context.ContextLevel) -> irast.Set: return setgen.ensure_set(op_node, ctx=ctx) +@dispatch.compile.register +def compile_StrInterp( + expr: qlast.StrInterp, *, ctx: context.ContextLevel +) -> irast.Set: + strs: list[qlast.Expr] = [] + strs.append(qlast.Constant.string(expr.prefix)) + + str_type = qlast.TypeName( + maintype=qlast.ObjectRef(module='__std__', name='str') + ) + for fragment in expr.interpolations: + strs.append(qlast.TypeCast( + expr=fragment.expr, type=str_type + )) + strs.append(qlast.Constant.string(fragment.suffix)) + + call = qlast.FunctionCall( + func=('__std__', 'array_join'), + args=[qlast.Array(elements=strs), qlast.Constant.string('')], + ) + + return dispatch.compile(call, ctx=ctx) + + @dispatch.compile.register(qlast.DetachedExpr) def compile_DetachedExpr( expr: qlast.DetachedExpr, diff --git a/edb/edgeql/parser/grammar/expressions.py b/edb/edgeql/parser/grammar/expressions.py index b02cc54c581..d486980c4c0 100644 --- a/edb/edgeql/parser/grammar/expressions.py +++ b/edb/edgeql/parser/grammar/expressions.py @@ -1182,6 +1182,10 @@ def reduce_FreeShape(self, *kids): def reduce_Constant(self, *kids): pass + @parsing.inline(0) + def reduce_StringInterpolation(self, *kids): + pass + def reduce_DUNDERSOURCE(self, *kids): self.val = qlast.Path(steps=[qlast.SpecialAnchor(name='__source__')]) @@ -1605,6 +1609,36 @@ def reduce_BaseBytesConstant(self, *kids): pass +class StringInterpolationTail(Nonterm): + def reduce_Expr_STRINTERPEND(self, *kids): + expr, lit = kids + self.val = qlast.StrInterp( + prefix='', + interpolations=[ + qlast.StrInterpFragment(expr=expr.val, suffix=lit.clean_value), + ] + ) + + def reduce_Expr_STRINTERPCONT_StringInterpolationTail(self, *kids): + expr, lit, tail = kids + self.val = tail.val + self.val.interpolations.append( + qlast.StrInterpFragment(expr=expr.val, suffix=lit.clean_value) + ) + + +class StringInterpolation(Nonterm): + def reduce_STRINTERPSTART_StringInterpolationTail(self, *kids): + # We produce somewhat malformed StrInterp values out of + # StringInterpolationTail, for convenience and efficiency, and + # fix them up here. + # (In particular, we put the interpolations in backward.) + lit, tail = kids + self.val = tail.val + self.val.prefix = lit.clean_value + self.val.interpolations.reverse() + + class BaseNumberConstant(Nonterm): def reduce_ICONST(self, *kids): self.val = qlast.Constant( diff --git a/edb/edgeql/parser/grammar/tokens.py b/edb/edgeql/parser/grammar/tokens.py index 3ac0dec63dc..d14bdce26ad 100644 --- a/edb/edgeql/parser/grammar/tokens.py +++ b/edb/edgeql/parser/grammar/tokens.py @@ -67,6 +67,18 @@ class T_STARTSDLDOCUMENT(GrammarToken): pass +class T_STRINTERPSTART(GrammarToken): + pass + + +class T_STRINTERPCONT(GrammarToken): + pass + + +class T_STRINTERPEND(GrammarToken): + pass + + class T_DOT(Token, lextoken='.'): pass diff --git a/edb/edgeql/quote.py b/edb/edgeql/quote.py index 92e8930c314..22dd2360d8d 100644 --- a/edb/edgeql/quote.py +++ b/edb/edgeql/quote.py @@ -35,25 +35,25 @@ ''') -def quote_literal(string: str) -> str: +def escape_string(s: str) -> str: + # characters escaped according to + # https://www.edgedb.com/docs/reference/edgeql/lexical#strings + result = s - def escape_string(s: str) -> str: - # characters escaped according to - # https://www.edgedb.com/docs/reference/edgeql/lexical#strings - result = s + # escape backslash first + result = result.replace('\\', '\\\\') - # escape backslash first - result = result.replace('\\', '\\\\') + result = result.replace('\'', '\\\'') + result = result.replace('\b', '\\b') + result = result.replace('\f', '\\f') + result = result.replace('\n', '\\n') + result = result.replace('\r', '\\r') + result = result.replace('\t', '\\t') - result = result.replace('\'', '\\\'') - result = result.replace('\b', '\\b') - result = result.replace('\f', '\\f') - result = result.replace('\n', '\\n') - result = result.replace('\r', '\\r') - result = result.replace('\t', '\\t') + return result - return result +def quote_literal(string: str) -> str: return "'" + escape_string(string) + "'" diff --git a/edb/edgeql/tracer.py b/edb/edgeql/tracer.py index b0a7963a75a..c2cc11453be 100644 --- a/edb/edgeql/tracer.py +++ b/edb/edgeql/tracer.py @@ -628,6 +628,19 @@ def trace_Array(node: qlast.Array, *, ctx: TracerContext) -> None: trace(el, ctx=ctx) +@trace.register +def trace_StrInterpFragment( + node: qlast.StrInterpFragment, *, ctx: TracerContext +) -> None: + trace(node.expr, ctx=ctx) + + +@trace.register +def trace_StrInterp(node: qlast.StrInterp, *, ctx: TracerContext) -> None: + for el in node.interpolations: + trace(el, ctx=ctx) + + @trace.register def trace_Set(node: qlast.Set, *, ctx: TracerContext) -> None: for el in node.elements: diff --git a/tests/test_edgeql_expressions.py b/tests/test_edgeql_expressions.py index 63d5ffeb27a..0e78de2d8be 100644 --- a/tests/test_edgeql_expressions.py +++ b/tests/test_edgeql_expressions.py @@ -10612,3 +10612,27 @@ async def test_edgeql_expr_with_module_08(self): "Foo' does not exist", ): await self.con.execute(query) + + async def test_edgeql_expr_str_interpolation_01(self): + await self.assert_query_result( + r''' + select "1 + 1 = \(1 + 1)" + ''', + ['1 + 1 = 2'], + ) + + await self.assert_query_result( + r''' + select ("1 + 1 = \(1 + 1)") + ''', + ['1 + 1 = 2'], + ) + + # Have some more fun. Nest it a bit. + await self.assert_query_result( + r'''select "asdf \(str_reverse("1234") ++ +"[\(sum({1,2,3}))]")! count(User)=\ +\( +count(User))" ++ "!";''', + ['asdf 4321[6]! count(User)=0!'], + ) diff --git a/tests/test_edgeql_syntax.py b/tests/test_edgeql_syntax.py index 6474694008e..2724e68709c 100644 --- a/tests/test_edgeql_syntax.py +++ b/tests/test_edgeql_syntax.py @@ -474,9 +474,6 @@ def test_edgeql_syntax_constants_40(self): SELECT "\x1F\x01\x8F\x6e"; """ - @tb.must_fail(errors.EdgeQLSyntaxError, - r"invalid string literal: invalid escape sequence '\\\('", - line=2, col=16) def test_edgeql_syntax_constants_41(self): r""" SELECT 'aaa \(aaa) bbb'; From b890665e7546e6e8ea5f227119d18f559e8c5917 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Tue, 14 Jan 2025 13:57:21 -0700 Subject: [PATCH 010/154] Monitor open FDs (#8217) Add an FD monitor for servers. This is potentially something we can do more efficiently in Rust at a later date, but a Python implementation will allow for an easier backport to 5.6. We use a new shortcut available in modern Linux kernels to avoid counting directory entries (os.stat's size == # of open FDs). Threads are used to simplify the implementation. --- edb/server/metrics.py | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/edb/server/metrics.py b/edb/server/metrics.py index 9768e1778ec..b2a72ade09d 100644 --- a/edb/server/metrics.py +++ b/edb/server/metrics.py @@ -16,6 +16,8 @@ # limitations under the License. # +import os +import sys from edb.common import prometheus as prom @@ -262,3 +264,77 @@ 'Total number of tenants the server failed to reload.', labels=("tenant",), ) + +if os.name == 'posix' and (sys.platform == 'linux' or sys.platform == 'darwin'): + open_fds = registry.new_gauge( + 'open_fds', + 'Number of open file descriptors.', + ) + + max_open_fds = registry.new_gauge( + 'max_open_fds', + 'Maximum number of open file descriptors.', + ) + +# Implement a function that monitors the number of open file descriptors +# and updates the metrics accordingly. This will be replaced with a more +# efficient implementation in Rust at a later date. + + +def monitor_open_fds_linux(): + import time + while True: + max_open_fds.set(os.sysconf('SC_OPEN_MAX')) + # To get the current number of open files, stat /proc/self/fd/ + # and get the size. If zero, count the number of entries in the + # directory. + # + # This is supported in modern Linux kernels. + # https://github.com/torvalds/linux/commit/f1f1f2569901ec5b9d425f2e91c09a0e320768f3 + try: + st = os.stat('/proc/self/fd/') + if st.st_size == 0: + open_fds.set(len(os.listdir('/proc/self/fd/'))) + else: + open_fds.set(st.st_size) + except Exception: + open_fds.set(-1) + + time.sleep(30) + + +def monitor_open_fds_macos(): + import time + while True: + max_open_fds.set(os.sysconf('SC_OPEN_MAX')) + # Iterate the contents of /dev/fd to list all entries. + # We assume that MacOS isn't going to be running a large installation + # of EdgeDB on a single machine. + try: + open_fds.set(len(os.listdir('/dev/fd'))) + except Exception: + open_fds.set(-1) + + time.sleep(30) + + +def start_monitoring_open_fds(): + import threading + + # Supported only on Linux and macOS. + if os.name == 'posix': + if sys.platform == 'darwin': + threading.Thread( + target=monitor_open_fds_macos, + name='open_fds_monitor', + daemon=True + ).start() + elif sys.platform == 'linux': + threading.Thread( + target=monitor_open_fds_linux, + name='open_fds_monitor', + daemon=True + ).start() + + +start_monitoring_open_fds() From 59524865edaa6724136bcaa90a54de8f060a81e4 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Tue, 14 Jan 2025 14:38:51 -0700 Subject: [PATCH 011/154] Add a server_notice callback during handshake (#8202) @elprans reported an unexpected notice message in the connected state. It's not clear why this is showing up, so let's add proper handling for it and log it. ``` WARNING 1 - 2025-01-10T19:11:09.185 edb.server: [pgrust::handshake::client_state_machine] Unexpected message 78 (length 243) received in Connected state ``` Notice messages should not show up during the early handshake, but we may as well route them the same way we route errors "just in case". --- rust/pgrust/src/errors/mod.rs | 39 ++++++++++++++++++- .../src/handshake/client_state_machine.rs | 24 +++++++++--- rust/pgrust/src/python.rs | 5 +++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/rust/pgrust/src/errors/mod.rs b/rust/pgrust/src/errors/mod.rs index a414b2ee112..af4b213b942 100644 --- a/rust/pgrust/src/errors/mod.rs +++ b/rust/pgrust/src/errors/mod.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, str::FromStr}; pub mod edgedb; -use crate::protocol::postgres::data::ErrorResponse; +use crate::protocol::postgres::data::{ErrorResponse, NoticeResponse}; #[macro_export] macro_rules! pg_error_class { @@ -438,6 +438,43 @@ impl From> for PgServerError { } } +impl From> for PgServerError { + fn from(error: NoticeResponse) -> Self { + let mut code = String::new(); + let mut message = String::new(); + let mut extra = HashMap::new(); + let mut severity = PgErrorSeverity::Error; + + for field in error.fields() { + let value = field.value().to_string_lossy().into_owned(); + match PgServerErrorField::try_from(field.ntype()) { + Ok(PgServerErrorField::Code) => code = value, + Ok(PgServerErrorField::Message) => message = value, + Ok(PgServerErrorField::SeverityNonLocalized) => { + severity = PgErrorSeverity::from_str(&value).unwrap_or_default() + } + Ok(field_type) => { + extra.insert(field_type, value); + } + Err(_) => {} + } + } + + // It's very unlikely the server will give us a non-five-character code + let code = match PgError::from_str(&code) { + Ok(code) => code, + Err(_) => PgError::Other(*b"?????"), + }; + + PgServerError { + code, + severity, + message, + extra, + } + } +} + /// Enum representing the field types in ErrorResponse and NoticeResponse messages. /// /// See diff --git a/rust/pgrust/src/handshake/client_state_machine.rs b/rust/pgrust/src/handshake/client_state_machine.rs index e0fa6583ea0..7b7b0dc3cf5 100644 --- a/rust/pgrust/src/handshake/client_state_machine.rs +++ b/rust/pgrust/src/handshake/client_state_machine.rs @@ -2,14 +2,15 @@ use super::ConnectionSslRequirement; use crate::{ connection::{invalid_state, ConnectionError, Credentials, SslError}, errors::PgServerError, - protocol::{ - postgres::data::{ + protocol::postgres::{ + builder, + data::{ AuthenticationCleartextPassword, AuthenticationMD5Password, AuthenticationMessage, AuthenticationOk, AuthenticationSASL, AuthenticationSASLContinue, - AuthenticationSASLFinal, BackendKeyData, ErrorResponse, Message, ParameterStatus, - ReadyForQuery, SSLResponse, + AuthenticationSASLFinal, BackendKeyData, ErrorResponse, Message, NoticeResponse, + ParameterStatus, ReadyForQuery, SSLResponse, }, - postgres::{builder, FrontendBuilder, InitialBuilder}, + FrontendBuilder, InitialBuilder, }, }; use base64::Engine; @@ -93,6 +94,7 @@ pub trait ConnectionStateUpdate: ConnectionStateSend { fn cancellation_key(&mut self, pid: i32, key: i32) {} fn state_changed(&mut self, state: ConnectionStateType) {} fn server_error(&mut self, error: &PgServerError) {} + fn server_notice(&mut self, notice: &PgServerError) {} fn auth(&mut self, auth: AuthType) {} } @@ -248,6 +250,10 @@ impl ConnectionState { password: &credentials.password, }.into())?; }, + (NoticeResponse as notice) => { + let err = PgServerError::from(notice); + update.server_notice(&err); + }, (ErrorResponse as error) => { self.0 = Error; let err = PgServerError::from(error); @@ -282,6 +288,10 @@ impl ConnectionState { (AuthenticationMessage as auth) => { trace!("SCRAM Unknown auth message: {}", auth.status()) }, + (NoticeResponse as notice) => { + let err = PgServerError::from(notice); + update.server_notice(&err); + }, (ErrorResponse as error) => { self.0 = Error; let err = PgServerError::from(error); @@ -309,6 +319,10 @@ impl ConnectionState { self.0 = Ready; update.state_changed(ConnectionStateType::Ready); }, + (NoticeResponse as notice) => { + let err = PgServerError::from(notice); + update.server_notice(&err); + }, (ErrorResponse as error) => { self.0 = Error; let err = PgServerError::from(error); diff --git a/rust/pgrust/src/python.rs b/rust/pgrust/src/python.rs index 6cb61da2bc1..b3e3f1c8a48 100644 --- a/rust/pgrust/src/python.rs +++ b/rust/pgrust/src/python.rs @@ -24,6 +24,7 @@ use pyo3::{ }; use std::collections::HashMap; use std::{borrow::Cow, path::Path}; +use tracing::warn; #[derive(Clone, Copy, PartialEq, Eq)] #[pyclass(eq, eq_int)] @@ -476,6 +477,10 @@ impl ConnectionStateUpdate for PyConnectionStateUpdate { }); } + fn server_notice(&mut self, notice: &PgServerError) { + warn!("Unexpected server notice during handshake: {:?}", notice); + } + fn server_error(&mut self, error: &PgServerError) { Python::with_gil(|py| { let mut fields = vec![]; From 728619a59893a719a8d5c20e0bda5eeeecae72f5 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 14 Jan 2025 16:51:34 -0500 Subject: [PATCH 012/154] Add tests for grouping free objects, pointers with iterators and volatiles. (#8194) The previous attempt (#8094) was reverted in #8117 to fix flaking tests. This PR re-introduces the tests, but using `tb.bag` to avoid checking list ordering. --- tests/test_edgeql_group.py | 3263 +++++++++++++++++++++++++++++++----- 1 file changed, 2817 insertions(+), 446 deletions(-) diff --git a/tests/test_edgeql_group.py b/tests/test_edgeql_group.py index bfb42bf4b52..48081910cf3 100644 --- a/tests/test_edgeql_group.py +++ b/tests/test_edgeql_group.py @@ -510,582 +510,2953 @@ async def test_edgeql_group_grouping_sets_02(self): ] ) - async def test_edgeql_group_duplicate_rejected_01(self): - async with self.assertRaisesRegexTx( - edgedb.QueryError, - "used directly in the BY clause", - ): - await self.con.execute(''' - group Card { name } - using element := .cost - by cube(.element, element) - ''') - - async def test_edgeql_group_duplicate_rejected_02(self): - async with self.assertRaisesRegexTx( - edgedb.QueryError, - "BY clause cannot refer to link property and object property with " - "the same name", - ): - await self.con.execute(''' - WITH MODULE cards - SELECT Card { - invalid := ( - GROUP .avatar - BY @text, .text - ) - } - ''') - - async with self.assertRaisesRegexTx( - edgedb.QueryError, - "BY clause cannot refer to link property and object property with " - "the same name", - ): - await self.con.execute(''' - WITH MODULE cards - SELECT Card { - invalid := ( - GROUP .avatar - BY .text, @text - ) - } - ''') - - async def test_edgeql_group_for_01(self): + async def test_edgeql_group_free_object_01(self): await self.assert_query_result( - r''' - WITH MODULE cards - FOR g in (GROUP Card BY .element) UNION ( - WITH U := g.elements, - SELECT U { - name, - cost_ratio := .cost / math::mean(g.elements.cost) - }); + ''' + group {a := 1, b := 2} by .a;; ''', tb.bag([ - {"cost_ratio": 0.42857142857142855, "name": "Sprite"}, - {"cost_ratio": 0.8571428571428571, "name": "Giant eagle"}, - {"cost_ratio": 1.7142857142857142, "name": "Djinn"}, - {"cost_ratio": 0.5, "name": "Dwarf"}, - {"cost_ratio": 1.5, "name": "Golem"}, - {"cost_ratio": 0.3333333333333333, "name": "Imp"}, - {"cost_ratio": 1.6666666666666667, "name": "Dragon"}, - {"cost_ratio": 0.8, "name": "Bog monster"}, - {"cost_ratio": 1.2, "name": "Giant turtle"} + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': 2}, + ]), + }, ]) ) - async def test_edgeql_group_simple_old_01(self): + async def test_edgeql_group_free_object_02(self): await self.assert_query_result( - r''' - for g in (group User by .name) - union count(g.elements. tuple ; + }; + insert tup { tup := {(1, 1), (1, 2), (1, 1), (2, 1)} }; + ''') + + await self.assert_query_result( + ''' + with X := tup.tup, + group X using z := X by z; + ''', + tb.bag([ + {"elements": [[1, 2]], "key": {"z": [1, 2]}}, + {"elements": [[2, 1]], "key": {"z": [2, 1]}}, + {"elements": tb.bag([[1, 1], [1, 1]]), "key": {"z": [1, 1]}} + ]) + ) + + async def test_edgeql_group_tuple_02(self): + await self.assert_query_result( + ''' + with X := {(1, 1), (1, 2), (1, 1), (2, 1)}, + group X using z := X by z; + ''', + tb.bag([ + {"elements": [[1, 2]], "key": {"z": [1, 2]}}, + {"elements": [[2, 1]], "key": {"z": [2, 1]}}, + {"elements": tb.bag([[1, 1], [1, 1]]), "key": {"z": [1, 1]}} + ]) + ) + + async def test_edgeql_group_semijoin_group_01(self): + await self.assert_query_result( + ''' + with module cards + group ( + select (group Card{name, cost} by .element) + order by .key.element limit 1 + ).elements by .cost; + ''', + tb.bag([ + { + "elements": [{"cost": 1, "name": "Sprite"}], + "grouping": ["cost"], + "key": {"cost": 1} + }, + { + "elements": [{"cost": 2, "name": "Giant eagle"}], + "grouping": ["cost"], + "key": {"cost": 2} + }, + { + "elements": [{"cost": 4, "name": "Djinn"}], + "grouping": ["cost"], + "key": {"cost": 4} + } + ]) + ) + + async def test_edgeql_group_simple_agg_01(self): + await self.assert_query_result( + r''' + with module cards + select (group Card by .element) { + el := .key.element, cs := array_agg(.elements) + }; + ''', + tb.bag([ + {'el': "Water", 'cs': [{'id': str}] * 2}, + {'el': "Fire", 'cs': [{'id': str}] * 2}, + {'el': "Earth", 'cs': [{'id': str}] * 2}, + {'el': "Air", 'cs': [{'id': str}] * 3}, + ]), + ) + + async def test_edgeql_group_simple_agg_02(self): + await self.assert_query_result( + r''' + with module cards + select (group Card by .element) { + el := .key.element, cs := array_agg(.elements { name }) + }; + ''', + tb.bag([ + { + "cs": tb.bag( + [{"name": "Bog monster"}, {"name": "Giant turtle"}]), + "el": "Water" + }, + { + "cs": tb.bag([{"name": "Imp"}, {"name": "Dragon"}]), + "el": "Fire", + }, + { + "cs": tb.bag([{"name": "Dwarf"}, {"name": "Golem"}]), + "el": "Earth", + }, + { + "cs": tb.bag([ + {"name": "Sprite"}, + {"name": "Giant eagle"}, + {"name": "Djinn"} + ]), + "el": "Air", + } + ]) + ) + + async def test_edgeql_group_agg_multi_01(self): + await self.assert_query_result( + ''' + with module cards + for g in (group Card BY .element) union ( + array_agg(g.elements.name ++ {"!", "?"}) + ); + ''', + tb.bag([ + {"Bog monster!", "Bog monster?", + "Giant turtle!", "Giant turtle?"}, + {"Imp!", "Imp?", "Dragon!", "Dragon?"}, + {"Dwarf!", "Dwarf?", "Golem!", "Golem?"}, + {"Sprite!", "Sprite?", "Giant eagle!", + "Giant eagle?", "Djinn!", "Djinn?"} + ]) + ) + + async def test_edgeql_group_agg_multi_02(self): + await self.assert_query_result( + ''' + with module cards + for g in (group Card BY .element) union ( + count((Award { multi z := g.elements.name }.z)) + ); ''', + tb.bag([6, 6, 6, 9]), + ) + + async def test_edgeql_group_agg_multi_03(self): + await self.assert_query_result( + ''' + for g in (group BooleanTest by .val) union ( + array_agg(g.elements.tags) + ); + ''', + tb.bag([ + ["red"], + [], + tb.bag(["red", "green"]), + tb.bag(["red", "black"]), + ]), + ) + + async def test_edgeql_group_agg_grouping_01(self): + # Something about this previously triggered a postgres ISE + # that we had to work around. + await self.assert_query_result( + ''' + select (group cards::Card + using awd_size := count(.awards) + by awd_size, .element) { grouping }; + ''', + [{"grouping": ["awd_size", "element"]}] * 6, + ) + + async def test_edgeql_trivial_grouping_01(self): + await self.assert_query_result( + ''' + group 0 using x := 0 by cube(x) + ''', + tb.bag([ + {"elements": [0], "grouping": [], "key": {"x": None}}, + {"elements": [0], "grouping": ["x"], "key": {"x": 0}} + ]), + ) + + async def test_edgeql_group_binding_01(self): + await self.assert_query_result( + ''' + with GR := (group cards::Card BY .element) + select GR { + multi elements := ( + with els := .elements + select els {name} + ) + }; + ''', + tb.bag([ + { + "elements": tb.bag( + [{"name": "Bog monster"}, {"name": "Giant turtle"}]), + }, + { + "elements": tb.bag([{"name": "Imp"}, {"name": "Dragon"}]), + }, + { + "elements": tb.bag([{"name": "Dwarf"}, {"name": "Golem"}]), + }, + { + "elements": tb.bag([ + {"name": "Sprite"}, + {"name": "Giant eagle"}, + {"name": "Djinn"} + ]), + } + ]) + ) + + async def test_edgeql_group_binding_free_object_01(self): + await self.assert_query_result( + ''' + with X := {a := 1, b := 2} + group X { a, b } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': 2}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_free_object_02(self): + await self.assert_query_result( + ''' + with X := {a := 1, b := {2, 3, 4}, c := { d := 5 } } + group X { a, b, c: {*} } using d := .c.d by d; + ''', + tb.bag([ + { + 'key': {'d': 5}, + 'grouping': {'d'}, + 'elements': tb.bag([ + {'a': 1, 'b': [2, 3, 4], 'c': {'d': 5}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_volatile_01(self): + await self.assert_query_result( + ''' + with N := random() + group cards::User { name } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice'}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob'}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol'}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave'}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_volatile_02(self): + await self.assert_query_result( + ''' + with N := random() + group { a := 1 } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_03(self): + await self.assert_query_result( + ''' + with N := random() + group cards::User { name } + using z := N <= 1 + by z; + ''', + tb.bag([ + { + 'key': {'z': True}, + 'grouping': {'z'}, + 'elements': tb.bag([ + {'name': 'Alice'}, + {'name': 'Bob'}, + {'name': 'Carol'}, + {'name': 'Dave'}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_04(self): + await self.assert_query_result( + ''' + with N := random() + group { a := 1 } + using z := N <= 1 + by z; + ''', + tb.bag([ + { + 'key': {'z': True}, + 'grouping': {'z'}, + 'elements': tb.bag([ + {'a': 1}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_05(self): + await self.assert_query_result( + ''' + select ( + with + N := random() + group cards::User { name, b := N } by .name + ) { + key, + grouping, + elements: { name, z := .b <= 1}, + }; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'z': True}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'z': True}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'z': True}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'z': True}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_06(self): + await self.assert_query_result( + ''' + select ( + with + N := random(), + group { a := 1, b := N } by .a + ) { + key, + grouping, + elements: { a, z := .b <= 1}, + }; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': True}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_set_01(self): + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select cards::User { name, b := n } + ) + group X { name, b } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': 8}, + {'name': 'Alice', 'b': 9}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': 8}, + {'name': 'Bob', 'b': 9}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': 8}, + {'name': 'Carol', 'b': 9}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': 8}, + {'name': 'Dave', 'b': 9}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_02(self): + # Remove computed pointer from output shape + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select cards::User { name, b := n } + ) + group X { name } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice'}, + {'name': 'Alice'}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob'}, + {'name': 'Bob'}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol'}, + {'name': 'Carol'}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave'}, + {'name': 'Dave'}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_03(self): + # Wrap subject in select + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select cards::User { name, b := n } + ) + group (select X { name, b }) by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': 8}, + {'name': 'Alice', 'b': 9}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': 8}, + {'name': 'Bob', 'b': 9}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': 8}, + {'name': 'Carol', 'b': 9}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': 8}, + {'name': 'Dave', 'b': 9}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_04(self): + # Use computed pointer only in by clause + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select cards::User { name, b := n } + ) + group X { name } by .b; + ''', + tb.bag([ + { + 'key': {'b': 8}, + 'grouping': {'b'}, + 'elements': tb.bag([ + {'name': 'Alice'}, + {'name': 'Bob'}, + {'name': 'Carol'}, + {'name': 'Dave'}, + ]), + }, + { + 'key': {'b': 9}, + 'grouping': {'b'}, + 'elements': tb.bag([ + {'name': 'Alice'}, + {'name': 'Bob'}, + {'name': 'Carol'}, + {'name': 'Dave'}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_05(self): + await self.assert_query_result( + ''' + with X := cards::User { + name, + b := (for n in { 8, 9 } select n), + } + group X { name, b } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {8, 9}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {8, 9}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {8, 9}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {8, 9}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_06(self): + await self.assert_query_result( + ''' + with X := cards::User { + name, + b := (for n in { 8, 9 } select n), + } + group X { name, b } + using total := sum(.b) + by total; + ''', + tb.bag([ + { + 'key': {'total': 17}, + 'grouping': {'total'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {8, 9}}, + {'name': 'Bob', 'b': {8, 9}}, + {'name': 'Carol', 'b': {8, 9}}, + {'name': 'Dave', 'b': {8, 9}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_07(self): + await self.assert_query_result( + ''' + with + N := (for n in { 8, 9 } select n), + X := cards::User { name, b := N } + group X { name, b } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {8, 9}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {8, 9}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {8, 9}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {8, 9}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_08(self): + await self.assert_query_result( + ''' + with + N := (for n in { 8, 9 } select n), + X := cards::User { name, b := N } + group X { name, b } + using total := sum(.b) + by total; + ''', + tb.bag([ + { + 'key': {'total': 17}, + 'grouping': {'total'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {8, 9}}, + {'name': 'Bob', 'b': {8, 9}}, + {'name': 'Carol', 'b': {8, 9}}, + {'name': 'Dave', 'b': {8, 9}}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_set_09(self): + await self.assert_query_result( + ''' + with X := cards::User { + name, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group X { name, b: { c, d } } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 3, 'd': 9}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 3, 'd': 9}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 3, 'd': 9}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 3, 'd': 9}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_set_10(self): + # Remove pointer from output shape + await self.assert_query_result( + ''' + with X := cards::User { + name, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group X { name, b: { c } } by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 3}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 3}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 3}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 3}}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_set_11(self): + # Wrap subject in select + await self.assert_query_result( + ''' + with X := cards::User { + name, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group (select X { name, b: { c, d } }) by .name; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 3, 'd': 9}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 3, 'd': 9}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 3, 'd': 9}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 3, 'd': 9}}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_set_12(self): + # Use computed pointer only in by clause + await self.assert_query_result( + ''' + with X := cards::User { + name, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group X { name, b: { c } } + using d := .b.d + by d; + ''', + tb.bag([ + { + 'key': {'d': 9}, + 'grouping': {'d'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 3, 'd': 9}}, + {'name': 'Bob', 'b': {'c': 3, 'd': 9}}, + {'name': 'Carol', 'b': {'c': 3, 'd': 9}}, + {'name': 'Dave', 'b': {'c': 3, 'd': 9}}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_free_object_01(self): + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select { a := 1, b := n } + ) + group X { a, b } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': 8}, + {'a': 1, 'b': 9}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_free_object_02(self): + # Remove computed pointer from output shape + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select { a := 1, b := n } + ) + group X { a } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1}, + {'a': 1}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_free_object_03(self): + # Wrap subject in select + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select { a := 1, b := n } + ) + group (select X { a, b }) by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': 8}, + {'a': 1, 'b': 9}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_free_object_04(self): + # Use computed pointer only in by clause + await self.assert_query_result( + ''' + with X := ( + for n in { 8, 9 } + select { a := 1, b := n } + ) + group X { a } by .b; + ''', + tb.bag([ + { + 'key': {'b': 8}, + 'grouping': {'b'}, + 'elements': tb.bag([ + {'a': 1}, + ]), + }, + { + 'key': {'b': 9}, + 'grouping': {'b'}, + 'elements': tb.bag([ + {'a': 1}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_free_object_05(self): + await self.assert_query_result( + ''' + with X := { + a := 1, + b := (for n in { 8, 9 } select n), } - ]) + group X { a, b } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {8, 9}}, + ]), + }, + ]) + ) - await self.assert_query_result(qry, res) + async def test_edgeql_group_binding_iterator_ptr_free_object_06(self): + await self.assert_query_result( + ''' + with X := { + a := 1, + b := (for n in { 8, 9 } select n), + } + group X { a, b } + using total := sum(.b) + by total; + ''', + tb.bag([ + { + 'key': {'total': 17}, + 'grouping': {'total'}, + 'elements': tb.bag([ + {'a': 1, 'b': {8, 9}}, + ]), + }, + ]) + ) - async def test_edgeql_group_by_group_by_03a(self): - await self._test_edgeql_group_by_group_by_03( + async def test_edgeql_group_binding_iterator_ptr_free_object_07(self): + await self.assert_query_result( ''' - with module cards - select (group Card by .element) { - el := .key.element, - groups := ( - with z := (group .elements using x := .cost%2 by x) - for z in z union ( - even := z.key.x, - elements := array_agg(z.elements{name, cost}), - ) - ) - }; + with + N := (for n in { 8, 9 } select n), + X := { a := 1, b := N } + group X { a, b } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {8, 9}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_free_object_08(self): + await self.assert_query_result( ''' + with + N := (for n in { 8, 9 } select n), + X := { a := 1, b := N } + group X { a, b } + using total := sum(.b) + by total; + ''', + tb.bag([ + { + 'key': {'total': 17}, + 'grouping': {'total'}, + 'elements': tb.bag([ + {'a': 1, 'b': {8, 9}}, + ]), + }, + ]) ) - @tb.needs_factoring_weakly - async def test_edgeql_group_by_group_by_03b(self): - await self._test_edgeql_group_by_group_by_03( + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_free_object_09(self): + await self.assert_query_result( ''' - with module cards - select (group Card by .element) { - el := .key.element, - groups := ( - with z := (group .elements using x := .cost%2 by x) - select ( - even := z.key.x, - elements := array_agg(z.elements{name, cost}), - ) - ) - }; + with X := { + a := 1, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group X { a, b: { c, d } } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 3, 'd': 9}}, + ]), + }, + ]) + ) + + async def test_edgeql_group_binding_iterator_ptr_free_object_10(self): + # Remove pointer from output shape + await self.assert_query_result( ''' + with X := { + a := 1, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group X { a, b: { c } } by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 3}}, + ]), + }, + ]) ) - async def test_edgeql_group_by_group_by_03c(self): - await self._test_edgeql_group_by_group_by_03( + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_free_object_11(self): + # Wrap subject in select + await self.assert_query_result( ''' - with module cards - select (group Card by .element) { - el := .key.element, - groups := ( - for z in (group .elements using x := .cost%2 by x) union ( - even := z.key.x, - elements := array_agg(z.elements{name, cost}), - ) - ) - }; + with X := { + a := 1, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group (select X { a, b: { c, d } }) by .a; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 3, 'd': 9}}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize computed pointers properly""") + async def test_edgeql_group_binding_iterator_ptr_free_object_12(self): + # Use computed pointer only in by clause + await self.assert_query_result( ''' + with X := { + a := 1, + b := (for n in { 9 } union ({ c := 3, d := n })) + } + group X { a, b: { c } } + using d := .b.d + by d; + ''', + tb.bag([ + { + 'key': {'d': 9}, + 'grouping': {'d'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 3}}, + ]), + }, + ]) ) - async def test_edgeql_group_errors_id(self): - async with self.assertRaisesRegexTx( - edgedb.UnsupportedFeatureError, - r"may not name a grouping alias 'id'" - ): - await self.con.execute(''' - group cards::Card{name} using id := .id by id - ''') + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_01(self): + await self.assert_query_result( + ''' + select ( + with X := cards::User { name, b := random() } + group X { name, b } by .name; + ) { + key, + grouping, + elements: { name, z := .b <= 1 }, + }; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'z': True}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'z': True}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'z': True}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'z': True}, + ]), + }, + ]) + ) - async with self.assertRaisesRegexTx( - edgedb.UnsupportedFeatureError, - r"may not group by a field named id", - _position=44, - ): - await self.con.execute(''' - group cards::Card{name} by .id - ''') + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_02(self): + # Remove pointer from output shape + await self.assert_query_result( + ''' + select ( + with X := cards::User { name, b := random() } + group X { name } by .name; + ) { + key, + grouping, + elements: { name }, + }; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice'}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob'}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol'}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave'}, + ]), + }, + ]) + ) - async def test_edgeql_group_errors_ref(self): - async with self.assertRaisesRegexTx( - edgedb.InvalidReferenceError, - r"variable 'name' referenced in BY but not declared in USING" - ): - await self.con.execute(''' - group User by name - ''') + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_03(self): + # Wrap subject in select + await self.assert_query_result( + ''' + select ( + with X := cards::User { name, b := random() } + group (select X { name, b }) by .name; + ) { + key, + grouping, + elements: { name, z := .b <= 1 }, + }; + ''', + tb.bag([ + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'z': True}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'z': True}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'z': True}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'z': True}, + ]), + }, + ]) + ) - async def test_edgeql_group_tuple_01(self): - await self.con.execute(''' - create type tup { - create multi property tup -> tuple ; + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_04(self): + # Use computed pointer only in by clause + await self.assert_query_result( + ''' + select ( + with X := cards::User { name, b := random() } + group X { name } by .b; + ) { + name: (select .elements.name limit 1), + grouping, + elements: { name, z := .b <= 1 }, }; - insert tup { tup := {(1, 1), (1, 2), (1, 1), (2, 1)} }; - ''') + ''', + tb.bag( + { + 'name': 'Alice', + 'grouping': {'b'}, + 'elements': tb.bag( + {'name': 'Alice', 'z': True}, + ), + }, + { + 'name': 'Bob', + 'grouping': {'b'}, + 'elements': tb.bag( + {'name': 'Bob', 'z': True}, + ), + }, + { + 'name': 'Carol', + 'grouping': {'b'}, + 'elements': tb.bag( + {'name': 'Carol', 'z': True}, + ), + }, + { + 'name': 'Dave', + 'grouping': {'b'}, + 'elements': tb.bag( + {'name': 'Dave', 'z': True}, + ), + }, + ) + ) + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_05(self): await self.assert_query_result( ''' - with X := tup.tup, - group X using z := X by z; + select ( + with X := cards::User { + name, + b := { c := 2, d := random() } + } + group X { name, b: { c, d } } by .name; + ) { + key, + grouping, + elements: { + name, + b: { + c, + z := .d <= 1, + }, + }, + }; ''', tb.bag([ - {"elements": [[1, 2]], "key": {"z": [1, 2]}}, - {"elements": [[2, 1]], "key": {"z": [2, 1]}}, - {"elements": tb.bag([[1, 1], [1, 1]]), "key": {"z": [1, 1]}} + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 2, 'z': True}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 2, 'z': True}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 2, 'z': True}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 2, 'z': True}}, + ]), + }, ]) ) - async def test_edgeql_group_tuple_02(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_06(self): + # Remove pointer from output shape await self.assert_query_result( ''' - with X := {(1, 1), (1, 2), (1, 1), (2, 1)}, - group X using z := X by z; + select ( + with X := cards::User { + name, + b := { c := 2, d := random() } + } + group (select X { name, b: { c } }) by .name; + ) { + key, + grouping, + elements: { + name, + b: { c }, + }, + }; ''', tb.bag([ - {"elements": [[1, 2]], "key": {"z": [1, 2]}}, - {"elements": [[2, 1]], "key": {"z": [2, 1]}}, - {"elements": tb.bag([[1, 1], [1, 1]]), "key": {"z": [1, 1]}} + { + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 2}}, + ]), + }, + { + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 2}}, + ]), + }, + { + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 2}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 2}}, + ]), + }, ]) ) - async def test_edgeql_group_semijoin_group_01(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_07(self): + # Wrap subject in select await self.assert_query_result( ''' - with module cards - group ( - select (group Card{name, cost} by .element) - order by .key.element limit 1 - ).elements by .cost; + select ( + with X := cards::User { + name, + b := { c := 2, d := random() } + } + group (select X { name, b: { c, d } }) by .name; + ) { + key, + grouping, + elements: { + name, + b: { + c, + z := .d <= 1, + }, + }, + }; ''', tb.bag([ { - "elements": [{"cost": 1, "name": "Sprite"}], - "grouping": ["cost"], - "key": {"cost": 1} + 'key': {'name': 'Alice'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 2, 'z': True}}, + ]), }, { - "elements": [{"cost": 2, "name": "Giant eagle"}], - "grouping": ["cost"], - "key": {"cost": 2} + 'key': {'name': 'Bob'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 2, 'z': True}}, + ]), }, { - "elements": [{"cost": 4, "name": "Djinn"}], - "grouping": ["cost"], - "key": {"cost": 4} - } + 'key': {'name': 'Carol'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 2, 'z': True}}, + ]), + }, + { + 'key': {'name': 'Dave'}, + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 2, 'z': True}}, + ]), + }, ]) ) - async def test_edgeql_group_simple_agg_01(self): - await self.assert_query_result( - r''' - with module cards - select (group Card by .element) { - el := .key.element, cs := array_agg(.elements) - }; - ''', - tb.bag([ - {'el': "Water", 'cs': [{'id': str}] * 2}, - {'el': "Fire", 'cs': [{'id': str}] * 2}, - {'el': "Earth", 'cs': [{'id': str}] * 2}, - {'el': "Air", 'cs': [{'id': str}] * 3}, - ]), - ) - - async def test_edgeql_group_simple_agg_02(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_set_08(self): + # Use computed pointer only in by clause await self.assert_query_result( - r''' - with module cards - select (group Card by .element) { - el := .key.element, cs := array_agg(.elements { name }) - }; + ''' + select ( + with X := cards::User { + name, + b := { c := 2, d := random() } + } + group (select X { name, b: { c } }) + using d := .b.c + by d; + ) { + name: (select .elements.name limit 1), + grouping, + elements: { + name, + b: { c }, + }, + }; ''', tb.bag([ { - "cs": tb.bag( - [{"name": "Bog monster"}, {"name": "Giant turtle"}]), - "el": "Water" + 'name': 'Alice', + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Alice', 'b': {'c': 2, 'z': True}}, + ]), }, { - "cs": tb.bag([{"name": "Imp"}, {"name": "Dragon"}]), - "el": "Fire", + 'name': 'Bob', + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Bob', 'b': {'c': 2, 'z': True}}, + ]), }, { - "cs": tb.bag([{"name": "Dwarf"}, {"name": "Golem"}]), - "el": "Earth", + 'name': 'Carol', + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Carol', 'b': {'c': 2, 'z': True}}, + ]), }, { - "cs": tb.bag([ - {"name": "Sprite"}, - {"name": "Giant eagle"}, - {"name": "Djinn"} + 'name': 'Dave', + 'grouping': {'name'}, + 'elements': tb.bag([ + {'name': 'Dave', 'b': {'c': 2, 'z': True}}, ]), - "el": "Air", - } + }, ]) ) - async def test_edgeql_group_agg_multi_01(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_01(self): await self.assert_query_result( ''' - with module cards - for g in (group Card BY .element) union ( - array_agg(g.elements.name ++ {"!", "?"}) - ); + select ( + with X := { a := 1, b := random() } + group X { a, b } by .a; + ) { + key, + grouping, + elements: { a, z := .b <= 1 }, + }; ''', tb.bag([ - {"Bog monster!", "Bog monster?", - "Giant turtle!", "Giant turtle?"}, - {"Imp!", "Imp?", "Dragon!", "Dragon?"}, - {"Dwarf!", "Dwarf?", "Golem!", "Golem?"}, - {"Sprite!", "Sprite?", "Giant eagle!", - "Giant eagle?", "Djinn!", "Djinn?"} + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'z': True}, + ]), + }, ]) ) - async def test_edgeql_group_agg_multi_02(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_02(self): + # Remove pointer from output shape await self.assert_query_result( ''' - with module cards - for g in (group Card BY .element) union ( - count((Award { multi z := g.elements.name }.z)) - ); ''', - tb.bag([6, 6, 6, 9]), + select ( + with X := { a := 1, b := random() } + group X { a } by .a; + ) { + key, + grouping, + elements: { a }, + }; + ''', + tb.bag([ + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1}, + ]), + }, + ]) ) - async def test_edgeql_group_agg_multi_03(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_03(self): + # Wrap subject in select await self.assert_query_result( ''' - for g in (group BooleanTest by .val) union ( - array_agg(g.elements.tags) - ); + select ( + with X := { a := 1, b := random() } + group (select X { a, b }) by .a; + ) { + key, + grouping, + elements: { a, z := .b <= 1 }, + }; ''', tb.bag([ - ["red"], - [], - tb.bag(["red", "green"]), - tb.bag(["red", "black"]), - ]), + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'z': True}, + ]), + }, + ]) ) - async def test_edgeql_group_agg_grouping_01(self): - # Something about this previously triggered a postgres ISE - # that we had to work around. + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_04(self): + # Use computed pointer only in by clause await self.assert_query_result( ''' - select (group cards::Card - using awd_size := count(.awards) - by awd_size, .element) { grouping }; + select ( + with X := { a := 1, b := random() } + group X { a } by .b; + ) { + key, + grouping, + elements: { a, z := .b <= 1 }, + }; ''', - [{"grouping": ["awd_size", "element"]}] * 6, + tb.bag([ + { + 'grouping': {'b'}, + 'elements': tb.bag([ + {'a': 1, 'z': True}, + ]), + }, + ]) ) - async def test_edgeql_trivial_grouping_01(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_05(self): await self.assert_query_result( ''' - group 0 using x := 0 by cube(x) + select ( + with X := { + a := 1, + b := { c := 2, d := random() } + } + group X { a, b: { c, d } } by .a; + ) { + key, + grouping, + elements: { + a, + b: { + c, + z := .d <= 1, + }, + }, + }; ''', tb.bag([ - {"elements": [0], "grouping": [], "key": {"x": None}}, - {"elements": [0], "grouping": ["x"], "key": {"x": 0}} - ]), + { + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 2, 'z': True}}, + ]), + }, + ]) ) - async def test_edgeql_group_binding_01(self): + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_06(self): + # Remove pointer from output shape await self.assert_query_result( ''' - with GR := (group cards::Card BY .element) - select GR { - multi elements := ( - with els := .elements - select els {name} - ) - }; + select ( + with X := { + a := 1, + b := { c := 2, d := random() } + } + group (select X { a, b: { c } }) by .a; + ) { + key, + grouping, + elements: { + a, + b: { c }, + }, + }; ''', tb.bag([ { - "elements": tb.bag( - [{"name": "Bog monster"}, {"name": "Giant turtle"}]), + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 2}}, + ]), }, - { - "elements": tb.bag([{"name": "Imp"}, {"name": "Dragon"}]), + ]) + ) + + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_07(self): + # Wrap subject in select + await self.assert_query_result( + ''' + select ( + with X := { + a := 1, + b := { c := 2, d := random() } + } + group (select X { a, b: { c, d } }) by .a; + ) { + key, + grouping, + elements: { + a, + b: { + c, + z := .d <= 1, + }, }, + }; + ''', + tb.bag([ { - "elements": tb.bag([{"name": "Dwarf"}, {"name": "Golem"}]), + 'key': {'a': 1}, + 'grouping': {'a'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 2, 'z': True}}, + ]), + }, + ]) + ) + + @test.xerror("""Group by doesn't materialize volatile properly""") + async def test_edgeql_group_binding_volatile_ptr_free_object_08(self): + # Use computed pointer only in by clause + await self.assert_query_result( + ''' + select ( + with X := { + a := 1, + b := { c := 2, d := random() } + } + group (select X { a, b: { c } }) + using d := .b.c + by d; + ) { + key, + grouping, + elements: { + a, + b: { c }, }, + }; + ''', + tb.bag([ { - "elements": tb.bag([ - {"name": "Sprite"}, - {"name": "Giant eagle"}, - {"name": "Djinn"} + 'grouping': {'d'}, + 'elements': tb.bag([ + {'a': 1, 'b': {'c': 2}}, ]), - } + }, ]) ) From 61e5d7a2c713fcd31f96c9e711c1a43376938807 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 14 Jan 2025 16:45:36 -0800 Subject: [PATCH 013/154] Delete some GROUP tests marked as not_implemented (#8219) These are tests for a different GROUP than the one we built. --- tests/test_edgeql_expressions.py | 105 ------------------------------- 1 file changed, 105 deletions(-) diff --git a/tests/test_edgeql_expressions.py b/tests/test_edgeql_expressions.py index 0e78de2d8be..0d11ab3b5ac 100644 --- a/tests/test_edgeql_expressions.py +++ b/tests/test_edgeql_expressions.py @@ -29,7 +29,6 @@ from edb import errors from edb.common import assert_data_shape from edb.testbase import server as tb -from edb.tools import test class value(typing.NamedTuple): @@ -8993,110 +8992,6 @@ async def test_edgeql_expr_for_02(self): {2, 3, 4, 5}, ) - @test.not_implemented('GROUP statement is not yet implemented') - async def test_edgeql_expr_group_01(self): - await self.assert_query_result( - r""" - WITH I := {1, 2, 3, 4} - GROUP I - USING _ := I % 2 = 0 - BY _ - INTO I - UNION _r := ( - values := array_agg(I ORDER BY I) - ) ORDER BY _r.values; - """, - [ - {'values': [1, 3]}, - {'values': [2, 4]} - ] - ) - - @test.not_implemented('GROUP statement is not yet implemented') - async def test_edgeql_expr_group_02(self): - await self.assert_query_result( - r''' - # handle a number of different aliases - WITH x := {(1, 2), (3, 4), (4, 2)} - GROUP y := x - USING _ := y.1 - BY _ - INTO y - UNION array_agg(y.0 ORDER BY y.0); - ''', - [[1, 4], [3]], - sort=True - ) - - @test.not_implemented('GROUP statement is not yet implemented') - async def test_edgeql_expr_group_03(self): - await self.assert_query_result( - r''' - WITH x := {(1, 2), (3, 4), (4, 2)} - GROUP x - USING _ := x.1 - BY _ - INTO x - UNION array_agg(x.0 ORDER BY x.0); - ''', - [[1, 4], [3]], - sort=True - ) - - @test.not_implemented('GROUP statement is not yet implemented') - async def test_edgeql_expr_group_04(self): - await self.assert_query_result( - r''' - WITH x := {(1, 2), (3, 4), (4, 2)} - GROUP x - USING B := x.1 - BY B - INTO x - UNION (B, array_agg(x.0 ORDER BY x.0)) - ORDER BY - B; - ''', - [[2, [1, 4]], [4, [3]]], - ) - - @test.not_implemented('GROUP statement is not yet implemented') - async def test_edgeql_expr_group_05(self): - await self.assert_query_result( - r''' - # handle the case where the value to be computed depends - # on both, the grouped subset and the original set - WITH - x1 := {(1, 0), (1, 0), (1, 0), (2, 0), (3, 0), (3, 0)}, - x2 := x1 - GROUP y := x1 - USING z := y.0 - BY z - INTO y - UNION ( - # we expect that count(x1) and count(x2) will be - # identical in this context, whereas count(y) will - # represent the size of each subset - z, count(y), count(x1), count(x2) - ) - ORDER BY z; - ''', - [[1, 3, 6, 6], [2, 1, 6, 6], [3, 2, 6, 6]] - ) - - @test.not_implemented('GROUP statement is not yet implemented') - async def test_edgeql_expr_group_06(self): - await self.assert_query_result( - r''' - GROUP X := {1, 1, 1, 2, 3, 3} - USING y := X - BY y - INTO y - UNION (y, count(X)) - ORDER BY y; - ''', - [[1, 3], [2, 1], [3, 2]] - ) - async def test_edgeql_expr_slice_01(self): with self.assertRaisesRegex( edgedb.QueryError, From b8c870b81eab5c0d8ade2cee8e384100ee439ec3 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 14 Jan 2025 17:27:57 -0800 Subject: [PATCH 014/154] Try to fix test_server_ops_multi_tenant in release builds (#8220) One of the tests needs to access the /server-info endpoint in order to test magic_smtp_config. But since testmode doesn't work with multitenant (see #8216), this does not work when being run outside devmode. Skip that part of the test when not in devmode, then. I don't find this particularly satisfying. --- tests/test_server_ops.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_server_ops.py b/tests/test_server_ops.py index a3f1958640b..f59d53a4e49 100644 --- a/tests/test_server_ops.py +++ b/tests/test_server_ops.py @@ -1800,6 +1800,13 @@ async def _test_server_ops_multi_tenant_6(self, mtargs: MultiTenantArgs): ) async def _test_server_ops_multi_tenant_7(self, mtargs: MultiTenantArgs): + # FIXME(fantix?) - We can't test this outside devmode, because we + # can't currently use testmode in multi-tenant mode. + # This means that we can't access the server-info debug endpoint + # to get the info we need outside of devmode. + if not devmode.is_in_dev_mode(): + return + self.assertEqual( (await mtargs.current_email_provider(1))["sender"], "sender@host1.com", From 1262b91782c4f5e91196c444d50908e92f7814f6 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 15 Jan 2025 13:59:24 -0800 Subject: [PATCH 015/154] Drop dump_traceback_later call (#8225) 30 minutes can get hit easily, and I think this might have been causing crashes in our nightly tests. See https://github.com/edgedb/edgedb/actions/runs/12779694201/job/35626124790 I don't fully understand why it would crash though. --- edb/tools/test/runner.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/edb/tools/test/runner.py b/edb/tools/test/runner.py index 21fd87e380e..b1be0126942 100644 --- a/edb/tools/test/runner.py +++ b/edb/tools/test/runner.py @@ -96,8 +96,6 @@ def init_worker( global py_random_seed faulthandler.enable(file=sys.stderr, all_threads=True) - # If we're still running after 30 minutes, dump the traceback. - faulthandler.dump_traceback_later(30 * 60, file=sys.stderr) if additional_init: additional_init() From f614408e8da8474a4301d0af22521e77d2461da8 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Thu, 16 Jan 2025 17:38:02 -0500 Subject: [PATCH 016/154] Bump Rust toolchain to 1.81 (#8230) --- rust-toolchain.toml | 2 +- rust/db_proto/src/gen.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 2f69b4471fd..20e20c7b43f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.80.1" +channel = "1.81" components = [ "rustfmt", "clippy" ] diff --git a/rust/db_proto/src/gen.rs b/rust/db_proto/src/gen.rs index 15e4e0cf6c6..3e36aee09dc 100644 --- a/rust/db_proto/src/gen.rs +++ b/rust/db_proto/src/gen.rs @@ -827,7 +827,7 @@ mod tests { fixed(fixed_offset = fixed_offset, (0)), }, { - name(l), type (crate::meta::Length), size(fixed = fixed), + name(l), type ($crate::meta::Length), size(fixed = fixed), value(auto = auto), docs(concat!("`", stringify! (l), "` field.")), fixed(fixed_offset = fixed_offset, ((0) + std::mem::size_of::())), }, @@ -843,7 +843,7 @@ mod tests { fixed(no_fixed_offset = no_fixed_offset, (0)), }, { - name(d), type (crate::meta::FixedArray<4, u8>), size(fixed = fixed), + name(d), type ($crate::meta::FixedArray<4, u8>), size(fixed = fixed), value(no_value = no_value), docs(concat!("`", stringify! (d), "` field.")), fixed(no_fixed_offset = no_fixed_offset, ((0) + std::mem::size_of::())), From 3086faa6a958fc4afac2d4580d8e87f2d5c36ca1 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Fri, 17 Jan 2025 11:06:52 -0500 Subject: [PATCH 017/154] multitenant: patch stdlib with testmode schema if requested (#8229) Reverts #8216 #8220 --- edb/server/args.py | 10 +++++-- edb/server/main.py | 59 +++++++++++++++++++++++++++++++++++++ edb/testbase/server.py | 8 +---- tests/test_server_config.py | 42 +++++++++++++++++++++----- tests/test_server_ops.py | 9 ------ 5 files changed, 102 insertions(+), 26 deletions(-) diff --git a/edb/server/args.py b/edb/server/args.py index a20af128f03..6428dbf33a2 100644 --- a/edb/server/args.py +++ b/edb/server/args.py @@ -687,6 +687,14 @@ def resolve_envvar_value(self, ctx: click.Context): envvar="GEL_SERVER_MULTITENANT_CONFIG_FILE", cls=EnvvarResolver, hidden=True, + help='Start the server in multi-tenant mode, with reloadable tenants ' + 'configured in the given file. Each tenant must have a unique ' + 'SNI name as the key to route the traffic correctly, as well as ' + 'a dedicated backend DSN to host the tenant data. See edb/server/' + 'multitenant.py for config file format. All tenants share the ' + 'same compiler pool, thus the same stdlib. So if any of the ' + 'backends contains test-mode schema, the server should be ' + 'started with --testmode to handle them properly.', ), click.option( '-l', '--log-level', @@ -1552,8 +1560,6 @@ def parse_args(**kwargs: Any): if kwargs['compiler_pool_mode'] is not CompilerPoolMode.MultiTenant: abort("must use --compiler-pool-mode=fixed_multi_tenant " "in multi-tenant mode") - if kwargs['testmode']: - abort("cannot use --testmode in multi-tenant mode") bootstrap_script_text: Optional[str] if kwargs['bootstrap_command_file']: diff --git a/edb/server/main.py b/edb/server/main.py index 1bd36c8ed01..302616a3b16 100644 --- a/edb/server/main.py +++ b/edb/server/main.py @@ -53,6 +53,7 @@ import uvloop from edb import buildmeta +from edb import errors from edb.ir import statypes from edb.common import exceptions from edb.common import devmode @@ -71,6 +72,7 @@ if TYPE_CHECKING: from . import server + from edb.server import bootstrap else: # Import server lazily to make sure that most of imports happen # under coverage (if we're testing with it). Otherwise @@ -411,6 +413,50 @@ async def _get_remote_pgcluster( return cluster, args +def _patch_stdlib_testmode( + stdlib: bootstrap.StdlibBits +) -> bootstrap.StdlibBits: + from edb import edgeql + from edb.pgsql import delta as delta_cmds + from edb.pgsql import params as pg_params + from edb.edgeql import ast as qlast + from edb.schema import ddl as s_ddl + from edb.schema import delta as sd + from edb.schema import schema as s_schema + from edb.schema import std as s_std + + schema: s_schema.Schema = s_schema.ChainedSchema( + s_schema.EMPTY_SCHEMA, + stdlib.stdschema, + stdlib.global_schema, + ) + reflschema = stdlib.reflschema + ctx = sd.CommandContext( + stdmode=True, + backend_runtime_params=pg_params.get_default_runtime_params(), + ) + + for modname in s_schema.TESTMODE_SOURCES: + ddl_text = s_std.get_std_module_text(modname) + for ddl_cmd in edgeql.parse_block(ddl_text): + assert isinstance(ddl_cmd, qlast.DDLCommand) + delta = s_ddl.delta_from_ddl( + ddl_cmd, modaliases={}, schema=schema, stdmode=True + ) + if not delta.canonical: + sd.apply(delta, schema=schema) + delta = delta_cmds.CommandMeta.adapt(delta) + schema = sd.apply(delta, schema=schema, context=ctx) + reflschema = delta.apply(reflschema, ctx) + + assert isinstance(schema, s_schema.ChainedSchema) + return stdlib._replace( + stdschema=schema.get_top_schema(), + global_schema=schema.get_global_schema(), + reflschema=reflschema, + ) + + async def run_server( args: srvargs.ServerConfig, *, @@ -496,6 +542,19 @@ async def run_server( "Cannot run multi-tenant server " "without pre-compiled standard library" ) + if args.testmode: + # In multitenant mode, the server/compiler is started without a + # backend and will be connected to many backends. That means we + # cannot load the stdlib from a certain backend; instead, the + # pre-compiled stdlib is always in use. This means that we need + # to explicitly enable --testmode starting a multitenant server + # in order to handle backends with test-mode schema properly. + try: + stdlib = _patch_stdlib_testmode(stdlib) + except errors.SchemaError: + # The pre-compiled standard library already has test-mode + # schema; ignore the patching error. + pass compiler = edbcompiler.new_compiler( stdlib.stdschema, diff --git a/edb/testbase/server.py b/edb/testbase/server.py index c57a293b245..ed2f28aabe1 100644 --- a/edb/testbase/server.py +++ b/edb/testbase/server.py @@ -2394,7 +2394,6 @@ def __init__( extra_args: Optional[List[str]] = None, net_worker_mode: Optional[str] = None, password: Optional[str] = None, - testmode: bool = True, ) -> None: self.bind_addrs = bind_addrs self.auto_shutdown_after = auto_shutdown_after @@ -2432,7 +2431,6 @@ def __init__( self.extra_args = extra_args self.net_worker_mode = net_worker_mode self.password = password - self.testmode = testmode async def wait_for_server_readiness(self, stream: asyncio.StreamReader): while True: @@ -2482,15 +2480,13 @@ async def __aenter__(self): cmd = [ sys.executable, '-m', 'edb.server.main', '--port', 'auto', + '--testmode', '--emit-server-status', f'fd://{status_w.fileno()}', '--compiler-pool-size', str(self.compiler_pool_size), '--tls-cert-mode', str(self.tls_cert_mode), '--jose-key-mode', 'generate', ] - if self.testmode: - cmd.extend(['--testmode']) - if self.compiler_pool_mode is not None: cmd.extend(('--compiler-pool-mode', self.compiler_pool_mode.value)) @@ -2767,7 +2763,6 @@ def start_edgedb_server( default_branch: Optional[str] = None, net_worker_mode: Optional[str] = None, force_new: bool = False, # True for ignoring multitenant config env - testmode: bool = True, ): if (not devmode.is_in_dev_mode() or adjacent_to) and not runstate_dir: if backend_dsn or adjacent_to: @@ -2849,7 +2844,6 @@ def start_edgedb_server( default_branch=default_branch, net_worker_mode=net_worker_mode, password=password, - testmode=testmode, ) diff --git a/tests/test_server_config.py b/tests/test_server_config.py index 2bc4145e91a..bb897039171 100644 --- a/tests/test_server_config.py +++ b/tests/test_server_config.py @@ -2202,12 +2202,17 @@ async def test_server_config_args_03(self): pass async def test_server_config_env_01(self): + # Backend settings cannot be set statically with remote backend + remote_pg = bool(os.getenv("EDGEDB_TEST_BACKEND_DSN")) + env = { "EDGEDB_SERVER_CONFIG_cfg::session_idle_timeout": "1m22s", - "EDGEDB_SERVER_CONFIG_cfg::query_execution_timeout": "403", "EDGEDB_SERVER_CONFIG_cfg::apply_access_policies": "false", "EDGEDB_SERVER_CONFIG_cfg::multiprop": "single", } + if not remote_pg: + env["EDGEDB_SERVER_CONFIG_cfg::query_execution_timeout"] = "403" + async with tb.start_edgedb_server(env=env) as sd: conn = await sd.connect() try: @@ -2223,13 +2228,14 @@ async def test_server_config_env_01(self): """), datetime.timedelta(minutes=1, seconds=22), ) - self.assertEqual( - await conn.query_single("""\ - select assert_single( - cfg::Config.query_execution_timeout) - """), - datetime.timedelta(seconds=403), - ) + if not remote_pg: + self.assertEqual( + await conn.query_single("""\ + select assert_single( + cfg::Config.query_execution_timeout) + """), + datetime.timedelta(seconds=403), + ) self.assertFalse( await conn.query_single("""\ select assert_single(cfg::Config.apply_access_policies) @@ -2313,6 +2319,10 @@ async def test_server_config_default(self): finally: await conn.aclose() + @unittest.skipIf( + "EDGEDB_SERVER_MULTITENANT_CONFIG_FILE" in os.environ, + "cannot use --config-file in multi-tenant mode", + ) async def test_server_config_file_01(self): conf = textwrap.dedent(''' ["cfg::Config"] @@ -2390,6 +2400,10 @@ async def test_server_config_file_01(self): finally: await conn.aclose() + @unittest.skipIf( + "EDGEDB_SERVER_MULTITENANT_CONFIG_FILE" in os.environ, + "cannot use --config-file in multi-tenant mode", + ) async def test_server_config_file_02(self): conf = textwrap.dedent(''' ["cfg::Config"] @@ -2406,6 +2420,10 @@ async def test_server_config_file_02(self): ): pass + @unittest.skipIf( + "EDGEDB_SERVER_MULTITENANT_CONFIG_FILE" in os.environ, + "cannot use --config-file in multi-tenant mode", + ) async def test_server_config_file_03(self): conf = textwrap.dedent(''' ["cfg::Config"] @@ -2422,6 +2440,10 @@ async def test_server_config_file_03(self): ): pass + @unittest.skipIf( + "EDGEDB_SERVER_MULTITENANT_CONFIG_FILE" in os.environ, + "cannot use --config-file in multi-tenant mode", + ) async def test_server_config_file_04(self): conf = textwrap.dedent(''' ["cfg::Config"] @@ -2439,6 +2461,10 @@ async def test_server_config_file_04(self): ): pass + @unittest.skipIf( + "EDGEDB_SERVER_MULTITENANT_CONFIG_FILE" in os.environ, + "cannot use --config-file in multi-tenant mode", + ) async def test_server_config_file_05(self): class Prop(enum.Enum): One = "One" diff --git a/tests/test_server_ops.py b/tests/test_server_ops.py index f59d53a4e49..f156bd1dd7d 100644 --- a/tests/test_server_ops.py +++ b/tests/test_server_ops.py @@ -1520,7 +1520,6 @@ async def _init_pg_cluster(self, path): runstate_dir=runstate_dir, backend_dsn=f'postgres:///?user=postgres&host={path}', reset_auth=True, - testmode=False, auto_shutdown_after=1, ) as sd: connect_args = { @@ -1577,7 +1576,6 @@ async def test_server_ops_multi_tenant(self): runstate_dir=runstate_dir, multitenant_config=conf_file.name, max_allowed_connections=None, - testmode=False, http_endpoint_security=args.ServerEndpointSecurityMode.Optional, ) async with srv as sd: @@ -1800,13 +1798,6 @@ async def _test_server_ops_multi_tenant_6(self, mtargs: MultiTenantArgs): ) async def _test_server_ops_multi_tenant_7(self, mtargs: MultiTenantArgs): - # FIXME(fantix?) - We can't test this outside devmode, because we - # can't currently use testmode in multi-tenant mode. - # This means that we can't access the server-info debug endpoint - # to get the info we need outside of devmode. - if not devmode.is_in_dev_mode(): - return - self.assertEqual( (await mtargs.current_email_provider(1))["sender"], "sender@host1.com", From a9c3dd778721845dfa373dbf374b562dca9c0c6e Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus Date: Fri, 17 Jan 2025 09:27:23 -0800 Subject: [PATCH 018/154] Allow tuples in GIN, GIST and BRIN indexes (#8232) This is an oversight, as all those Postgres indexes support indexing multiple columns. --- edb/schema/indexes.py | 23 +++++++-- tests/schemas/pg_trgm.esdl | 10 ++++ tests/schemas/pg_trgm_setup.edgeql | 18 +++++-- tests/test_edgeql_ext_pg_trgm.py | 78 ++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 8 deletions(-) diff --git a/edb/schema/indexes.py b/edb/schema/indexes.py index bbcc21e07c3..08e2863a7e4 100644 --- a/edb/schema/indexes.py +++ b/edb/schema/indexes.py @@ -71,21 +71,19 @@ def is_index_valid_for_type( schema: s_schema.Schema, context: sd.CommandContext, ) -> bool: - index_name = str(index.get_name(schema)) + index_allows_tuples = is_index_supporting_tuples(index, schema) for index_match in schema.get_referrers( index, scls_type=IndexMatch, field_name='index', ): valid_type = index_match.get_valid_type(schema) - if index_name == 'std::fts::index': - # FTS index works not only on its valid type (document), but also - # on tuples comtaining document as an element. + if index_allows_tuples: if is_subclass_or_tuple(expr_type, valid_type, schema): return True elif expr_type.issubclass(schema, valid_type): return True - if context.testmode and index_name == 'default::test': + if context.testmode and str(index.get_name(schema)) == 'default::test': # For functional tests of abstract indexes. return expr_type.issubclass( schema, @@ -95,6 +93,21 @@ def is_index_valid_for_type( return False +def is_index_supporting_tuples( + index: Index, + schema: s_schema.Schema, +) -> bool: + index_name = str(index.get_name(schema)) + return index_name in { + "std::fts::index", + "ext::pg_trgm::gin", + "ext::pg_trgm::gist", + "pg::gist", + "pg::gin", + "pg::brin", + } + + def is_subclass_or_tuple( ty: s_types.Type, parent: s_types.Type, schema: s_schema.Schema ) -> bool: diff --git a/tests/schemas/pg_trgm.esdl b/tests/schemas/pg_trgm.esdl index aee13d97f08..96fae0e80d6 100644 --- a/tests/schemas/pg_trgm.esdl +++ b/tests/schemas/pg_trgm.esdl @@ -26,6 +26,16 @@ type Gin extending Base { index ext::pg_trgm::gin on (.p_str); } +type Gin2 extending Base { + p_str_2: str; + index ext::pg_trgm::gist on ((.p_str, .p_str_2)); +} + type Gist extending Base { index ext::pg_trgm::gist on (.p_str); } + +type Gist2 extending Base { + p_str_2: str; + index ext::pg_trgm::gist on ((.p_str, .p_str_2)); +} diff --git a/tests/schemas/pg_trgm_setup.edgeql b/tests/schemas/pg_trgm_setup.edgeql index 0069f1fae18..ab1d2ae48f5 100644 --- a/tests/schemas/pg_trgm_setup.edgeql +++ b/tests/schemas/pg_trgm_setup.edgeql @@ -18,7 +18,14 @@ for x in range_unpack(range(1, 1001)) union ( insert Gist { - p_str := "qwertyu" ++ str_pad_start(x, 4, "0") + p_str := "qwertyu" ++ str_pad_start(x, 4, "0"), + } +); + +for x in range_unpack(range(1, 1001)) union ( + insert Gist2 { + p_str := "qwertyu" ++ str_pad_start(x, 4, "0"), + p_str_2 := "iopasdf" ++ str_pad_start(x, 4, "0"), } ); @@ -531,7 +538,12 @@ for x in { "Samarra School", "Jangal-e Marakeh Sar", } union ( - insert Gist { + (insert Gist { p_str := x - } + }) + union + (insert Gist2 { + p_str := x, + p_str_2 := x, + }) ); diff --git a/tests/test_edgeql_ext_pg_trgm.py b/tests/test_edgeql_ext_pg_trgm.py index 098fa2f81ff..139e482fa35 100644 --- a/tests/test_edgeql_ext_pg_trgm.py +++ b/tests/test_edgeql_ext_pg_trgm.py @@ -129,6 +129,47 @@ async def test_edgeql_ext_pg_trgm_similarity(self): index_type="ext::pg_trgm::gist", ) + qry = """ + SELECT + Gist2 { + p_str, + sim_dist := ext::pg_trgm::similarity_dist( + .p_str, "q0987wertyu0988" + ), + p_str_2, + sim_dist_2 := ext::pg_trgm::similarity_dist( + .p_str_2, "q0987opasdf0988" + ), + } + ORDER BY + .sim_dist EMPTY LAST THEN .sim_dist_2 EMPTY LAST + LIMIT + 2 + """ + + await self.assert_query_result( + qry, + [ + { + "p_str": "qwertyu0988", + "sim_dist": 0.411765, + "p_str_2": "iopasdf0988", + "sim_dist_2": 0.5, + }, + { + "p_str": "qwertyu0987", + "sim_dist": 0.5, + "p_str_2": "iopasdf0987", + "sim_dist_2": 0.57894737, + }, + ] + ) + + await self.assert_index_use( + qry, + index_type="ext::pg_trgm::gist", + ) + async def test_edgeql_ext_pg_trgm_word_similarity(self): await self.assert_query_result( """ @@ -216,6 +257,43 @@ async def test_edgeql_ext_pg_trgm_word_similarity(self): index_type="ext::pg_trgm::gist", ) + qry = """ + SELECT + Gist2 { + p_str, + word_sim_dist := ext::pg_trgm::word_similarity_dist( + "Kabankala", .p_str + ), + p_str_2, + word_sim_dist_2 := ext::pg_trgm::word_similarity_dist( + "Pub", .p_str_2 + ) + } + ORDER BY + .word_sim_dist EMPTY LAST THEN .word_sim_dist_2 EMPTY LAST + LIMIT + 2 + """ + + await self.assert_query_result( + qry, + [ + { + "p_str": "Kabankala", + "word_sim_dist": 0.0, + }, + { + "p_str": "Kabankalan City Public Plaza", + "word_sim_dist": 0.1, + }, + ] + ) + + await self.assert_index_use( + qry, + index_type="ext::pg_trgm::gist", + ) + async def test_edgeql_ext_pg_trgm_strict_word_similarity(self): await self.assert_query_result( """ From a91d693b9544cb27689dadae4561c71c1aa1e516 Mon Sep 17 00:00:00 2001 From: Scott Trinh Date: Fri, 17 Jan 2025 13:19:15 -0500 Subject: [PATCH 019/154] Do not fail if SMTP provider is not configured (#8228) Now that we have webhooks, it is valid to not have an SMTP provider configured. In this case, we log an error and send a fake email instead. --- edb/server/protocol/auth_ext/email.py | 51 +++++--- edb/testbase/http.py | 175 ++++++++++++++------------ tests/test_http_ext_auth.py | 43 ++++++- 3 files changed, 167 insertions(+), 102 deletions(-) diff --git a/edb/server/protocol/auth_ext/email.py b/edb/server/protocol/auth_ext/email.py index c5879d45a8d..70c8758cc33 100644 --- a/edb/server/protocol/auth_ext/email.py +++ b/edb/server/protocol/auth_ext/email.py @@ -1,13 +1,19 @@ import asyncio import urllib.parse import random +import logging +from email.message import EmailMessage from typing import Any, Coroutine from edb.server import tenant, smtp +from edb import errors from . import util, ui +logger = logging.getLogger("edb.server.ext.auth") + + async def send_password_reset_email( db: Any, tenant: tenant.Tenant, @@ -30,12 +36,7 @@ async def send_password_reset_email( reset_url=reset_url, **email_args, ) - smtp_provider = smtp.SMTP(db) - coro = smtp_provider.send( - msg, - test_mode=test_mode, - ) - await _protected_send(coro, tenant) + await _maybe_send_message(msg, tenant, db, test_mode) async def send_verification_email( @@ -70,12 +71,7 @@ async def send_verification_email( verify_url=verify_url, **email_args, ) - smtp_provider = smtp.SMTP(db) - coro = smtp_provider.send( - msg, - test_mode=test_mode, - ) - await _protected_send(coro, tenant) + await _maybe_send_message(msg, tenant, db, test_mode) async def send_magic_link_email( @@ -100,12 +96,7 @@ async def send_magic_link_email( link=link, **email_args, ) - smtp_provider = smtp.SMTP(db) - coro = smtp_provider.send( - msg, - test_mode=test_mode, - ) - await _protected_send(coro, tenant) + await _maybe_send_message(msg, tenant, db, test_mode) async def send_fake_email(tenant: tenant.Tenant) -> None: @@ -116,6 +107,30 @@ async def noop_coroutine() -> None: await _protected_send(coro, tenant) +async def _maybe_send_message( + msg: EmailMessage, + tenant: tenant.Tenant, + db: Any, + test_mode: bool, +) -> None: + try: + smtp_provider = smtp.SMTP(db) + except errors.ConfigurationError as e: + logger.debug( + "ConfigurationError while instantiating SMTP provider, " + f"sending fake email instead: {e}" + ) + smtp_provider = None + if smtp_provider is None: + coro = send_fake_email(tenant) + else: + coro = smtp_provider.send( + msg, + test_mode=test_mode, + ) + await _protected_send(coro, tenant) + + async def _protected_send( coro: Coroutine[Any, Any, None], tenant: tenant.Tenant ) -> None: diff --git a/edb/testbase/http.py b/edb/testbase/http.py index 4e74eb45cc7..6ab949a92c7 100644 --- a/edb/testbase/http.py +++ b/edb/testbase/http.py @@ -47,7 +47,13 @@ class BaseHttpTest(server.QueryTestCase): @classmethod async def _wait_for_db_config( - cls, config_key, *, server=None, instance_config=False, value=None + cls, + config_key, + *, + server=None, + instance_config=False, + value=None, + is_reset=False, ): dbname = cls.get_database_name() # Wait for the database config changes to propagate to the @@ -68,17 +74,21 @@ async def _wait_for_db_config( path="server-info", ) data = json.loads(rdata) - if 'databases' not in data: + if "databases" not in data: # multi-tenant instance - use the first tenant - data = next(iter(data['tenants'].values())) + data = next(iter(data["tenants"].values())) if instance_config: - config = data['instance_config'] + config = data["instance_config"] + else: + config = data["databases"][dbname]["config"] + if is_reset: + if config_key in config: + raise AssertionError("database config not ready") else: - config = data['databases'][dbname]['config'] - if config_key not in config: - raise AssertionError('database config not ready') - if value and config[config_key] != value: - raise AssertionError(f'database config not ready') + if config_key not in config: + raise AssertionError("database config not ready") + if value and config[config_key] != value: + raise AssertionError("database config not ready") class BaseHttpExtensionTest(BaseHttpTest): @@ -90,25 +100,23 @@ def get_extension_path(cls): def get_api_prefix(cls): extpath = cls.get_extension_path() dbname = cls.get_database_name() - return f'/branch/{dbname}/{extpath}' + return f"/branch/{dbname}/{extpath}" class ExtAuthTestCase(BaseHttpExtensionTest): - - EXTENSIONS = ['pgcrypto', 'auth'] + EXTENSIONS = ["pgcrypto", "auth"] @classmethod def get_extension_path(cls): - return 'ext/auth' + return "ext/auth" class EdgeQLTestCase(BaseHttpExtensionTest): - - EXTENSIONS = ['edgeql_http'] + EXTENSIONS = ["edgeql_http"] @classmethod def get_extension_path(cls): - return 'edgeql' + return "edgeql" def edgeql_query( self, @@ -119,46 +127,44 @@ def edgeql_query( globals=None, origin=None, ): - req_data = { - 'query': query - } + req_data = {"query": query} if use_http_post: if variables is not None: - req_data['variables'] = variables + req_data["variables"] = variables if globals is not None: - req_data['globals'] = globals - req = urllib.request.Request(self.http_addr, method='POST') - req.add_header('Content-Type', 'application/json') - req.add_header('Authorization', self.make_auth_header()) + req_data["globals"] = globals + req = urllib.request.Request(self.http_addr, method="POST") + req.add_header("Content-Type", "application/json") + req.add_header("Authorization", self.make_auth_header()) if origin: - req.add_header('Origin', origin) + req.add_header("Origin", origin) response = urllib.request.urlopen( req, json.dumps(req_data).encode(), context=self.tls_context ) resp_data = json.loads(response.read()) else: if variables is not None: - req_data['variables'] = json.dumps(variables) + req_data["variables"] = json.dumps(variables) if globals is not None: - req_data['globals'] = json.dumps(globals) + req_data["globals"] = json.dumps(globals) req = urllib.request.Request( - f'{self.http_addr}/?{urllib.parse.urlencode(req_data)}', + f"{self.http_addr}/?{urllib.parse.urlencode(req_data)}", ) - req.add_header('Authorization', self.make_auth_header()) + req.add_header("Authorization", self.make_auth_header()) response = urllib.request.urlopen( req, context=self.tls_context, ) resp_data = json.loads(response.read()) - if 'data' in resp_data: - return (resp_data['data'], response) + if "data" in resp_data: + return (resp_data["data"], response) - err = resp_data['error'] + err = resp_data["error"] - ex_msg = err['message'].strip() - ex_code = err['code'] + ex_msg = err["message"].strip() + ex_code = err["code"] raise edgedb.EdgeDBError._from_code(ex_code, ex_msg) @@ -177,7 +183,8 @@ def assert_edgeql_query_result( query, use_http_post=use_http_post, variables=variables, - globals=globals) + globals=globals, + ) if sort is not None: # GQL will always have a single object returned. The data is @@ -185,18 +192,16 @@ def assert_edgeql_query_result( for r in res.values(): assert_data_shape.sort_results(r, sort) - assert_data_shape.assert_data_shape( - res, result, self.fail, message=msg) + assert_data_shape.assert_data_shape(res, result, self.fail, message=msg) return res class GraphQLTestCase(BaseHttpExtensionTest): - - EXTENSIONS = ['graphql'] + EXTENSIONS = ["graphql"] @classmethod def get_extension_path(cls): - return 'graphql' + return "graphql" def graphql_query( self, @@ -208,25 +213,25 @@ def graphql_query( globals=None, deprecated_globals=None, ): - req_data = {'query': query} + req_data = {"query": query} if operation_name is not None: - req_data['operationName'] = operation_name + req_data["operationName"] = operation_name if use_http_post: if variables is not None: - req_data['variables'] = variables + req_data["variables"] = variables if globals is not None: if variables is None: - req_data['variables'] = dict() - req_data['variables']['__globals__'] = globals + req_data["variables"] = dict() + req_data["variables"]["__globals__"] = globals # Support testing the old way of sending globals. if deprecated_globals is not None: - req_data['globals'] = deprecated_globals + req_data["globals"] = deprecated_globals - req = urllib.request.Request(self.http_addr, method='POST') - req.add_header('Content-Type', 'application/json') - req.add_header('Authorization', self.make_auth_header()) + req = urllib.request.Request(self.http_addr, method="POST") + req.add_header("Content-Type", "application/json") + req.add_header("Authorization", self.make_auth_header()) response = urllib.request.urlopen( req, json.dumps(req_data).encode(), context=self.tls_context ) @@ -235,45 +240,48 @@ def graphql_query( if globals is not None: if variables is None: variables = dict() - variables['__globals__'] = globals + variables["__globals__"] = globals # Support testing the old way of sending globals. if deprecated_globals is not None: - req_data['globals'] = json.dumps(deprecated_globals) + req_data["globals"] = json.dumps(deprecated_globals) if variables is not None: - req_data['variables'] = json.dumps(variables) + req_data["variables"] = json.dumps(variables) req = urllib.request.Request( - f'{self.http_addr}/?{urllib.parse.urlencode(req_data)}', + f"{self.http_addr}/?{urllib.parse.urlencode(req_data)}", ) - req.add_header('Authorization', self.make_auth_header()) + req.add_header("Authorization", self.make_auth_header()) response = urllib.request.urlopen( req, context=self.tls_context, ) resp_data = json.loads(response.read()) - if 'data' in resp_data: - return resp_data['data'] + if "data" in resp_data: + return resp_data["data"] - err = resp_data['errors'][0] + err = resp_data["errors"][0] - typename, msg = err['message'].split(':', 1) + typename, msg = err["message"].split(":", 1) msg = msg.strip() try: ex_type = getattr(edgedb, typename) except AttributeError: raise AssertionError( - f'server returned an invalid exception typename: {typename!r}' - f'\n Message: {msg}') + f"server returned an invalid exception typename: {typename!r}" + f"\n Message: {msg}" + ) ex = ex_type(msg) - if 'locations' in err: + if "locations" in err: # XXX Fix this when LSP "location" objects are implemented ex._attrs[base_errors.FIELD_LINE_START] = str( - err['locations'][0]['line']).encode() + err["locations"][0]["line"] + ).encode() ex._attrs[base_errors.FIELD_COLUMN_START] = str( - err['locations'][0]['column']).encode() + err["locations"][0]["column"] + ).encode() raise ex @@ -296,7 +304,8 @@ def assert_graphql_query_result( use_http_post=use_http_post, variables=variables, globals=globals, - deprecated_globals=deprecated_globals) + deprecated_globals=deprecated_globals, + ) if sort is not None: # GQL will always have a single object returned. The data is @@ -304,8 +313,7 @@ def assert_graphql_query_result( for r in res.values(): assert_data_shape.sort_results(r, sort) - assert_data_shape.assert_data_shape( - res, result, self.fail, message=msg) + assert_data_shape.assert_data_shape(res, result, self.fail, message=msg) return res @@ -317,12 +325,12 @@ def get_server_and_path(self) -> tuple[str, str]: def do_GET(self): self.close_connection = False server, path = self.get_server_and_path() - self.server.owner.handle_request('GET', server, path, self) + self.server.owner.handle_request("GET", server, path, self) def do_POST(self): self.close_connection = False server, path = self.get_server_and_path() - self.server.owner.handle_request('POST', server, path, self) + self.server.owner.handle_request("POST", server, path, self) def log_message(self, *args): pass @@ -332,10 +340,9 @@ class MultiHostMockHttpServerHandler(MockHttpServerHandler): def get_server_and_path(self) -> tuple[str, str]: # Path looks like: # http://127.0.0.1:32881/https%3A//slack.com/.well-known/openid-configuration - raw_url = urllib.parse.unquote(self.path.lstrip('/')) + raw_url = urllib.parse.unquote(self.path.lstrip("/")) url = urllib.parse.urlparse(raw_url) - return (f'{url.scheme}://{url.netloc}', - url.path.lstrip('/')) + return (f"{url.scheme}://{url.netloc}", url.path.lstrip("/")) ResponseType = tuple[str, int] | tuple[str, int, dict[str, str]] @@ -384,7 +391,7 @@ def wrapper( | Callable[ [MockHttpServerHandler, RequestDetails], ResponseType ] - ) + ), ): self.routes[(method, server, path)] = handler return handler @@ -408,8 +415,8 @@ def handle_request( parsed_path = urllib.parse.urlparse(path) headers = {k.lower(): v for k, v in dict(handler.headers).items()} query_params = urllib.parse.parse_qs(parsed_path.query) - if 'content-length' in headers: - body = handler.rfile.read(int(headers['content-length'])).decode() + if "content-length" in headers: + body = handler.rfile.read(int(headers["content-length"])).decode() else: body = None @@ -420,8 +427,10 @@ def handle_request( ) self.requests[key].append(request_details) if key not in self.routes: - error_message = (f"No route handler for {key}\n\n" - f"Available routes:\n{self.routes}") + error_message = ( + f"No route handler for {key}\n\n" + f"Available routes:\n{self.routes}" + ) handler.send_error(404, message=error_message) return @@ -458,9 +467,9 @@ def handle_request( ) or accept_header == "*/*" ): - content_type = 'application/json' + content_type = "application/json" elif accept_header.startswith("application/x-www-form-urlencoded"): - content_type = 'application/x-www-form-urlencoded' + content_type = "application/x-www-form-urlencoded" else: handler.send_error( 415, f"Unsupported accept header: {accept_header}" @@ -470,8 +479,8 @@ def handle_request( data = response.encode() handler.send_response(status) - handler.send_header('Content-Type', content_type) - handler.send_header('Content-Length', str(len(data))) + handler.send_header("Content-Type", content_type) + handler.send_header("Content-Length", str(len(data))) if additional_headers is not None: for header, value in additional_headers.items(): handler.send_header(header, value) @@ -479,11 +488,11 @@ def handle_request( handler.wfile.write(data) def start(self): - assert not hasattr(self, '_http_runner') + assert not hasattr(self, "_http_runner") self._http_runner = threading.Thread(target=self._http_worker) self._http_runner.start() self.has_started.wait() - self.url = f'http://{self._address[0]}:{self._address[1]}/' + self.url = f"http://{self._address[0]}:{self._address[1]}/" def __enter__(self): self.start() @@ -491,7 +500,7 @@ def __enter__(self): def _http_worker(self): self._http_server = http.server.HTTPServer( - ('localhost', 0), self.handler_type + ("localhost", 0), self.handler_type ) self._http_server.owner = self self._address = self._http_server.server_address diff --git a/tests/test_http_ext_auth.py b/tests/test_http_ext_auth.py index 149a928e166..1fd6323e935 100644 --- a/tests/test_http_ext_auth.py +++ b/tests/test_http_ext_auth.py @@ -256,7 +256,7 @@ class TestHttpExtAuth(tb.ExtAuthTestCase): }}; CONFIGURE CURRENT DATABASE SET - cfg::current_email_provider_name := "email_hosting_is_easy"; + current_email_provider_name := "email_hosting_is_easy"; CONFIGURE CURRENT DATABASE SET ext::auth::AuthConfig::auth_signing_key := '{SIGNING_KEY}'; @@ -2817,6 +2817,47 @@ async def test_http_auth_ext_local_password_register_form_02(self): self.assertEqual(status, 400) + async def test_http_auth_ext_local_password_register_form_no_smtp(self): + await self.con.query( + """ + CONFIGURE CURRENT DATABASE RESET + current_email_provider_name; + """, + ) + await self._wait_for_db_config( + "cfg::current_email_provider_name", is_reset=True + ) + try: + with self.http_con() as http_con: + email = f"{uuid.uuid4()}@example.com" + form_data = { + "provider": "builtin::local_emailpassword", + "email": email, + "password": "test_password", + "challenge": str(uuid.uuid4()), + } + form_data_encoded = urllib.parse.urlencode(form_data).encode() + + _, _, status = self.http_con_request( + http_con, + None, + path="register", + method="POST", + body=form_data_encoded, + headers={ + "Content-Type": "application/x-www-form-urlencoded" + }, + ) + + self.assertEqual(status, 201) + finally: + await self.con.query( + """ + CONFIGURE CURRENT DATABASE SET + current_email_provider_name := "email_hosting_is_easy"; + """, + ) + async def test_http_auth_ext_local_password_register_json_02(self): with self.http_con() as http_con: provider_name = "builtin::local_emailpassword" From ade85c572c116fd7d53f715cee6d0d45e79ad610 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 20 Jan 2025 11:27:19 -0700 Subject: [PATCH 020/154] Add a full test for notice handling on handshake (#8223) This improves the captive postgres utility to make it more flexible and ensures that we can test a postgres server configured with `client_min_messages=debug5`. --- rust/captive_postgres/src/lib.rs | 332 ++++++++++++++---- .../src/handshake/client_state_machine.rs | 8 +- rust/pgrust/tests/real_postgres.rs | 35 +- 3 files changed, 301 insertions(+), 74 deletions(-) diff --git a/rust/captive_postgres/src/lib.rs b/rust/captive_postgres/src/lib.rs index 03be2d2ff53..885e8c870aa 100644 --- a/rust/captive_postgres/src/lib.rs +++ b/rust/captive_postgres/src/lib.rs @@ -19,6 +19,202 @@ pub const DEFAULT_USERNAME: &str = "username"; pub const DEFAULT_PASSWORD: &str = "password"; pub const DEFAULT_DATABASE: &str = "postgres"; +use std::collections::HashMap; + +#[derive(Debug, Clone, Default)] +pub enum PostgresBinPath { + #[default] + Path, + Specified(PathBuf), +} + +#[derive(Debug)] +pub struct PostgresBuilder { + auth: AuthType, + bin_path: PostgresBinPath, + data_dir: Option, + server_options: HashMap, + ssl_cert_and_key: Option<(PathBuf, PathBuf)>, + unix_enabled: bool, + debug_level: Option, +} + +impl Default for PostgresBuilder { + fn default() -> Self { + Self { + auth: AuthType::Trust, + bin_path: PostgresBinPath::default(), + data_dir: None, + server_options: HashMap::new(), + ssl_cert_and_key: None, + unix_enabled: false, + debug_level: None, + } + } +} + +impl PostgresBuilder { + pub fn new() -> Self { + Self::default() + } + + /// Attempt to configure the builder to use the default postgres binaries. + /// Returns an error if the binaries are not found. + pub fn with_automatic_bin_path(mut self) -> std::io::Result { + let bindir = postgres_bin_dir()?; + self.bin_path = PostgresBinPath::Specified(bindir); + Ok(self) + } + + /// Configures the builder with a quick networking mode. + pub fn with_automatic_mode(mut self, mode: Mode) -> Self { + match mode { + Mode::Tcp => { + // No special configuration needed for TCP mode + } + Mode::TcpSsl => { + let certs_dir = test_data_dir().join("certs"); + let cert = certs_dir.join("server.cert.pem"); + let key = certs_dir.join("server.key.pem"); + self.ssl_cert_and_key = Some((cert, key)); + } + Mode::Unix => { + self.unix_enabled = true; + } + } + self + } + + pub fn auth(mut self, auth: AuthType) -> Self { + self.auth = auth; + self + } + + pub fn bin_path(mut self, bin_path: impl AsRef) -> Self { + self.bin_path = PostgresBinPath::Specified(bin_path.as_ref().to_path_buf()); + self + } + + pub fn data_dir(mut self, data_dir: PathBuf) -> Self { + self.data_dir = Some(data_dir); + self + } + + pub fn debug_level(mut self, debug_level: u8) -> Self { + self.debug_level = Some(debug_level); + self + } + + pub fn server_option(mut self, key: impl AsRef, value: impl AsRef) -> Self { + self.server_options + .insert(key.as_ref().to_string(), value.as_ref().to_string()); + self + } + + pub fn server_options( + mut self, + server_options: impl IntoIterator, impl AsRef)>, + ) -> Self { + for (key, value) in server_options { + self.server_options + .insert(key.as_ref().to_string(), value.as_ref().to_string()); + } + self + } + + pub fn enable_ssl(mut self, cert_path: PathBuf, key_path: PathBuf) -> Self { + self.ssl_cert_and_key = Some((cert_path, key_path)); + self + } + + pub fn enable_unix(mut self) -> Self { + self.unix_enabled = true; + self + } + + pub fn build(self) -> std::io::Result { + let initdb = match &self.bin_path { + PostgresBinPath::Path => "initdb".into(), + PostgresBinPath::Specified(path) => path.join("initdb"), + }; + let postgres = match &self.bin_path { + PostgresBinPath::Path => "postgres".into(), + PostgresBinPath::Specified(path) => path.join("postgres"), + }; + + if !initdb.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("initdb executable not found at {}", initdb.display()), + )); + } + if !postgres.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("postgres executable not found at {}", postgres.display()), + )); + } + + let temp_dir = TempDir::new()?; + let port = EphemeralPort::allocate()?; + let data_dir = self + .data_dir + .unwrap_or_else(|| temp_dir.path().join("data")); + + init_postgres(&initdb, &data_dir, self.auth)?; + let port = port.take(); + + let ssl_config = self.ssl_cert_and_key; + + let (socket_address, socket_path) = if self.unix_enabled { + ( + ListenAddress::Unix(get_unix_socket_path(&data_dir, port)), + Some(&data_dir), + ) + } else { + ( + ListenAddress::Tcp(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port)), + None, + ) + }; + + let tcp_address = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port); + + let mut command = Command::new(postgres); + command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("-D") + .arg(&data_dir) + .arg("-h") + .arg(Ipv4Addr::LOCALHOST.to_string()) + .arg("-F") + .arg("-p") + .arg(port.to_string()); + + if let Some(socket_path) = &socket_path { + command.arg("-k").arg(socket_path); + } + + for (key, value) in self.server_options { + command.arg("-c").arg(format!("{}={}", key, value)); + } + + if let Some(debug_level) = self.debug_level { + command.arg("-d").arg(debug_level.to_string()); + } + + let child = run_postgres(command, &data_dir, socket_path, ssl_config, port)?; + + Ok(PostgresProcess { + child, + socket_address, + tcp_address, + temp_dir, + }) + } +} + #[derive(Debug, Clone)] pub enum ListenAddress { Tcp(SocketAddr), @@ -138,6 +334,7 @@ fn init_postgres(initdb: &Path, data_dir: &Path, auth: AuthType) -> std::io::Res .arg("-U") .arg(DEFAULT_USERNAME); + eprintln!("initdb command: {:?}", command); let output = command.output()?; let status = output.status; @@ -158,28 +355,13 @@ fn init_postgres(initdb: &Path, data_dir: &Path, auth: AuthType) -> std::io::Res } fn run_postgres( - postgres_bin: &Path, + mut command: Command, data_dir: &Path, - socket_path: &Path, + socket_path: Option>, ssl: Option<(PathBuf, PathBuf)>, port: u16, ) -> std::io::Result { - let mut command = Command::new(postgres_bin); - command - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("-D") - .arg(data_dir) - .arg("-k") - .arg(socket_path) - .arg("-h") - .arg(Ipv4Addr::LOCALHOST.to_string()) - .arg("-F") - // Useful for debugging - // .arg("-d") - // .arg("5") - .arg("-p") - .arg(port.to_string()); + let socket_path = socket_path.map(|path| path.as_ref().to_owned()); if let Some((cert_path, key_path)) = ssl { let postgres_cert_path = data_dir.join("server.crt"); @@ -223,11 +405,13 @@ fn run_postgres( let mut tcp_socket: Option = None; let mut unix_socket: Option = None; - let unix_socket_path = get_unix_socket_path(socket_path, port); + let unix_socket_path = socket_path.map(|path| get_unix_socket_path(path, port)); let tcp_socket_addr = std::net::SocketAddr::from((Ipv4Addr::LOCALHOST, port)); + let mut db_ready = false; + let mut network_ready = false; - while start_time.elapsed() < STARTUP_TIMEOUT_DURATION { + while start_time.elapsed() < STARTUP_TIMEOUT_DURATION && !network_ready { std::thread::sleep(HOT_LOOP_INTERVAL); match child.try_wait() { Ok(Some(status)) => { @@ -245,19 +429,17 @@ fn run_postgres( } else { continue; } - if unix_socket.is_none() { - unix_socket = std::os::unix::net::UnixStream::connect(&unix_socket_path).ok(); + if let Some(unix_socket_path) = &unix_socket_path { + if unix_socket.is_none() { + unix_socket = std::os::unix::net::UnixStream::connect(unix_socket_path).ok(); + } } if tcp_socket.is_none() { tcp_socket = std::net::TcpStream::connect(tcp_socket_addr).ok(); } - if unix_socket.is_some() && tcp_socket.is_some() { - break; - } - } - if unix_socket.is_some() && tcp_socket.is_some() { - return Ok(child); + network_ready = + (unix_socket_path.is_none() || unix_socket.is_some()) && tcp_socket.is_some(); } // Print status for TCP/unix sockets @@ -276,6 +458,10 @@ fn run_postgres( eprintln!("Unix socket at {unix_socket_path:?} connection failed"); } + if network_ready { + return Ok(child); + } + Err(std::io::Error::new( std::io::ErrorKind::TimedOut, "PostgreSQL failed to start within 30 seconds", @@ -302,8 +488,8 @@ fn postgres_bin_dir() -> std::io::Result { } } -fn get_unix_socket_path(socket_path: &Path, port: u16) -> PathBuf { - socket_path.join(format!(".s.PGSQL.{}", port)) +fn get_unix_socket_path(socket_path: impl AsRef, port: u16) -> PathBuf { + socket_path.as_ref().join(format!(".s.PGSQL.{}", port)) } #[derive(Debug, Clone, Copy)] @@ -323,6 +509,7 @@ pub fn create_ssl_client() -> Result> { pub struct PostgresProcess { child: std::process::Child, pub socket_address: ListenAddress, + pub tcp_address: SocketAddr, #[allow(unused)] temp_dir: TempDir, } @@ -334,51 +521,54 @@ impl Drop for PostgresProcess { } /// Creates and runs a new Postgres server process in a temporary directory. -pub fn setup_postgres( - auth: AuthType, - mode: Mode, -) -> Result, Box> { - let Ok(bindir) = postgres_bin_dir() else { - println!("Skipping test: postgres bin dir not found"); - return Ok(None); - }; - - let initdb = bindir.join("initdb"); - let postgres = bindir.join("postgres"); +pub fn setup_postgres(auth: AuthType, mode: Mode) -> std::io::Result> { + let builder: PostgresBuilder = PostgresBuilder::new(); - if !initdb.exists() || !postgres.exists() { - println!("Skipping test: initdb or postgres not found"); + let Ok(mut builder) = builder.with_automatic_bin_path() else { + eprintln!("Skipping test: postgres bin dir not found"); return Ok(None); - } - - let temp_dir = TempDir::new()?; - let port = EphemeralPort::allocate()?; - let data_dir = temp_dir.path().join("data"); - - init_postgres(&initdb, &data_dir, auth)?; - let ssl_key = match mode { - Mode::TcpSsl => { - let certs_dir = test_data_dir().join("certs"); - let cert = certs_dir.join("server.cert.pem"); - let key = certs_dir.join("server.key.pem"); - Some((cert, key)) - } - _ => None, }; - let port = port.take(); - let child = run_postgres(&postgres, &data_dir, &data_dir, ssl_key, port)?; + builder = builder.auth(auth).with_automatic_mode(mode); - let socket_address = match mode { - Mode::Unix => ListenAddress::Unix(get_unix_socket_path(&data_dir, port)), - Mode::Tcp | Mode::TcpSsl => { - ListenAddress::Tcp(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port)) - } - }; + let process = builder.build()?; + Ok(Some(process)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_builder_defaults() { + let builder = PostgresBuilder::new(); + assert!(matches!(builder.auth, AuthType::Trust)); + assert!(matches!(builder.bin_path, PostgresBinPath::Path)); + assert!(builder.data_dir.is_none()); + assert_eq!(builder.server_options.len(), 0); + } - Ok(Some(PostgresProcess { - child, - socket_address, - temp_dir, - })) + #[test] + fn test_builder_customization() { + let mut options = HashMap::new(); + options.insert("max_connections", "100"); + + let data_dir = PathBuf::from("/tmp/pg_data"); + let bin_path = PathBuf::from("/usr/local/pgsql/bin"); + + let builder = PostgresBuilder::new() + .auth(AuthType::Md5) + .bin_path(bin_path) + .data_dir(data_dir.clone()) + .server_options(options); + + assert!(matches!(builder.auth, AuthType::Md5)); + assert!(matches!(builder.bin_path, PostgresBinPath::Specified(_))); + assert_eq!(builder.data_dir.unwrap(), data_dir); + assert_eq!( + builder.server_options.get("max_connections").unwrap(), + "100" + ); + } } diff --git a/rust/pgrust/src/handshake/client_state_machine.rs b/rust/pgrust/src/handshake/client_state_machine.rs index 7b7b0dc3cf5..cfba84dda8a 100644 --- a/rust/pgrust/src/handshake/client_state_machine.rs +++ b/rust/pgrust/src/handshake/client_state_machine.rs @@ -93,8 +93,12 @@ pub trait ConnectionStateUpdate: ConnectionStateSend { fn parameter(&mut self, name: &str, value: &str) {} fn cancellation_key(&mut self, pid: i32, key: i32) {} fn state_changed(&mut self, state: ConnectionStateType) {} - fn server_error(&mut self, error: &PgServerError) {} - fn server_notice(&mut self, notice: &PgServerError) {} + fn server_error(&mut self, error: &PgServerError) { + error!("Server error during handshake: {:?}", error); + } + fn server_notice(&mut self, notice: &PgServerError) { + warn!("Server notice during handshake: {:?}", notice); + } fn auth(&mut self, auth: AuthType) {} } diff --git a/rust/pgrust/tests/real_postgres.rs b/rust/pgrust/tests/real_postgres.rs index 0d562e2047e..185bd1bc12a 100644 --- a/rust/pgrust/tests/real_postgres.rs +++ b/rust/pgrust/tests/real_postgres.rs @@ -17,8 +17,41 @@ fn address(address: &ListenAddress) -> ResolvedTarget { } } +/// Ensure that a notice doesn't cause unexpected behavior. +#[test_log::test(tokio::test)] +async fn test_auth_noisy() -> Result<(), Box> { + let Ok(builder) = PostgresBuilder::new().with_automatic_bin_path() else { + return Ok(()); + }; + + let builder = builder + .debug_level(5) + .server_option("client_min_messages", "debug5"); + + let process = builder.build()?; + + let credentials = Credentials { + username: DEFAULT_USERNAME.to_string(), + password: DEFAULT_PASSWORD.to_string(), + database: DEFAULT_DATABASE.to_string(), + server_settings: Default::default(), + }; + + let client = address(&process.socket_address).connect().await?; + + let ssl_requirement = ConnectionSslRequirement::Optional; + + let params = connect_raw_ssl(credentials, ssl_requirement, create_ssl_client()?, client) + .await? + .params() + .clone(); + assert_eq!(params.auth, AuthType::Trust); + + Ok(()) +} + #[rstest] -#[tokio::test] +#[test_log::test(tokio::test)] async fn test_auth_real( #[values(AuthType::Trust, AuthType::Plain, AuthType::Md5, AuthType::ScramSha256)] auth: AuthType, From c2113283a130febabe270ec14f7752d5584ae80d Mon Sep 17 00:00:00 2001 From: Fantix King Date: Tue, 21 Jan 2025 11:41:50 -0500 Subject: [PATCH 021/154] multitenant: retry adding tenants more eagerly (#8236) --- edb/server/multitenant.py | 3 +- tests/test_server_ops.py | 66 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/edb/server/multitenant.py b/edb/server/multitenant.py index cc59e71c30d..afa70a2b8b7 100644 --- a/edb/server/multitenant.py +++ b/edb/server/multitenant.py @@ -46,7 +46,6 @@ from . import server from . import tenant as edbtenant from .compiler_pool import pool as compiler_pool -from .pgcon import errors as pgerrors logger = logging.getLogger("edb.server") @@ -316,7 +315,7 @@ async def _add_tenant(): rloop = retryloop.RetryLoop( backoff=retryloop.exp_backoff(), timeout=300, - ignore=(pgerrors.BackendError, IOError), + ignore=Exception, retry_cb=_warn, ) async for iteration in rloop: diff --git a/tests/test_server_ops.py b/tests/test_server_ops.py index f156bd1dd7d..84c4bd759f0 100644 --- a/tests/test_server_ops.py +++ b/tests/test_server_ops.py @@ -1591,7 +1591,7 @@ async def test_server_ops_multi_tenant(self): cf1, cf2 ) - for i in range(1, 8): + for i in range(1, 9): name = f"_test_server_ops_multi_tenant_{i}" with self.subTest(name, i=i): await getattr(self, name)(mtargs) @@ -1830,6 +1830,70 @@ async def _test_server_ops_multi_tenant_7(self, mtargs: MultiTenantArgs): "sender@host2.com", ) + async def _test_server_ops_multi_tenant_8(self, mtargs: MultiTenantArgs): + # Start with 2 tenants + data = mtargs.sd.fetch_metrics() + self.assertIn( + '\nedgedb_server_mt_tenants_current 2.0\n', + data, + ) + await self._test_server_ops_multi_tenant_1(mtargs) + + with tempfile.TemporaryDirectory() as td: + # Test adding tenant with a non-existing jwt-sub-allowlist-file + tf = tempfile.mktemp(dir=td) + conf = mtargs.conf["1.localhost"].copy() + conf.update({ + "instance-name": "localtest3", + "jwt-sub-allowlist-file": tf, + }) + mtargs.conf["3.localhost"] = conf + mtargs.reload_server() + + # The tenant should not be ready at this moment, while the server + # keeps retrying to add the tenant + args3 = mtargs.args1.copy() + args3["server_hostname"] = "3.localhost" + with self.assertRaises(edgedb.AvailabilityError): + async for tr in self.try_until_succeeds( + ignore=edgedb.AvailabilityError, timeout=3 + ): + async with tr: + conn = await mtargs.sd.connect(**args3) + await conn.aclose() + + # Though, the metrics should reflect the ongoing attempt + data = mtargs.sd.fetch_metrics() + self.assertIn( + '\nedgedb_server_mt_tenant_add_total' + '{tenant="localtest3"} 1.0\n', + data, + ) + self.assertIn( + '\nedgedb_server_mt_tenants_current 2.0\n', + data, + ) + + # Now, create the missing file and the tenant should be added + with open(tf, "w") as f: + f.write("\n") + async for tr in self.try_until_succeeds( + ignore=edgedb.AvailabilityError + ): + async with tr: + conn = await mtargs.sd.connect(**args3) + await conn.aclose() + data = mtargs.sd.fetch_metrics() + self.assertIn( + '\nedgedb_server_mt_tenant_add_total' + '{tenant="localtest3"} 1.0\n', + data, + ) + self.assertIn( + '\nedgedb_server_mt_tenants_current 3.0\n', + data, + ) + class MultiTenantArgs(NamedTuple): srv: tb._EdgeDBServer From a85379feebabb8ab975ee11060613c430b45103a Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Tue, 21 Jan 2025 10:13:06 -0700 Subject: [PATCH 022/154] Add simple clustering to captive_postgres (#8234) In anticipation of work on postgres clustering support, adds an option to create a captive postgres cluster from scratch. This bootstraps a primary and N secondaries, each one streaming the WAL from the primary. The secondary is bootstrapped from `pg_basebackup` over the localhost connection, with a `standby.signal` file. This is what the test output from test_create_cluster looks like: ``` ---- tests::test_create_cluster stdout ---- initdb command: "/devel/github/edgedb/build/postgres/install/bin/initdb" "-D" "/tmp/.tmphZYXZg/data" "-A" "trust" "--pwfile" "/tmp/.tmpNXDTJ3" "-U" "username" "--no-instructions" initdb: exit status: 0 === begin initdb stdout:=== The files belonging to this database system will be owned by user "matt". This user must also own the server process. The database cluster will be initialized with locale "en_US.UTF-8". The default database encoding has accordingly been set to "UTF8". The default text search configuration will be set to "english". Data page checksums are disabled. creating directory /tmp/.tmphZYXZg/data ... ok creating subdirectories ... ok selecting dynamic shared memory implementation ... posix selecting default "max_connections" ... 100 selecting default "shared_buffers" ... 128MB selecting default time zone ... America/Edmonton creating configuration files ... ok running bootstrap script ... ok performing post-bootstrap initialization ... ok syncing data to disk ... ok === end initdb stdout === postgres command: "/devel/github/edgedb/build/postgres/install/bin/postgres" "-D" "/tmp/.tmphZYXZg/data" "-h" "127.0.0.1" "-F" "-p" "34733" "-c" "wal_level=replica" [pg_stderr 151842]: 2025-01-20 10:53:14.237 MST [151842] LOG: starting PostgreSQL 17.2 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, 64-bit [pg_stderr 151842]: 2025-01-20 10:53:14.237 MST [151842] LOG: listening on IPv4 address "127.0.0.1", port 34733 [pg_stderr 151842]: 2025-01-20 10:53:14.237 MST [151842] LOG: listening on Unix socket "/tmp/.s.PGSQL.34733" [pg_stderr 151842]: 2025-01-20 10:53:14.238 MST [151847] LOG: database system was shut down at 2025-01-20 10:53:14 MST [pg_stderr 151842]: 2025-01-20 10:53:14.240 MST [151842] LOG: database system is ready to accept connections Database is ready TCP socket at 127.0.0.1:34733 bound successfully (local address was 127.0.0.1:58350) pg_basebackup command: "/devel/github/edgedb/build/postgres/install/bin/pg_basebackup" "-D" "/tmp/.tmp9SXDzR/data" "-h" "localhost" "-p" "34733" "-U" "username" "-X" "stream" [pg_stderr 151842]: 2025-01-20 10:53:14.334 MST [151845] LOG: checkpoint starting: force wait [pg_stderr 151842]: 2025-01-20 10:53:14.340 MST [151845] LOG: checkpoint complete: wrote 3 buffers (0.0%); 0 WAL file(s) added, 0 removed, 1 recycled; write=0.001 s, sync=0.001 s, total=0.006 s; sync files=0, longest=0.000 s, average=0.000 s; distance=11331 kB, estimate=11331 kB; lsn=0/2000080, redo lsn=0/2000028 pg_basebackup: exit status: 0 pg_basebackup: No output postgres command: "/devel/github/edgedb/build/postgres/install/bin/postgres" "-D" "/tmp/.tmp9SXDzR/data" "-h" "127.0.0.1" "-F" "-p" "42179" "-c" "primary_conninfo=host=localhost port=34733 user=username password=password" "-c" "hot_standby=on" [pg_stderr 151856]: 2025-01-20 10:53:14.387 MST [151856] LOG: starting PostgreSQL 17.2 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, 64-bit [pg_stderr 151856]: 2025-01-20 10:53:14.387 MST [151856] LOG: listening on IPv4 address "127.0.0.1", port 42179 [pg_stderr 151856]: 2025-01-20 10:53:14.387 MST [151856] LOG: listening on Unix socket "/tmp/.s.PGSQL.42179" [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151861] LOG: database system was interrupted; last known up at 2025-01-20 10:53:14 MST [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151861] LOG: starting backup recovery with redo LSN 0/2000028, checkpoint LSN 0/2000080, on timeline ID 1 [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151861] LOG: entering standby mode [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151861] LOG: redo starts at 0/2000028 [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151861] LOG: completed backup recovery with redo LSN 0/2000028 and end LSN 0/2000120 [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151861] LOG: consistent recovery state reached at 0/2000120 [pg_stderr 151856]: 2025-01-20 10:53:14.388 MST [151856] LOG: database system is ready to accept read-only connections [pg_stderr 151856]: 2025-01-20 10:53:14.390 MST [151862] LOG: started streaming WAL from primary at 0/3000000 on timeline 1 Database is ready TCP socket at 127.0.0.1:42179 bound successfully (local address was 127.0.0.1:37644) [pg_stderr 151856]: 2025-01-20 10:53:14.477 MST [151856] LOG: received smart shutdown request [pg_stderr 151856]: 2025-01-20 10:53:14.477 MST [151862] FATAL: terminating walreceiver process due to administrator command [pg_stderr 151856]: 2025-01-20 10:53:14.478 MST [151859] LOG: shutting down [pg_stderr 151856]: 2025-01-20 10:53:14.481 MST [151856] LOG: database system is shut down Process 151856 died gracefully. (ExitStatus(unix_wait_status(0))) [pg_stderr 151842]: 2025-01-20 10:53:14.585 MST [151842] LOG: received smart shutdown request [pg_stderr 151842]: 2025-01-20 10:53:14.586 MST [151842] LOG: background worker "logical replication launcher" (PID 151850) exited with exit code 1 [pg_stderr 151842]: 2025-01-20 10:53:14.586 MST [151845] LOG: shutting down [pg_stderr 151842]: 2025-01-20 10:53:14.586 MST [151845] LOG: checkpoint starting: shutdown immediate [pg_stderr 151842]: 2025-01-20 10:53:14.586 MST [151845] LOG: checkpoint complete: wrote 0 buffers (0.0%); 0 WAL file(s) added, 0 removed, 1 recycled; write=0.001 s, sync=0.001 s, total=0.001 s; sync files=0, longest=0.000 s, average=0.000 s; distance=16384 kB, estimate=16384 kB; lsn=0/3000028, redo lsn=0/3000028 [pg_stderr 151842]: 2025-01-20 10:53:14.590 MST [151842] LOG: database system is shut down Process 151842 died gracefully. (ExitStatus(unix_wait_status(0))) ``` --- Cargo.lock | 1 + rust/captive_postgres/Cargo.toml | 3 +- rust/captive_postgres/src/ephemeral_port.rs | 52 +++ rust/captive_postgres/src/lib.rs | 457 +++++++++++++++----- rust/captive_postgres/src/stdio_reader.rs | 48 ++ 5 files changed, 446 insertions(+), 115 deletions(-) create mode 100644 rust/captive_postgres/src/ephemeral_port.rs create mode 100644 rust/captive_postgres/src/stdio_reader.rs diff --git a/Cargo.lock b/Cargo.lock index 3d95d9a1d55..b49bd696d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,6 +311,7 @@ name = "captive_postgres" version = "0.1.0" dependencies = [ "gel_auth", + "nix", "openssl", "socket2", "tempfile", diff --git a/rust/captive_postgres/Cargo.toml b/rust/captive_postgres/Cargo.toml index 995a4b6d482..7ca25c35c55 100644 --- a/rust/captive_postgres/Cargo.toml +++ b/rust/captive_postgres/Cargo.toml @@ -14,4 +14,5 @@ gel_auth.workspace = true openssl = "0.10.55" tempfile = "3" -socket2 = "0.5.8" +socket2 = { version = "0.5", features = ["all"] } +nix = { version = "0.29", features = ["signal"] } diff --git a/rust/captive_postgres/src/ephemeral_port.rs b/rust/captive_postgres/src/ephemeral_port.rs new file mode 100644 index 00000000000..647dde096e7 --- /dev/null +++ b/rust/captive_postgres/src/ephemeral_port.rs @@ -0,0 +1,52 @@ +use std::net::{Ipv4Addr, TcpListener}; +use std::time::Instant; + +use crate::{HOT_LOOP_INTERVAL, LINGER_DURATION, PORT_RELEASE_TIMEOUT}; + +/// Represents an ephemeral port that can be allocated and released for immediate re-use by another process. +pub struct EphemeralPort { + port: u16, + listener: Option, +} + +impl EphemeralPort { + /// Allocates a new ephemeral port. + /// + /// Returns a Result containing the EphemeralPort if successful, + /// or an IO error if the allocation fails. + pub fn allocate() -> std::io::Result { + let socket = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None)?; + socket.set_reuse_address(true)?; + socket.set_reuse_port(true)?; + socket.set_linger(Some(LINGER_DURATION))?; + socket.bind(&std::net::SocketAddr::from((Ipv4Addr::LOCALHOST, 0)).into())?; + socket.listen(1)?; + let listener = TcpListener::from(socket); + let port = listener.local_addr()?.port(); + Ok(EphemeralPort { + port, + listener: Some(listener), + }) + } + + /// Consumes the EphemeralPort and returns the allocated port number. + pub fn take(self) -> u16 { + // Drop the listener to free up the port + drop(self.listener); + + // Loop until the port is free + let start = Instant::now(); + + // If we can successfully connect to the port, it's not fully closed + while start.elapsed() < PORT_RELEASE_TIMEOUT { + let res = std::net::TcpStream::connect((Ipv4Addr::LOCALHOST, self.port)); + if res.is_err() { + // If connection fails, the port is released + break; + } + std::thread::sleep(HOT_LOOP_INTERVAL); + } + + self.port + } +} diff --git a/rust/captive_postgres/src/lib.rs b/rust/captive_postgres/src/lib.rs index 885e8c870aa..419a87ecfd1 100644 --- a/rust/captive_postgres/src/lib.rs +++ b/rust/captive_postgres/src/lib.rs @@ -1,16 +1,21 @@ -// Constants +use ephemeral_port::EphemeralPort; use gel_auth::AuthType; use openssl::ssl::{Ssl, SslContext, SslMethod}; -use std::io::{BufRead, BufReader, Write}; -use std::net::{Ipv4Addr, SocketAddr, TcpListener}; +use std::io::{BufReader, Write}; +use std::net::{Ipv4Addr, SocketAddr}; +use std::num::NonZeroUsize; use std::os::unix::fs::PermissionsExt; +use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -use std::sync::{Arc, RwLock}; -use std::thread; use std::time::{Duration, Instant}; +use stdio_reader::StdioReader; use tempfile::TempDir; +mod ephemeral_port; +mod stdio_reader; + +// Constants pub const STARTUP_TIMEOUT_DURATION: Duration = Duration::from_secs(30); pub const PORT_RELEASE_TIMEOUT: Duration = Duration::from_secs(30); pub const LINGER_DURATION: Duration = Duration::from_secs(1); @@ -28,7 +33,7 @@ pub enum PostgresBinPath { Specified(PathBuf), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PostgresBuilder { auth: AuthType, bin_path: PostgresBinPath, @@ -37,6 +42,7 @@ pub struct PostgresBuilder { ssl_cert_and_key: Option<(PathBuf, PathBuf)>, unix_enabled: bool, debug_level: Option, + standby_of_port: Option, } impl Default for PostgresBuilder { @@ -49,6 +55,7 @@ impl Default for PostgresBuilder { ssl_cert_and_key: None, unix_enabled: false, debug_level: None, + standby_of_port: None, } } } @@ -132,6 +139,11 @@ impl PostgresBuilder { self } + pub fn enable_standby_of(mut self, port: u16) -> Self { + self.standby_of_port = Some(port); + self + } + pub fn build(self) -> std::io::Result { let initdb = match &self.bin_path { PostgresBinPath::Path => "initdb".into(), @@ -141,6 +153,10 @@ impl PostgresBuilder { PostgresBinPath::Path => "postgres".into(), PostgresBinPath::Specified(path) => path.join("postgres"), }; + let pg_basebackup = match &self.bin_path { + PostgresBinPath::Path => "pg_basebackup".into(), + PostgresBinPath::Specified(path) => path.join("pg_basebackup"), + }; if !initdb.exists() { return Err(std::io::Error::new( @@ -154,6 +170,15 @@ impl PostgresBuilder { format!("postgres executable not found at {}", postgres.display()), )); } + if !pg_basebackup.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!( + "pg_basebackup executable not found at {}", + pg_basebackup.display() + ), + )); + } let temp_dir = TempDir::new()?; let port = EphemeralPort::allocate()?; @@ -161,7 +186,15 @@ impl PostgresBuilder { .data_dir .unwrap_or_else(|| temp_dir.path().join("data")); - init_postgres(&initdb, &data_dir, self.auth)?; + // Create a standby signal file if requested + if let Some(standby_of_port) = self.standby_of_port { + run_pgbasebackup(&pg_basebackup, &data_dir, "localhost", standby_of_port)?; + let standby_signal_path = data_dir.join("standby.signal"); + std::fs::write(&standby_signal_path, "")?; + } else { + init_postgres(&initdb, &data_dir, self.auth)?; + } + let port = port.take(); let ssl_config = self.ssl_cert_and_key; @@ -207,7 +240,7 @@ impl PostgresBuilder { let child = run_postgres(command, &data_dir, socket_path, ssl_config, port)?; Ok(PostgresProcess { - child, + child: Some(child), socket_address, tcp_address, temp_dir, @@ -222,96 +255,72 @@ pub enum ListenAddress { Unix(PathBuf), } -/// Represents an ephemeral port that can be allocated and released for immediate re-use by another process. -struct EphemeralPort { - port: u16, - listener: Option, -} +fn spawn(command: &mut Command) -> std::io::Result<()> { + // Set the process group to 0 to ensure that the child process is killed when the parent process exits. + command.process_group(0); -impl EphemeralPort { - /// Allocates a new ephemeral port. - /// - /// Returns a Result containing the EphemeralPort if successful, - /// or an IO error if the allocation fails. - fn allocate() -> std::io::Result { - let socket = socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::STREAM, None)?; - socket.set_reuse_address(true)?; - socket.set_reuse_port(true)?; - socket.set_linger(Some(LINGER_DURATION))?; - socket.bind(&std::net::SocketAddr::from((Ipv4Addr::LOCALHOST, 0)).into())?; - socket.listen(1)?; - let listener = TcpListener::from(socket); - let port = listener.local_addr()?.port(); - Ok(EphemeralPort { - port, - listener: Some(listener), - }) - } + let program = Path::new(command.get_program()) + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); - /// Consumes the EphemeralPort and returns the allocated port number. - fn take(self) -> u16 { - // Drop the listener to free up the port - drop(self.listener); + eprintln!("{program} command:\n {:?}", command); + let command = command.spawn()?; + let output = std::thread::scope(|s| { + use nix::sys::signal::{self, Signal}; + use nix::unistd::Pid; - // Loop until the port is free + let pid = Pid::from_raw(command.id() as _); + let handle = s.spawn(|| command.wait_with_output()); let start = Instant::now(); - - // If we can successfully connect to the port, it's not fully closed - while start.elapsed() < PORT_RELEASE_TIMEOUT { - let res = std::net::TcpStream::connect((Ipv4Addr::LOCALHOST, self.port)); - if res.is_err() { - // If connection fails, the port is released - break; + while start.elapsed() < Duration::from_secs(30) { + if handle.is_finished() { + let handle = handle.join().map_err(|e| { + std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")) + })??; + return Ok(handle); } std::thread::sleep(HOT_LOOP_INTERVAL); } - - self.port - } -} - -struct StdioReader { - output: Arc>, -} - -impl StdioReader { - fn spawn(reader: R, prefix: &'static str) -> Self { - let output = Arc::new(RwLock::new(String::new())); - let output_clone = Arc::clone(&output); - - thread::spawn(move || { - let mut buf_reader = std::io::BufReader::new(reader); - loop { - let mut line = String::new(); - match buf_reader.read_line(&mut line) { - Ok(0) => break, - Ok(_) => { - if let Ok(mut output) = output_clone.write() { - output.push_str(&line); - } - eprint!("[{}]: {}", prefix, line); - } - Err(e) => { - let error_line = format!("Error reading {}: {}\n", prefix, e); - if let Ok(mut output) = output_clone.write() { - output.push_str(&error_line); - } - eprintln!("{}", error_line); - } - } - } - }); - - StdioReader { output } + eprintln!("Command timed out after 30 seconds. Sending SIGKILL."); + signal::kill(pid, Signal::SIGKILL)?; + handle + .join() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))? + })?; + eprintln!("{program}: {}", output.status); + let status = output.status; + let output_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let error_str = String::from_utf8_lossy(&output.stderr).trim().to_string(); + + if !output_str.is_empty() { + eprintln!("=== begin {} stdout:===", program); + eprintln!("{}", output_str); + if !output_str.ends_with('\n') { + eprintln!(); + } + eprintln!("=== end {} stdout ===", program); } - - fn contains(&self, s: &str) -> bool { - if let Ok(output) = self.output.read() { - output.contains(s) - } else { - false + if !error_str.is_empty() { + eprintln!("=== begin {} stderr:===", program); + eprintln!("{}", error_str); + if !error_str.ends_with('\n') { + eprintln!(); } + eprintln!("=== end {} stderr ===", program); + } + if output_str.is_empty() && error_str.is_empty() { + eprintln!("{program}: No output\n"); + } + if !status.success() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("{program} failed with: {}", status), + )); } + + Ok(()) } fn init_postgres(initdb: &Path, data_dir: &Path, auth: AuthType) -> std::io::Result<()> { @@ -332,25 +341,41 @@ fn init_postgres(initdb: &Path, data_dir: &Path, auth: AuthType) -> std::io::Res .arg("--pwfile") .arg(pwfile.path()) .arg("-U") - .arg(DEFAULT_USERNAME); + .arg(DEFAULT_USERNAME) + .arg("--no-instructions"); - eprintln!("initdb command: {:?}", command); - let output = command.output()?; + spawn(&mut command)?; - let status = output.status; - let output_str = String::from_utf8_lossy(&output.stdout).to_string(); - let error_str = String::from_utf8_lossy(&output.stderr).to_string(); - - eprintln!("initdb stdout:\n{}", output_str); - eprintln!("initdb stderr:\n{}", error_str); + Ok(()) +} - if !status.success() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "initdb command failed", - )); - } +fn run_pgbasebackup( + pg_basebackup: &Path, + data_dir: &Path, + host: &str, + port: u16, +) -> std::io::Result<()> { + let mut command = Command::new(pg_basebackup); + // This works for testing purposes but putting passwords in the environment + // is usually bad practice. + // + // "Use of this environment variable is not recommended for security + // reasons" + command.env("PGPASSWORD", DEFAULT_PASSWORD); + command + .arg("-D") + .arg(data_dir) + .arg("-h") + .arg(host) + .arg("-p") + .arg(port.to_string()) + .arg("-U") + .arg(DEFAULT_USERNAME) + .arg("-X") + .arg("stream") + .arg("-w"); + spawn(&mut command)?; Ok(()) } @@ -393,12 +418,14 @@ fn run_postgres( command.arg("-l"); } + command.process_group(0); + eprintln!("postgres command:\n {:?}", command); let mut child = command.spawn()?; let stdout_reader = BufReader::new(child.stdout.take().expect("Failed to capture stdout")); - let _ = StdioReader::spawn(stdout_reader, "stdout"); + let _ = StdioReader::spawn(stdout_reader, format!("pg_stdout {}", child.id())); let stderr_reader = BufReader::new(child.stderr.take().expect("Failed to capture stderr")); - let stderr_reader = StdioReader::spawn(stderr_reader, "stderr"); + let stderr_reader = StdioReader::spawn(stderr_reader, format!("pg_stderr {}", child.id())); let start_time = Instant::now(); @@ -423,7 +450,7 @@ fn run_postgres( Err(e) => return Err(e), _ => {} } - if !db_ready && stderr_reader.contains("database system is ready to accept connections") { + if !db_ready && stderr_reader.contains("database system is ready to accept ") { eprintln!("Database is ready"); db_ready = true; } else { @@ -445,17 +472,19 @@ fn run_postgres( // Print status for TCP/unix sockets if let Some(tcp) = &tcp_socket { eprintln!( - "TCP socket at {tcp_socket_addr:?} bound successfully on {}", + "TCP socket at {tcp_socket_addr:?} bound successfully (local address was {})", tcp.local_addr()? ); } else { eprintln!("TCP socket at {tcp_socket_addr:?} binding failed"); } - if unix_socket.is_some() { - eprintln!("Unix socket at {unix_socket_path:?} connected successfully"); - } else { - eprintln!("Unix socket at {unix_socket_path:?} connection failed"); + if let Some(unix_socket_path) = &unix_socket_path { + if unix_socket.is_some() { + eprintln!("Unix socket at {unix_socket_path:?} connected successfully"); + } else { + eprintln!("Unix socket at {unix_socket_path:?} connection failed"); + } } if network_ready { @@ -506,17 +535,162 @@ pub fn create_ssl_client() -> Result> { Ok(ssl) } +/// The signal to send to the server to shut it down. +/// +/// +#[derive(Debug, Clone, Copy)] +pub enum ShutdownSignal { + /// "After receiving SIGTERM, the server disallows new connections, but lets + /// existing sessions end their work normally. It shuts down only after all + /// of the sessions terminate normally. This is the Smart Shutdown." + Smart, + /// "The server disallows new connections and sends all existing server + /// processes SIGTERM, which will cause them to abort their current + /// transactions and exit promptly. It then waits for the server processes + /// to exit and finally shuts down. This is the Fast Shutdown." + Fast, + /// "This is the Immediate Shutdown, which will cause the postmaster process + /// to send a SIGQUIT to all child processes and exit immediately, without + /// properly shutting itself down. The child processes likewise exit + /// immediately upon receiving SIGQUIT. This will lead to recovery (by + /// replaying the WAL log) upon next start-up. This is recommended only in + /// emergencies." + Immediate, + /// "It is best not to use SIGKILL to shut down the server. Doing so will + /// prevent the server from releasing shared memory and semaphores, which + /// may then have to be done manually before a new server can be started. + /// Furthermore, SIGKILL kills the postmaster process without letting it + /// relay the signal to its subprocesses, so it will be necessary to kill + /// the individual subprocesses by hand as well." + Forceful, +} + +#[derive(Debug)] +pub struct PostgresCluster { + primary: PostgresProcess, + standbys: Vec, +} + +impl PostgresCluster { + pub fn shutdown_timeout( + self, + timeout: Duration, + signal: ShutdownSignal, + ) -> Result<(), Vec> { + let mut failed = Vec::new(); + for standby in self.standbys { + if let Err(e) = standby.shutdown_timeout(timeout, signal) { + failed.push(e); + } + } + if let Err(e) = self.primary.shutdown_timeout(timeout, signal) { + failed.push(e); + } + if failed.is_empty() { + Ok(()) + } else { + Err(failed) + } + } +} + +#[derive(Debug)] pub struct PostgresProcess { - child: std::process::Child, + child: Option, pub socket_address: ListenAddress, pub tcp_address: SocketAddr, #[allow(unused)] temp_dir: TempDir, } +impl PostgresProcess { + fn child(&self) -> &std::process::Child { + self.child.as_ref().unwrap() + } + + fn child_mut(&mut self) -> &mut std::process::Child { + self.child.as_mut().unwrap() + } + + pub fn notify_shutdown(&mut self, signal: ShutdownSignal) -> std::io::Result<()> { + use nix::sys::signal::{self, Signal}; + use nix::unistd::Pid; + + let id = Pid::from_raw(self.child().id() as _); + // https://www.postgresql.org/docs/8.1/postmaster-shutdown.html + match signal { + ShutdownSignal::Smart => signal::kill(id, Signal::SIGTERM)?, + ShutdownSignal::Fast => signal::kill(id, Signal::SIGINT)?, + ShutdownSignal::Immediate => signal::kill(id, Signal::SIGQUIT)?, + ShutdownSignal::Forceful => signal::kill(id, Signal::SIGKILL)?, + } + Ok(()) + } + + pub fn try_wait(&mut self) -> std::io::Result> { + self.child_mut().try_wait() + } + + /// Try to shut down, waiting up to `timeout` for the process to exit. + pub fn shutdown_timeout( + mut self, + timeout: Duration, + signal: ShutdownSignal, + ) -> Result { + _ = self.notify_shutdown(signal); + + let id = self.child().id(); + + let start = Instant::now(); + while start.elapsed() < timeout { + if let Ok(Some(exit)) = self.child_mut().try_wait() { + self.child = None; + eprintln!("Process {id} died gracefully. ({exit:?})"); + return Ok(exit); + } + std::thread::sleep(HOT_LOOP_INTERVAL); + } + Err(self) + } +} + impl Drop for PostgresProcess { fn drop(&mut self) { - let _ = self.child.kill(); + use nix::sys::signal::{self, Signal}; + use nix::unistd::Pid; + + let Some(mut child) = self.child.take() else { + return; + }; + + // Create a thread to send SIGQUIT to the child process. The thread will not block + // process exit. + + let id = Pid::from_raw(child.id() as _); + if let Ok(Some(_)) = child.try_wait() { + eprintln!("Process {id} already exited (crashed?)."); + return; + } + if let Err(e) = signal::kill(id, Signal::SIGQUIT) { + eprintln!("Failed to send SIGQUIT to process {id}: {e:?}"); + } + + let builder = std::thread::Builder::new().name("postgres-shutdown-signal".into()); + builder + .spawn(move || { + // Instead of sleeping, loop and check if the child process has exited every 100ms for up to 10 seconds. + let start = Instant::now(); + while start.elapsed() < std::time::Duration::from_secs(10) { + if let Ok(Some(_)) = child.try_wait() { + eprintln!("Process {id} died gracefully."); + return; + } + std::thread::sleep(HOT_LOOP_INTERVAL); + } + eprintln!("Process {id} did not die gracefully. Sending SIGKILL."); + _ = signal::kill(id, Signal::SIGKILL); + }) + .unwrap(); } } @@ -535,10 +709,53 @@ pub fn setup_postgres(auth: AuthType, mode: Mode) -> std::io::Result std::io::Result> { + let builder: PostgresBuilder = PostgresBuilder::new(); + + let Ok(mut builder) = builder.with_automatic_bin_path() else { + eprintln!("Skipping test: postgres bin dir not found"); + return Ok(None); + }; + + builder = builder.auth(auth).with_automatic_mode(Mode::Tcp); + + // Primary requires the following postgres settings: + // - wal_level = replica + + let primary = builder + .clone() + .server_option("wal_level", "replica") + .build()?; + let primary_port = primary.tcp_address.port(); + + let mut cluster = PostgresCluster { + primary, + standbys: vec![], + }; + + // Standby requires the following postgres settings: + // - primary_conninfo = 'host=localhost port= user=postgres password=password' + // - hot_standby = on + + for _ in 0..size.get() - 1 { + let builder = builder.clone() + .server_option("primary_conninfo", format!("host=localhost port={primary_port} user={DEFAULT_USERNAME} password={DEFAULT_PASSWORD}")) + .server_option("hot_standby", "on") + .enable_standby_of(primary_port); + let standby = builder.build()?; + cluster.standbys.push(standby); + } + + Ok(Some(cluster)) +} + #[cfg(test)] mod tests { use super::*; - use std::path::PathBuf; + use std::{num::NonZeroUsize, path::PathBuf}; #[test] fn test_builder_defaults() { @@ -571,4 +788,16 @@ mod tests { "100" ); } + + #[test] + fn test_create_cluster() { + let Some(cluster) = create_cluster(AuthType::Md5, NonZeroUsize::new(2).unwrap()).unwrap() + else { + return; + }; + assert_eq!(cluster.standbys.len(), 1); + cluster + .shutdown_timeout(Duration::from_secs(10), ShutdownSignal::Smart) + .unwrap(); + } } diff --git a/rust/captive_postgres/src/stdio_reader.rs b/rust/captive_postgres/src/stdio_reader.rs new file mode 100644 index 00000000000..0ae2ec99be0 --- /dev/null +++ b/rust/captive_postgres/src/stdio_reader.rs @@ -0,0 +1,48 @@ +use std::io::BufRead; +use std::sync::{Arc, RwLock}; +use std::thread; + +pub struct StdioReader { + output: Arc>, +} + +impl StdioReader { + pub fn spawn(reader: R, prefix: impl Into) -> Self { + let prefix = prefix.into(); + let output = Arc::new(RwLock::new(String::new())); + let output_clone = Arc::clone(&output); + + thread::spawn(move || { + let mut buf_reader = std::io::BufReader::new(reader); + loop { + let mut line = String::new(); + match buf_reader.read_line(&mut line) { + Ok(0) => break, + Ok(_) => { + if let Ok(mut output) = output_clone.write() { + output.push_str(&line); + } + eprint!("[{}]: {}", prefix, line); + } + Err(e) => { + let error_line = format!("Error reading {}: {}\n", prefix, e); + if let Ok(mut output) = output_clone.write() { + output.push_str(&error_line); + } + eprintln!("{}", error_line); + } + } + } + }); + + StdioReader { output } + } + + pub fn contains(&self, s: &str) -> bool { + if let Ok(output) = self.output.read() { + output.contains(s) + } else { + false + } + } +} From 2122497cdaebde2f7868d527f38c5f12397d2a71 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 21 Jan 2025 12:32:28 -0800 Subject: [PATCH 023/154] Fix a ordering based flake in test_edgeql_triggers_mixed_all_02 (#8226) --- tests/test_edgeql_triggers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_edgeql_triggers.py b/tests/test_edgeql_triggers.py index 7104b0aeb9d..e0d52826909 100644 --- a/tests/test_edgeql_triggers.py +++ b/tests/test_edgeql_triggers.py @@ -471,7 +471,7 @@ async def test_edgeql_triggers_mixed_all_02(self): {"name": "old", "notes": {"b!"}}, {"name": "old", "notes": {"d"}}, {"name": "old", "notes": {"c", "e"}}, - {"name": "old", "notes": ["c!", "e!", "f!"]}, + {"name": "old", "notes": {"c!", "e!", "f!"}}, ]) await self.assert_query_result( From e6c12f7474bcbaa18e227780805977c784129997 Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus Date: Tue, 21 Jan 2025 13:49:41 -0800 Subject: [PATCH 024/154] Allow dropping isolation level to REPEATABLE READ in a READ ONLY tx (#8237) This re-enabled REPEATABLE READ isolation in explicit transaction blocks, but only in those that are READ ONLY. Anomalies observed in read statements are normal non-serializability anomalies, so with an explicit opt-in it is a reasonable tradeoff between lessening the transaction serialization load and consistency. --- edb/edgeql-parser/src/keywords.rs | 1 + edb/edgeql/parser/grammar/statements.py | 4 ++++ edb/server/compiler/compiler.py | 13 +++++++++++ tests/test_server_proto.py | 30 +++++++++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/edb/edgeql-parser/src/keywords.rs b/edb/edgeql-parser/src/keywords.rs index e79393ea8c3..eb416dbd2fc 100644 --- a/edb/edgeql-parser/src/keywords.rs +++ b/edb/edgeql-parser/src/keywords.rs @@ -79,6 +79,7 @@ pub const UNRESERVED_KEYWORDS: phf::Set<&str> = phf_set!( "reject", "release", "rename", + "repeatable", "required", "reset", "restrict", diff --git a/edb/edgeql/parser/grammar/statements.py b/edb/edgeql/parser/grammar/statements.py index dd3cbef702e..3994896a155 100644 --- a/edb/edgeql/parser/grammar/statements.py +++ b/edb/edgeql/parser/grammar/statements.py @@ -66,6 +66,10 @@ def reduce_ISOLATION_SERIALIZABLE(self, *kids): self.val = (qltypes.TransactionIsolationLevel.SERIALIZABLE, kids[0].span) + def reduce_ISOLATION_REPEATABLE_READ(self, *kids): + self.val = (qltypes.TransactionIsolationLevel.REPEATABLE_READ, + kids[0].span) + def reduce_READ_WRITE(self, *kids): self.val = (qltypes.TransactionAccessMode.READ_WRITE, kids[0].span) diff --git a/edb/server/compiler/compiler.py b/edb/server/compiler/compiler.py index 1206bbcba82..922d7ecb445 100644 --- a/edb/server/compiler/compiler.py +++ b/edb/server/compiler/compiler.py @@ -2153,6 +2153,19 @@ def _compile_ql_transaction( ctx.state.start_tx() sqls = 'START TRANSACTION' + iso = ql.isolation + if iso is not None: + if ( + iso is not qltypes.TransactionIsolationLevel.SERIALIZABLE + and ql.access is not qltypes.TransactionAccessMode.READ_ONLY + ): + raise errors.TransactionError( + f"{iso.value} transaction isolation level is only " + "supported in read-only transactions", + span=ql.span, + hint=f"specify READ ONLY access mode", + ) + sqls += f' ISOLATION LEVEL {iso.value}' if ql.access is not None: sqls += f' {ql.access.value}' if ql.deferrable is not None: diff --git a/tests/test_server_proto.py b/tests/test_server_proto.py index 42f20abab3a..49e5abd1071 100644 --- a/tests/test_server_proto.py +++ b/tests/test_server_proto.py @@ -1577,6 +1577,36 @@ async def test_server_proto_tx_07(self): await self.con.query('SELECT 42'), [42]) + async def test_server_proto_tx_08(self): + try: + await self.con.query(''' + START TRANSACTION ISOLATION REPEATABLE READ, READ ONLY; + ''') + + self.assertEqual( + await self.con.query( + 'select sys::get_transaction_isolation();', + ), + ["RepeatableRead"], + ) + finally: + await self.con.query(f''' + ROLLBACK; + ''') + + async def test_server_proto_tx_09(self): + try: + with self.assertRaisesRegex( + edgedb.TransactionError, + 'only supported in read-only transactions'): + await self.con.query(''' + START TRANSACTION ISOLATION REPEATABLE READ; + ''') + finally: + await self.con.query(f''' + ROLLBACK; + ''') + async def test_server_proto_tx_10(self): # Basic test that ROLLBACK works on SET ALIAS changes. From 729e0f7f7697381f3ee1e78269a7229de40672c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Wed, 22 Jan 2025 09:21:18 +0100 Subject: [PATCH 025/154] Fix sql value functions in rel vars over SQL adapter (#8235) --- edb/pgsql/codegen.py | 22 +--------------------- edb/pgsql/common.py | 23 +++++++++++++++++++++++ edb/pgsql/resolver/range_var.py | 28 ++++++++++++++++++++++++++++ tests/test_sql_query.py | 4 ++++ 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/edb/pgsql/codegen.py b/edb/pgsql/codegen.py index b07061dee9f..64031254e00 100644 --- a/edb/pgsql/codegen.py +++ b/edb/pgsql/codegen.py @@ -1130,27 +1130,7 @@ def visit_DeallocateStmt(self, node: pgast.DeallocateStmt) -> None: self.write(f"DEALLOCATE {common.quote_ident(node.name)}") def visit_SQLValueFunction(self, node: pgast.SQLValueFunction) -> None: - from edb.pgsql.ast import SQLValueFunctionOP as op - - names = { - op.CURRENT_DATE: "current_date", - op.CURRENT_TIME: "current_time", - op.CURRENT_TIME_N: "current_time", - op.CURRENT_TIMESTAMP: "current_timestamp", - op.CURRENT_TIMESTAMP_N: "current_timestamp", - op.LOCALTIME: "localtime", - op.LOCALTIME_N: "localtime", - op.LOCALTIMESTAMP: "localtimestamp", - op.LOCALTIMESTAMP_N: "localtimestamp", - op.CURRENT_ROLE: "current_role", - op.CURRENT_USER: "current_user", - op.USER: "user", - op.SESSION_USER: "session_user", - op.CURRENT_CATALOG: "current_catalog", - op.CURRENT_SCHEMA: "current_schema", - } - - self.write(names[node.op]) + self.write(common.get_sql_value_function_op(node.op)) if node.arg: self.write("(") self.visit(node.arg) diff --git a/edb/pgsql/common.py b/edb/pgsql/common.py index bac2f1d84b7..864dd8204fa 100644 --- a/edb/pgsql/common.py +++ b/edb/pgsql/common.py @@ -586,3 +586,26 @@ def get_object_from_backend_name(schema, metaclass, name, *, aspect=None): else: raise ValueError( f'cannot determine object from backend name for {metaclass!r}') + + +def get_sql_value_function_op(op: pgast.SQLValueFunctionOP) -> str: + from edb.pgsql.ast import SQLValueFunctionOP as OP + + NAMES = { + OP.CURRENT_DATE: "current_date", + OP.CURRENT_TIME: "current_time", + OP.CURRENT_TIME_N: "current_time", + OP.CURRENT_TIMESTAMP: "current_timestamp", + OP.CURRENT_TIMESTAMP_N: "current_timestamp", + OP.LOCALTIME: "localtime", + OP.LOCALTIME_N: "localtime", + OP.LOCALTIMESTAMP: "localtimestamp", + OP.LOCALTIMESTAMP_N: "localtimestamp", + OP.CURRENT_ROLE: "current_role", + OP.CURRENT_USER: "current_user", + OP.USER: "user", + OP.SESSION_USER: "session_user", + OP.CURRENT_CATALOG: "current_catalog", + OP.CURRENT_SCHEMA: "current_schema", + } + return NAMES[op] diff --git a/edb/pgsql/resolver/range_var.py b/edb/pgsql/resolver/range_var.py index 63b1eacd4a8..9e9ff8236a3 100644 --- a/edb/pgsql/resolver/range_var.py +++ b/edb/pgsql/resolver/range_var.py @@ -26,6 +26,7 @@ from edb.common.parsing import Span from edb.pgsql import ast as pgast +from edb.pgsql import common as pgcommon from edb.pgsql.compiler import astutils as pgastutils from . import dispatch @@ -311,6 +312,32 @@ def _resolve_RangeFunction( else: col_names.append(name) functions.append(dispatch.resolve(function, ctx=subctx)) + case pgast.SQLValueFunction(op=op): + # If SQLValueFunction gets statically evaluated, we need to + # wrap it into a subquery, otherwise it is syntactically + # incorrect. E.g. `SELECT * FROM current_user`, should be + # compiled to `SELECT * FROM (SELECT 'admin')` + + val = dispatch.resolve(function, ctx=subctx) + + name = pgcommon.get_sql_value_function_op(op) + range = pgast.RangeSubselect( + subquery=pgast.SelectStmt( + target_list=[pgast.ResTarget(val=val, name=name)] + ), + alias=pgast.Alias( + aliasname=alias.aliasname, + colnames=[name], + ), + ) + + column = context.Column( + name=name, + kind=context.ColumnByName(reference_as=name), + ) + table = context.Table(columns=[column]) + + return range, table case _: functions.append(dispatch.resolve(function, ctx=subctx)) @@ -374,6 +401,7 @@ def _zip_column_alias( if len(columns) != len(alias.colnames): from edb.server.pgcon import errors as pgerror + raise errors.QueryError( f'Table alias for `{alias.aliasname}` contains ' f'{len(alias.colnames)} columns, but the query resolves to ' diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index ee017d6324f..145de667994 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -926,6 +926,10 @@ async def test_sql_query_44(self): ): await self.squery_values('SELECT name FROM User') + val = await self.scon.fetch('SELECT * FROM User') + self.assert_shape(val, 1, ['user']) + self.assertEqual(tuple(val[0].values()), ('admin',)) + async def test_sql_query_45(self): res = await self.scon.fetch('SELECT 1 AS a, 2 AS a') self.assert_shape(res, 1, ['a', 'a']) From 223d04221e47a692515521070d4ab96701d60348 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 22 Jan 2025 14:30:03 -0800 Subject: [PATCH 026/154] Fix dump and MIGRATION REWRITE when there are many (>1000) migrations (#8240) Currently those paths rely on topological sorting of the migration history. Our topo sort has various bells and whistles that would make it irritating to rewrite it non-recursively (not impossible, of course; there is a theorem to that effect); instead, order the migrations in a more obvious way. It takes about a minute to set up that many migrations, so I haven't added a test. If we think it is worth it I can, though. --- edb/schema/ddl.py | 15 ++++++--------- edb/schema/migrations.py | 24 ++++++++++++++++++++++++ edb/server/compiler/ddl.py | 22 ++++++++++++++++------ tests/test_edgeql_data_migration.py | 13 +++++++++++++ 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/edb/schema/ddl.py b/edb/schema/ddl.py index 284b880e622..3b8278917bb 100644 --- a/edb/schema/ddl.py +++ b/edb/schema/ddl.py @@ -79,7 +79,6 @@ def delta_schemas( include_module_diff: bool=True, include_std_diff: bool=False, include_derived_types: bool=True, - include_migrations: bool=False, include_extensions: bool=False, linearize_delta: bool=True, descriptive_mode: bool=False, @@ -133,9 +132,6 @@ def delta_schemas( include_derived_types: Whether to include derived types, like unions, in the diff. - include_migrations: - Whether to include migrations in the diff. - linearize_delta: Whether the resulting diff should be properly ordered using the dependencies between objects. @@ -296,6 +292,7 @@ def _filter(schema: s_schema.Schema, obj: so.Object) -> bool: s_mod.Module, s_func.Parameter, s_pseudo.PseudoType, + s_migr.Migration, ) schemaclasses = [ @@ -304,10 +301,6 @@ def _filter(schema: s_schema.Schema, obj: so.Object) -> bool: if ( not issubclass(schemacls, excluded_classes) and not schemacls.is_abstract() - and ( - include_migrations - or not issubclass(schemacls, s_migr.Migration) - ) ) ] @@ -996,8 +989,12 @@ def ddl_text_from_schema( include_module_diff=include_module_ddl, include_std_diff=include_std_ddl, include_derived_types=False, - include_migrations=include_migrations, ) + if include_migrations: + context = so.ComparisonContext() + for mig in s_migr.get_ordered_migrations(schema): + diff.add(mig.as_create_delta(schema, context)) + return ddl_text_from_delta(None, schema, diff, include_ext_version=not include_migrations) diff --git a/edb/schema/migrations.py b/edb/schema/migrations.py index a1b79eb59c1..9a59240187d 100644 --- a/edb/schema/migrations.py +++ b/edb/schema/migrations.py @@ -287,3 +287,27 @@ class AlterMigration(MigrationCommand, sd.AlterObject[Migration]): class DeleteMigration(MigrationCommand, sd.DeleteObject[Migration]): astnode = qlast.DropMigration + + +def get_ordered_migrations( + schema: s_schema.Schema, +) -> list[Migration]: + '''Get all the migrations, in order. + + It would be nice if our toposort could do this for us, but + toposort is implemented recursively, and it would be a pain to + change that. + + ''' + output = [] + mig = schema.get_last_migration() + while mig: + output.append(mig) + + parents = mig.get_parents(schema).objects(schema) + assert len(parents) <= 1, "only one parent supported currently" + mig = parents[0] if parents else None + + output.reverse() + + return output diff --git a/edb/server/compiler/ddl.py b/edb/server/compiler/ddl.py index f6ba698501b..1991466e92c 100644 --- a/edb/server/compiler/ddl.py +++ b/edb/server/compiler/ddl.py @@ -396,7 +396,20 @@ def _process_delta( compiler.compile_schema_storage_in_delta( ctx, pgdelta, subblock, context=context ) - if not ctx.bootstrap_mode: + + # Performance hack; we really want trivial migration commands + # (that only mutate the migration log) to not trigger a pg_catalog + # view refresh, since many get issued as part of MIGRATION + # REWRITEs. + all_migration_tweaks = all( + isinstance( + cmd, (s_ver.AlterSchemaVersion, s_migrations.MigrationCommand) + ) + and not cmd.get_subcommands(type=s_delta.ObjectCommand) + for cmd in delta.get_subcommands() + ) + + if not ctx.bootstrap_mode and not all_migration_tweaks: from edb.pgsql import metaschema refresh = metaschema.generate_sql_information_schema_refresh( ctx.compiler_state.backend_runtime_params.instance_params.version @@ -1059,11 +1072,8 @@ def _commit_migration_rewrite( cmds: List[qlast.DDLCommand] = [] # Now we find all the migrations... - migrations = s_delta.sort_by_cross_refs( - schema, - schema.get_objects(type=s_migrations.Migration), - ) - for mig in migrations: + migrations = s_migrations.get_ordered_migrations(schema) + for mig in reversed(migrations): cmds.append( qlast.DropMigration( name=qlast.ObjectRef(name=mig.get_name(schema).name) diff --git a/tests/test_edgeql_data_migration.py b/tests/test_edgeql_data_migration.py index e6f97b59e8a..e418522928c 100644 --- a/tests/test_edgeql_data_migration.py +++ b/tests/test_edgeql_data_migration.py @@ -23,6 +23,7 @@ import os.path import re import textwrap +import unittest import uuid import edgedb @@ -13255,3 +13256,15 @@ async def test_edgeql_migration_rewrite_raw_01(self): POPULATE MIGRATION; COMMIT MIGRATION; """) + + @unittest.skipIf( + True, + """ + This test is still pretty slow + """ + ) + async def test_edgeql_migration_rewrite_raw_02(self): + for _ in range(1200): + await self.con.execute(r""" + create applied migration { } + """) From 90c0bb3ecab90acff0a928fae7c606c2784333f0 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 23 Jan 2025 12:16:03 -0800 Subject: [PATCH 027/154] Reference documentation for string interpolation (#8244) --- docs/stdlib/string.rst | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/stdlib/string.rst b/docs/stdlib/string.rst index f8d2db95756..c58a15221fb 100644 --- a/docs/stdlib/string.rst +++ b/docs/stdlib/string.rst @@ -102,7 +102,7 @@ Strings A unicode string of text. - Any other type (except :eql:type:`bytes`) can be + Most primitive types (except :eql:type:`bytes`) can be :eql:op:`cast ` to and from a string: .. code-block:: edgeql-repl @@ -163,6 +163,24 @@ Strings This type is subject to `the Postgres maximum field size`_ of 1GB. + .. versionadded:: 6.0 + + Regular strings may use ``\(expr)`` to interpolate the value of + ``expr`` into the string. The value will be cast to ``str`` if it + is not already. For example: + + .. code-block:: edgeql-repl + + db> select '1 + 1 = \(1+1)'; + {'1 + 1 = 2'} + db> select User { name := '\(.first_name) \(.last_name)' }; + { + default::User { + name := 'Keanu Reeves', + }, + ... + } + .. lint-off .. _the Postgres maximum field size: https://wiki.postgresql.org/wiki/FAQ#What_is_the_maximum_size_for_a_row.2C_a_table.2C_and_a_database.3F> @@ -465,7 +483,7 @@ Strings If *trim* specifies more than one character they will be removed from both ends of the string regardless of the order in which they appear. - This is the same as applying + This is the same as applying :eql:func:`str_trim_start` and :eql:func:`str_trim_end`. .. code-block:: edgeql-repl @@ -688,7 +706,7 @@ Strings {'21st century'} .. note:: - + If you want to use literal text in your format string, it's best to enclose it in double quotes as shown above with ``of`` and ``century``. @@ -749,7 +767,7 @@ Strings A version of ``std::to_str`` exists which operates on arrays but has been deprecated; :eql:func:`array_join` should be used instead. - A :eql:type:`bytes` value can be converted to a :eql:type:`str` using + A :eql:type:`bytes` value can be converted to a :eql:type:`str` using UTF-8 encoding. Returns an InvalidValueError if input UTF-8 is invalid. .. code-block:: edgeql-repl From 722eb03365f4a1b551e56dfeabb213a4e2901fb7 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 23 Jan 2025 12:55:07 -0800 Subject: [PATCH 028/154] Don't hardcode number of tests in test_server_ops_multi_tenant (#8243) Search based on name for subtests instead of hardcoding the number. This is generally nicer and means fewer (not none) conflicts to resolve when backporting. --- tests/test_server_ops.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_server_ops.py b/tests/test_server_ops.py index 84c4bd759f0..e6cf9a4d628 100644 --- a/tests/test_server_ops.py +++ b/tests/test_server_ops.py @@ -1591,8 +1591,10 @@ async def test_server_ops_multi_tenant(self): cf1, cf2 ) - for i in range(1, 9): - name = f"_test_server_ops_multi_tenant_{i}" + test_prefix = '_test_server_ops_multi_tenant_' + tests = [s for s in dir(self) if s.startswith(test_prefix)] + for name in tests: + i = name.replace(test_prefix, '') with self.subTest(name, i=i): await getattr(self, name)(mtargs) finally: From 097ab766d8012e0568582dbb9fde4e86138cc70c Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 23 Jan 2025 15:08:30 -0800 Subject: [PATCH 029/154] Delete old .s.EDGEDB.admin.XXX sockets (#8248) This will make sure we can create the fallback symlink even if an old socket wasn't deleted, which should fix edgedb/edgedb-cli#1418. (Though maybe we want a CLI-side fix *also*, so 6.0b2 works better?) --- edb/server/server.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/edb/server/server.py b/edb/server/server.py index 59caa6311f9..9af2b193560 100644 --- a/edb/server/server.py +++ b/edb/server/server.py @@ -749,7 +749,17 @@ async def _start_admin_server( self._runstate_dir, f'.s.GEL.admin.{port}') symlink = os.path.join( self._runstate_dir, f'.s.EDGEDB.admin.{port}') - if not os.path.exists(symlink): + + exists = False + try: + mode = os.lstat(symlink).st_mode + if stat.S_ISSOCK(mode): + os.unlink(symlink) + else: + exists = True + except FileNotFoundError: + pass + if not exists: os.symlink(admin_unix_sock_path, symlink) assert len(admin_unix_sock_path) <= ( From 414380a2f7b20cdc77c556c6d248d6e375aa93ce Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 24 Jan 2025 09:47:32 -0800 Subject: [PATCH 030/154] Pin gel 3.0.0b7 as our client bindings (#8246) --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ec69cefe59e..a27450d7db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ description = "Gel Server" requires-python = '>=3.12.0' dynamic = ["version"] dependencies = [ - 'edgedb==3.0.0b5', + 'gel==3.0.0b7', 'httptools>=0.6.0', 'immutables>=0.18', @@ -103,7 +103,7 @@ requires = [ "wheel", "parsing ~= 2.0", - "edgedb==3.0.0b5", + "gel==3.0.0b7", ] # Custom backend needed to set up build-time sys.path because # setup.py needs to import `edb.buildmeta`. From ea5afab9ad5fe8538c0cfca5a6f8d54f929dbc70 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 24 Jan 2025 10:25:07 -0800 Subject: [PATCH 031/154] Retry test suite queries after TransactionConflictError (#8249) Only outside of transactions. This should reduce flakes on global DDL conflicts, among other things. --- edb/testbase/connection.py | 61 ++++++++++++++++++++++++++++---------- tests/test_server_proto.py | 6 ++-- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/edb/testbase/connection.py b/edb/testbase/connection.py index 1b54476a452..50f2aed2949 100644 --- a/edb/testbase/connection.py +++ b/edb/testbase/connection.py @@ -144,7 +144,12 @@ async def commit(self) -> None: async def _commit(self) -> None: query = self._make_commit_query() try: - await self._connection.execute(query) + # Use _fetchall to ensure there is no retry performed. + # The protocol level apparently thinks the transaction is + # over if COMMIT fails, and since we use that to decide + # whether to retry in query/execute, it would want to + # retry a COMMIT. + await self._connection._fetchall(query) except BaseException: self._state = TransactionState.FAILED raise @@ -391,29 +396,55 @@ def _shallow_clone(self): def _get_query_cache(self) -> abstract.QueryCache: return self._query_cache + async def ensure_connected(self): + if self.is_closed(): + await self.connect() + return self + async def _query(self, query_context: abstract.QueryContext): await self.ensure_connected() return await self.raw_query(query_context) + async def _retry_operation(self, func): + i = 0 + while True: + i += 1 + try: + return await func() + # Retry transaction conflict errors, up to a maximum of 5 + # times. We don't do this if we are in a transaction, + # since that *ought* to be done at the transaction level. + # Though in reality in the test suite it is usually done at the + # test runner level. + except errors.TransactionConflictError: + if i >= 5 or self.is_in_transaction(): + raise + await asyncio.sleep( + min((2 ** i) * 0.1, 10) + + random.randrange(100) * 0.001 + ) + async def _execute(self, script: abstract.ExecuteContext) -> None: await self.ensure_connected() - ctx = script.lower(allow_capabilities=edgedb_enums.Capability.ALL) - res = await self._protocol.execute(ctx) - if ctx.warnings: - script.warning_handler(ctx.warnings, res) - async def ensure_connected(self): - if self.is_closed(): - await self.connect() - return self + async def _inner(): + ctx = script.lower(allow_capabilities=edgedb_enums.Capability.ALL) + res = await self._protocol.execute(ctx) + if ctx.warnings: + script.warning_handler(ctx.warnings, res) + + await self._retry_operation(_inner) async def raw_query(self, query_context: abstract.QueryContext): - ctx = query_context.lower( - allow_capabilities=edgedb_enums.Capability.ALL) - res = await self._protocol.query(ctx) - if ctx.warnings: - res = query_context.warning_handler(ctx.warnings, res) - return res + async def _inner(): + ctx = query_context.lower( + allow_capabilities=edgedb_enums.Capability.ALL) + res = await self._protocol.query(ctx) + if ctx.warnings: + res = query_context.warning_handler(ctx.warnings, res) + return res + + return await self._retry_operation(_inner) async def _fetchall_generic(self, ctx): await self.ensure_connected() diff --git a/tests/test_server_proto.py b/tests/test_server_proto.py index 49e5abd1071..9d5f0f4a53a 100644 --- a/tests/test_server_proto.py +++ b/tests/test_server_proto.py @@ -3395,7 +3395,8 @@ async def test_server_proto_concurrent_ddl(self): # deferred_shield ensures that none of the # operations get cancelled, which allows us to # aclose them all cleanly. - g.create_task(asyncutil.deferred_shield(con.execute(f''' + # Use _fetchall, because it doesn't retry + g.create_task(asyncutil.deferred_shield(con._fetchall(f''' CREATE TYPE {typename_prefix}{i} {{ CREATE REQUIRED PROPERTY prop1 -> std::int64; }}; @@ -3441,7 +3442,8 @@ async def test_server_proto_concurrent_global_ddl(self): # deferred_shield ensures that none of the # operations get cancelled, which allows us to # aclose them all cleanly. - g.create_task(asyncutil.deferred_shield(con.execute(f''' + # Use _fetchall, because it doesn't retry + g.create_task(asyncutil.deferred_shield(con._fetchall(f''' CREATE SUPERUSER ROLE concurrent_{i} '''))) except ExceptionGroup as e: From 8b79e3fbc0118811a6b6508d5af366dce90916ca Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 24 Jan 2025 16:05:01 -0800 Subject: [PATCH 032/154] [sql] Fix (SELECT * FROM ...) in subqueries (#8254) Currently it will select all columns in *all* tables in scope. We only want to select from the things in the FROM clause. --- edb/pgsql/resolver/expr.py | 5 +++++ tests/test_sql_query.py | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/edb/pgsql/resolver/expr.py b/edb/pgsql/resolver/expr.py index 76baf070af3..fca1fb0685e 100644 --- a/edb/pgsql/resolver/expr.py +++ b/edb/pgsql/resolver/expr.py @@ -315,6 +315,11 @@ def _lookup_column( return [ (t, c) for t in ctx.scope.tables + # Only look at the highest precedence level for + # *. That is, we take everything in our local FROM + # clauses but not stuff in enclosing queries, if we + # are a subquery. + if t.precedence == 0 for c in t.columns if not c.hidden ] diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index 145de667994..c4e7a381bae 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -2286,6 +2286,19 @@ async def test_sql_query_access_policy_04(self): res = await self.squery_values('SELECT * FROM ONLY "Content"') self.assertEqual(len(res), 0) + async def test_sql_query_subquery_splat_01(self): + res = await self.squery_values( + ''' + with "average_pages" as (select avg("pages") as "value" from "Book") + select pages from "Book" + where "Book".pages < (select * from "average_pages") + ''' + ) + self.assertEqual( + res, + [[206]], + ) + async def test_sql_query_unsupported_01(self): # test error messages of unsupported queries From afb4143f67da0df974132fffaa60117ee2612176 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:46:33 -0800 Subject: [PATCH 033/154] [sql] Support inserting into column with _id suffix (#8251) Some special handling needed to not assume it is a link. --- edb/pgsql/resolver/command.py | 18 ++++++++++++------ tests/test_sql_dml.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/edb/pgsql/resolver/command.py b/edb/pgsql/resolver/command.py index 5a2375fc0e0..f5cb2228062 100644 --- a/edb/pgsql/resolver/command.py +++ b/edb/pgsql/resolver/command.py @@ -1997,13 +1997,19 @@ def _get_pointer_for_column( assert not isinstance(subject, s_properties.Property) is_link = False + ptr_name = col.name if col.name.endswith('_id'): - # this condition will break *properties* that end with _id - # I'm not sure if this is a problem - ptr_name = col.name[0:-3] - is_link = True - else: - ptr_name = col.name + # If the name ends with _id, and a single link exists with that name, + # then we are referring to the link. + root_name = ptr_name[0:-3] + if ( + (link := subject.maybe_get_ptr( + ctx.schema, sn.UnqualName(root_name), type=s_links.Link + )) + and link.singular(ctx.schema) + ): + ptr_name = root_name + is_link = True ptr = subject.maybe_get_ptr(ctx.schema, sn.UnqualName(ptr_name)) assert ptr diff --git a/tests/test_sql_dml.py b/tests/test_sql_dml.py index 0869d7e563d..a45726dbb7a 100644 --- a/tests/test_sql_dml.py +++ b/tests/test_sql_dml.py @@ -86,6 +86,10 @@ class TestSQLDataModificationLanguage(tb.SQLQueryTestCase): create required property title: str; create link owner: User; }; + + create type Numbered { + create required property num_id: int64; + }; """ ] @@ -968,6 +972,16 @@ async def test_sql_dml_insert_44(self): res = await self.squery_values('SELECT id, title FROM "Document"') self.assertEqual(res, [[docid, 'Test returning (new)']]) + async def test_sql_dml_insert_45(self): + # Test that properties ending in _id work. + res = await self.scon.execute( + ''' + INSERT INTO "Numbered" (num_id) VALUES (10) + ''' + ) + res = await self.squery_values('SELECT num_id FROM "Numbered"') + self.assertEqual(res, [[10]]) + async def test_sql_dml_delete_01(self): # delete, inspect CommandComplete tag From 8cc2947c3369cb71efcc3f4110084017403ed64c Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:46:42 -0800 Subject: [PATCH 034/154] [sql] Fix some bugs with UNION and similar (#8252) - Fix parenthesization in top level UNION Make sure the LHS of a top-level UNION gets parenthesized. Sometimes it works without them, but often it does not. - Preserve OFFSET/LIMIT clauses on UNION expressions. --- edb/pgsql/codegen.py | 15 +++++++++++---- edb/pgsql/resolver/relation.py | 4 +--- tests/test_sql_parse.py | 4 ++-- tests/test_sql_query.py | 27 +++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/edb/pgsql/codegen.py b/edb/pgsql/codegen.py index 64031254e00..7e99510cfe9 100644 --- a/edb/pgsql/codegen.py +++ b/edb/pgsql/codegen.py @@ -169,6 +169,7 @@ def __init__( add_line_information=opts.add_line_information, pretty=opts.pretty, ) + self.is_toplevel = True # params self.with_source_map: bool = with_source_map self.reordered = reordered @@ -184,6 +185,7 @@ def write( *x: str, delimiter: Optional[str] = None, ) -> None: + self.is_toplevel = False start = len(self.result) super().write(*x, delimiter=delimiter) for new in range(start, len(self.result)): @@ -268,12 +270,10 @@ def visit_NullRelation(self, node: pgast.NullRelation) -> None: self.write(')') def visit_SelectStmt(self, node: pgast.SelectStmt) -> None: - # This is a very crude detection of whether this SELECT is - # a top level statement. - parenthesize = bool(self.result) + parenthesize = not self.is_toplevel if parenthesize: - if not self.reordered: + if not self.reordered and self.result: self.new_lines = 1 self.write('(') if self.reordered: @@ -316,6 +316,13 @@ def _select() -> None: if node.op: # Upper level set operation node (UNION/INTERSECT) + + # HACK: The LHS of a set operation is *not* top-level, and + # shouldn't be treated as such. Since we (also hackily) + # use whether anything has been written do determine + # whether we are at the top level, write out an empty + # string to force parenthesization. + self.is_toplevel = False self.visit(node.larg) self.write(' ' + node.op + ' ') if node.all: diff --git a/edb/pgsql/resolver/relation.py b/edb/pgsql/resolver/relation.py index 92b9b6d046c..715475db9f1 100644 --- a/edb/pgsql/resolver/relation.py +++ b/edb/pgsql/resolver/relation.py @@ -102,11 +102,9 @@ def resolve_SelectStmt( span=stmt.span, ) - relation = pgast.SelectStmt( + relation = stmt.replace( larg=cast(pgast.Query, larg), rarg=cast(pgast.Query, rarg), - op=stmt.op, - all=stmt.all, ctes=ctes + extract_ctes_from_ctx(ctx), ) return (relation, ltable) diff --git a/tests/test_sql_parse.py b/tests/test_sql_parse.py index 2536ca9cbdf..288190d0c64 100644 --- a/tests/test_sql_parse.py +++ b/tests/test_sql_parse.py @@ -115,7 +115,7 @@ def test_sql_parse_select_07(self): """ SELECT * FROM table_one UNION select * FROM table_two % OK % - SELECT * FROM table_one UNION (SELECT * FROM table_two) + (SELECT * FROM table_one) UNION (SELECT * FROM table_two) """ def test_sql_parse_select_08(self): @@ -252,7 +252,7 @@ def test_sql_parse_select_27(self): """ select * FROM table_one UNION select * FROM table_two % OK % - SELECT * FROM table_one UNION (SELECT * FROM table_two) + (SELECT * FROM table_one) UNION (SELECT * FROM table_two) """ def test_sql_parse_select_28(self): diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index c4e7a381bae..29810326645 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -1174,6 +1174,33 @@ async def test_sql_query_56(self): [12, 4], ]) + async def test_sql_query_57(self): + res = await self.squery_values( + f''' + (select 1 limit 1) union (select 2 limit 1); + ''' + ) + self.assertEqual( + res, + [ + [1], + [2], + ] + ) + + res = await self.squery_values( + f''' + (select 1) union (select 2) LIMIT $1; + ''', + 1 + ) + self.assertEqual( + res, + [ + [1], + ] + ) + async def test_sql_query_introspection_00(self): dbname = self.con.dbname res = await self.squery_values( From 00fe1c1957d73172f49e9868a663345b20f7d3c8 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:46:50 -0800 Subject: [PATCH 035/154] Remove some lingering debug code (#8253) *Most* of these breakpoint() calls were commented out... --- edb/pgsql/dbops/functions.py | 1 - edb/schema/objects.py | 1 - edb/schema/reflection/writer.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/edb/pgsql/dbops/functions.py b/edb/pgsql/dbops/functions.py index e9cdcc8ac9d..3928ff86f31 100644 --- a/edb/pgsql/dbops/functions.py +++ b/edb/pgsql/dbops/functions.py @@ -198,7 +198,6 @@ def __init__( conditions: Optional[List[str | base.Condition]] = None, neg_conditions: Optional[List[str | base.Condition]] = None, ): - # breakpoint() self.conditional = if_exists super().__init__(conditions=conditions, neg_conditions=neg_conditions) self.name = name diff --git a/edb/schema/objects.py b/edb/schema/objects.py index 455af155156..f61bba2a877 100644 --- a/edb/schema/objects.py +++ b/edb/schema/objects.py @@ -1532,7 +1532,6 @@ def refresh_classref( coll = self.get_explicit_field_value(schema, attr, None) if coll is not None: - # breakpoint() all_coll = colltype.create(schema, coll.objects(schema)) schema = self.set_field_value(schema, attr, all_coll) diff --git a/edb/schema/reflection/writer.py b/edb/schema/reflection/writer.py index 8062530702a..a5cb9bb00e6 100644 --- a/edb/schema/reflection/writer.py +++ b/edb/schema/reflection/writer.py @@ -1108,8 +1108,6 @@ def write_meta_delete_object( parent_variables = {} - if not hasattr(target, 'id'): - breakpoint() parent_variables[f'__{target_link}'] = ( json.dumps(str(target.id)) ) From 1ae465c86206e4b12a9cc8b0f39c26feee4735e1 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:47:39 -0800 Subject: [PATCH 036/154] [sql] Don't ignore HAVING clause (#8255) --- edb/pgsql/ast.py | 2 +- edb/pgsql/codegen.py | 4 ++-- edb/pgsql/compiler/astutils.py | 2 +- edb/pgsql/parser/ast_builder.py | 2 +- edb/pgsql/resolver/relation.py | 4 ++++ tests/test_sql_query.py | 11 +++++++++++ 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/edb/pgsql/ast.py b/edb/pgsql/ast.py index b87f18b38a1..b683edba82b 100644 --- a/edb/pgsql/ast.py +++ b/edb/pgsql/ast.py @@ -687,7 +687,7 @@ class SelectStmt(Query): # GROUP BY clauses group_clause: typing.Optional[typing.List[Base]] = None # HAVING expression - having: typing.Optional[BaseExpr] = None + having_clause: typing.Optional[BaseExpr] = None # WINDOW window_name AS(...), window_clause: typing.Optional[typing.List[Base]] = None # List of ImplicitRow's in a VALUES query diff --git a/edb/pgsql/codegen.py b/edb/pgsql/codegen.py index 7e99510cfe9..cfbe4c9c1d8 100644 --- a/edb/pgsql/codegen.py +++ b/edb/pgsql/codegen.py @@ -385,13 +385,13 @@ def _select() -> None: self.visit_list(node.group_clause) self.indentation -= 2 - if node.having: + if node.having_clause: self.indentation += 1 self.new_lines = 1 self.write('HAVING') self.new_lines = 1 self.indentation += 1 - self.visit(node.having) + self.visit(node.having_clause) self.indentation -= 2 if node.sort_clause: diff --git a/edb/pgsql/compiler/astutils.py b/edb/pgsql/compiler/astutils.py index c5a7125f3f5..5d6aea725f7 100644 --- a/edb/pgsql/compiler/astutils.py +++ b/edb/pgsql/compiler/astutils.py @@ -453,7 +453,7 @@ def select_is_simple(stmt: pgast.SelectStmt) -> bool: not stmt.distinct_clause and not stmt.where_clause and not stmt.group_clause - and not stmt.having + and not stmt.having_clause and not stmt.window_clause and not stmt.values and not stmt.sort_clause diff --git a/edb/pgsql/parser/ast_builder.py b/edb/pgsql/parser/ast_builder.py index 3353d137f45..f76b7754cbb 100644 --- a/edb/pgsql/parser/ast_builder.py +++ b/edb/pgsql/parser/ast_builder.py @@ -276,7 +276,7 @@ def _build_select_stmt(n: Node, c: Context) -> pgast.SelectStmt: or [], where_clause=_maybe(n, c, "whereClause", _build_base_expr), group_clause=_maybe_list(n, c, "groupClause", _build_base), - having=_maybe(n, c, "having", _build_base_expr), + having_clause=_maybe(n, c, "havingClause", _build_base_expr), window_clause=_maybe_list(n, c, "windowClause", _build_base), values=_maybe_list(n, c, "valuesLists", _build_base_expr), sort_clause=_maybe_list(n, c, "sortClause", _build_sort_by), diff --git a/edb/pgsql/resolver/relation.py b/edb/pgsql/resolver/relation.py index 715475db9f1..59b1316fb65 100644 --- a/edb/pgsql/resolver/relation.py +++ b/edb/pgsql/resolver/relation.py @@ -123,6 +123,9 @@ def resolve_SelectStmt( group_clause = dispatch.resolve_opt_list(stmt.group_clause, ctx=subctx) + # HAVING + having = dispatch.resolve_opt(stmt.having_clause, ctx=ctx) + # SELECT projection table = context.Table() target_list: List[pgast.ResTarget] = [] @@ -174,6 +177,7 @@ def resolve_SelectStmt( from_clause=from_clause, target_list=target_list, group_clause=group_clause, + having_clause=having, where_clause=where, sort_clause=sort_clause, limit_offset=limit_offset, diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index 29810326645..103e0b5ef8f 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -2326,6 +2326,17 @@ async def test_sql_query_subquery_splat_01(self): [[206]], ) + async def test_sql_query_having_01(self): + res = await self.squery_values( + ''' + select 1 having false + ''' + ) + self.assertEqual( + res, + [], + ) + async def test_sql_query_unsupported_01(self): # test error messages of unsupported queries From df585393d518443ea4f4a16072ed38071f7eb264 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:47:48 -0800 Subject: [PATCH 037/154] [sql] Fix using `distinct`+`order by` and similar in native protocol (#8256) It doesn't work in general to stick everything in the target list into a call to ROW, because certain things in SQL only work when an expression appears in the target list. Preserve the shape of the original query and turn it into ROWs in an outer query. --- edb/pgsql/resolver/__init__.py | 30 ++++++++++++++++++++++-------- tests/test_sql_query.py | 13 +++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/edb/pgsql/resolver/__init__.py b/edb/pgsql/resolver/__init__.py index 3008bc31bf2..b3304b6c7a4 100644 --- a/edb/pgsql/resolver/__init__.py +++ b/edb/pgsql/resolver/__init__.py @@ -130,15 +130,29 @@ def resolve( if options.include_edgeql_io_format_alternative: edgeql_output_format_ast = copy.copy(resolved) if e := as_plain_select(edgeql_output_format_ast, resolved_table, ctx): - e.target_list = [ - pgast.ResTarget( - val=expr.construct_row_expr( - (rt.val for rt in e.target_list), - ctx=ctx, + # Turn the query into one that returns a ROW. + # + # We need to do this by injecting a new query and putting + # the old one in its FROM clause, since things like + # DISTINCT/ORDER BY care about what exact columns are in + # the target list. + columns = [] + for i, target in enumerate(e.target_list): + if not target.name: + e.target_list[i] = target = target.replace(name=f'__i~{i}') + assert target.name + columns.append(pgast.ColumnRef(name=(target.name,))) + + edgeql_output_format_ast = pgast.SelectStmt( + target_list=[ + pgast.ResTarget( + val=expr.construct_row_expr(columns, ctx=ctx) ) - ) - ] - edgeql_output_format_ast = e + ], + from_clause=[pgast.RangeSubselect(subquery=e)], + ctes=e.ctes, + ) + e.ctes = [] else: edgeql_output_format_ast = None diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index 103e0b5ef8f..0b1a6f5d6ce 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -2934,6 +2934,19 @@ async def test_sql_native_query_25(self): implicit_limit=3, ) + async def test_sql_native_query_26(self): + await self.assert_sql_query_result( + """ + select distinct title, pages from "Book" + order by title, pages; + """, + [ + {'title': 'Chronicles of Narnia', 'pages': 206}, + {'title': 'Hunger Games', 'pages': 374}, + ], + apply_access_policies=False, + ) + class TestSQLQueryNonTransactional(tb.SQLQueryTestCase): From a7a84cabec8c8e50ad85246d2c29ef748e51b74c Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:47:57 -0800 Subject: [PATCH 038/154] [sql] Support DEFAULT in INSERT more fully (#8258) Drop the restriction that *all* values in a column have to be DEFAULT for DEFAULT to work. Implement it by compiling the DEFAULT for each insert. --- edb/pgsql/resolver/command.py | 75 +++++++++++++++++++++++++++++------ tests/test_sql_dml.py | 66 +++++++++++++++++++++++++----- 2 files changed, 118 insertions(+), 23 deletions(-) diff --git a/edb/pgsql/resolver/command.py b/edb/pgsql/resolver/command.py index f5cb2228062..c353b81e011 100644 --- a/edb/pgsql/resolver/command.py +++ b/edb/pgsql/resolver/command.py @@ -315,7 +315,7 @@ def _uncompile_insert_object_stmt( # handle DEFAULT and prepare the value relation value_relation, expected_columns = _uncompile_default_value( - stmt.select_stmt, stmt.ctes, expected_columns + stmt.select_stmt, stmt.ctes, expected_columns, sub, ctx=ctx ) # if we are sure that we are inserting a single row @@ -629,7 +629,7 @@ def _uncompile_insert_pointer_stmt( # handle DEFAULT and prepare the value relation value_relation, expected_columns = _uncompile_default_value( - stmt.select_stmt, stmt.ctes, expected_columns + stmt.select_stmt, stmt.ctes, expected_columns, sub, ctx=ctx ) # if we are sure that we are inserting a single row @@ -858,10 +858,43 @@ def _has_at_most_one_row(query: pgast.Query | None) -> bool: return False +def _compile_standalone_default( + col: context.Column, + sub: s_objtypes.ObjectType | s_links.Link | s_properties.Property, + ctx: Context, +) -> pgast.BaseExpr: + ptr, _, _ = _get_pointer_for_column(col, sub, ctx) + default = ptr.get_default(ctx.schema) + if default is None: + return pgast.NullConstant() + + # TODO(?): Support defaults that reference the object being inserted. + # That seems like a pretty heavy lift in this scenario, though. + + options = qlcompiler.CompilerOptions( + make_globals_empty=False, + apply_user_access_policies=ctx.options.apply_access_policies, + ) + compiled = default.compiled(ctx.schema, options=options, context=None) + + sql_tree = pgcompiler.compile_ir_to_sql_tree( + compiled.irast, + output_format=pgcompiler.OutputFormat.NATIVE_INTERNAL, + alias_generator=ctx.alias_generator, + ) + merge_params(sql_tree, compiled.irast, ctx) + + assert isinstance(sql_tree.ast, pgast.BaseExpr) + return sql_tree.ast + + def _uncompile_default_value( value_query: Optional[pgast.Query], value_ctes: Optional[List[pgast.CommonTableExpr]], expected_columns: List[context.Column], + sub: s_objtypes.ObjectType | s_links.Link | s_properties.Property, + *, + ctx: Context, ) -> Tuple[pgast.BaseRelation, List[context.Column]]: # INSERT INTO x DEFAULT VALUES if not value_query: @@ -878,34 +911,52 @@ def _uncompile_default_value( def is_default(e: pgast.BaseExpr) -> bool: return isinstance(e, pgast.Keyword) and e.name == 'DEFAULT' - default_columns = set() + default_columns: dict[int, int] = {} for row in value_query.values: assert isinstance(row, pgast.ImplicitRowExpr) for to_remove, col in enumerate(row.args): if is_default(col): - default_columns.add(to_remove) + default_columns[to_remove] = ( + default_columns.setdefault(to_remove, 0) + 1 + ) # remove DEFAULT keywords and expected columns, # so EdgeQL insert will not get those columns, which will use the # property defaults. for to_remove in sorted(default_columns, reverse=True): + if default_columns[to_remove] != len(value_query.values): + continue + raise errors.QueryError( + 'DEFAULT keyword is supported only when ' + 'used for a column in all rows', + span=value_query.span, + pgext_code=pgerror.ERROR_FEATURE_NOT_SUPPORTED, + ) + del expected_columns[to_remove] for r_index, row in enumerate(value_query.values): assert isinstance(row, pgast.ImplicitRowExpr) - - if not is_default(row.args[to_remove]): - raise errors.QueryError( - 'DEFAULT keyword is supported only when ' - 'used for a column in all rows', - span=value_query.span, - pgext_code=pgerror.ERROR_FEATURE_NOT_SUPPORTED, - ) + assert is_default(row.args[to_remove]) cols = list(row.args) del cols[to_remove] value_query.values[r_index] = row.replace(args=cols) + # Go back through and compile any left over + for r_index, row in enumerate(value_query.values): + assert isinstance(row, pgast.ImplicitRowExpr) + if not any(is_default(col) for col in row.args): + continue + + cols = list(row.args) + for i, col in enumerate(row.args): + if is_default(col): + cols[i] = _compile_standalone_default( + expected_columns[i], sub, ctx=ctx + ) + value_query.values[r_index] = row.replace(args=cols) + if ( len(value_query.values) > 0 and isinstance(value_query.values[0], pgast.ImplicitRowExpr) diff --git a/tests/test_sql_dml.py b/tests/test_sql_dml.py index a45726dbb7a..34ffedbde7f 100644 --- a/tests/test_sql_dml.py +++ b/tests/test_sql_dml.py @@ -73,6 +73,9 @@ class TestSQLDataModificationLanguage(tb.SQLQueryTestCase): set default := 'untitled'; }; create property created_at: datetime; + create property content: str { + set default := 'This page intentionally left blank'; + } }; create global y: str; @@ -358,7 +361,7 @@ async def test_sql_dml_insert_16(self): ''' ) - async def test_sql_dml_insert_17(self): + async def test_sql_dml_insert_17a(self): # default values await self.scon.execute( @@ -375,16 +378,57 @@ async def test_sql_dml_insert_17(self): res = await self.squery_values('SELECT title FROM "Document"') self.assert_data_shape(res, tb.bag([[None], ['Report (new)']])) - with self.assertRaisesRegex( - asyncpg.FeatureNotSupportedError, - 'DEFAULT keyword is supported only when ' - 'used for a column in all rows', - ): - await self.scon.execute( - ''' - INSERT INTO "Document" (title) VALUES ('Report'), (DEFAULT); - ''' - ) + await self.scon.execute( + ''' + INSERT INTO "Document" (title) VALUES ('Report2'), (DEFAULT); + ''' + ) + res = await self.squery_values('SELECT title FROM "Document"') + self.assert_data_shape( + res, + tb.bag([ + [None], + [None], + ['Report (new)'], + ['Report2 (new)'], + ]), + ) + + await self.scon.execute( + ''' + INSERT INTO "Post" (title) VALUES ('post'), (DEFAULT); + ''' + ) + res = await self.squery_values('SELECT title FROM "Post"') + self.assert_data_shape( + res, + tb.bag([ + ['post'], + ['untitled'], + ]), + ) + + async def test_sql_dml_insert_17b(self): + # more default values + await self.scon.execute( + ''' + INSERT INTO "Post" (id, title, content) VALUES + (DEFAULT, 'foo', 'bar'), + (DEFAULT, 'post', DEFAULT), + (DEFAULT, DEFAULT, 'content'), + (DEFAULT, DEFAULT, DEFAULT); + ''' + ) + res = await self.squery_values('SELECT title, content FROM "Post"') + self.assert_data_shape( + res, + tb.bag([ + ['foo', 'bar'], + ['post', 'This page intentionally left blank'], + ['untitled', 'content'], + ['untitled', 'This page intentionally left blank'], + ]), + ) async def test_sql_dml_insert_18(self): res = await self.scon.fetch( From b3d2d03e1cf7afef36a58dcac8173874683c4c1b Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 10:48:06 -0800 Subject: [PATCH 039/154] [sql] Produce on error on ON CONFLICT (#8260) ON CONFLICT isn't supported yet. Start producing an error instead of silently ignoring. See #8259 --- edb/pgsql/resolver/command.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/edb/pgsql/resolver/command.py b/edb/pgsql/resolver/command.py index c353b81e011..9983e8b715e 100644 --- a/edb/pgsql/resolver/command.py +++ b/edb/pgsql/resolver/command.py @@ -276,6 +276,12 @@ def _uncompile_subject_columns( def _uncompile_insert_stmt( stmt: pgast.InsertStmt, *, ctx: Context ) -> UncompiledDML: + if stmt.on_conflict: + raise errors.UnsupportedFeatureError( + 'ON CONFLICT is not yet supported', + span=stmt.on_conflict.span, + ) + # determine the subject object sub_table, sub = _uncompile_dml_subject(stmt.relation, ctx=ctx) From 97a0dba60ac91b9737e7a779587ee2f1df80ed7c Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Mon, 27 Jan 2025 12:30:38 -0700 Subject: [PATCH 040/154] Add a new `gel-stream` crate to de-duplicate all the stream code (#8250) We've got a number of stream implementations throughout the server code and the `edgedb-rust`/`gel-rust` project. This introduces a new `gel-stream` crate that will eventually replace all of these. The stream transparently supports Unix socket, TCP sockets and TCP sockets with TLS, and supports both direct and STARTTLS-style upgrades (which Postgres requires). You create a stream using a `Target` and `TlsParameters`, and then create a `Connector` which can create multiple streams: ``` let target = Target::new_tcp_tls(("localhost", 1234), TlsParameters::insecure()); let connector = Connector::new(target)?; let stream = connector.connect().await?; ``` The connector takes care of async DNS resolution and if direct TLS is required, the initial TLS handshake. In addition, this crate also supports both rustls and openssl for TLS backends, which will allow us to provide both options in `edgedb-tokio` in the future. As part of this work, the test certs were regenerated because the CRL was missing two required fields that caused `rustls` to reject the CRL. A shell script was used for this purpose as `gen.py` didn't seem to create all necessary files. --- Cargo.lock | 815 ++++++++++++++++-- Cargo.toml | 4 +- rust/captive_postgres/src/lib.rs | 5 - rust/gel-stream/Cargo.toml | 49 ++ rust/gel-stream/src/client/connection.rs | 48 ++ rust/gel-stream/src/client/mod.rs | 597 +++++++++++++ rust/gel-stream/src/client/openssl.rs | 177 ++++ rust/gel-stream/src/client/rustls.rs | 271 ++++++ .../src/client}/stream.rs | 22 +- rust/gel-stream/src/client/target.rs | 315 +++++++ .../src/client/tokio_stream.rs} | 43 +- rust/gel-stream/src/lib.rs | 1 + rust/pgrust/Cargo.toml | 1 + rust/pgrust/examples/connect.rs | 17 +- rust/pgrust/src/connection/conn.rs | 90 +- rust/pgrust/src/connection/dsn/host.rs | 53 ++ rust/pgrust/src/connection/dsn/mod.rs | 2 +- rust/pgrust/src/connection/mod.rs | 96 +-- rust/pgrust/src/connection/openssl.rs | 129 --- rust/pgrust/src/connection/raw_conn.rs | 215 ++--- .../src/handshake/client_state_machine.rs | 10 +- rust/pgrust/src/handshake/edgedb_server.rs | 4 +- .../src/handshake/server_state_machine.rs | 4 +- rust/pgrust/src/python.rs | 16 +- rust/pgrust/tests/query_real_postgres.rs | 13 +- rust/pgrust/tests/real_postgres.rs | 66 +- tests/certs/.gitignore | 3 + tests/certs/ca.cert.pem | 71 +- tests/certs/ca.conf | 39 + tests/certs/ca.crl.pem | 39 +- tests/certs/ca.key.pem | 103 +-- tests/certs/client.cert.pem | 53 +- tests/certs/client.csr.pem | 43 +- tests/certs/client.key.pem | 79 +- tests/certs/client.key.protected.pem | 84 +- tests/certs/client_ca.cert.pem | 57 +- tests/certs/client_ca.cert.srl | 2 +- tests/certs/client_ca.key.pem | 79 +- tests/certs/gen.py | 3 + tests/certs/gen.sh | 45 + tests/certs/server.cert.pem | 71 +- tests/certs/server.key.pem | 103 +-- tests/test_backend_connect.py | 8 +- 43 files changed, 3071 insertions(+), 874 deletions(-) create mode 100644 rust/gel-stream/Cargo.toml create mode 100644 rust/gel-stream/src/client/connection.rs create mode 100644 rust/gel-stream/src/client/mod.rs create mode 100644 rust/gel-stream/src/client/openssl.rs create mode 100644 rust/gel-stream/src/client/rustls.rs rename rust/{pgrust/src/connection => gel-stream/src/client}/stream.rs (91%) create mode 100644 rust/gel-stream/src/client/target.rs rename rust/{pgrust/src/connection/tokio.rs => gel-stream/src/client/tokio_stream.rs} (72%) create mode 100644 rust/gel-stream/src/lib.rs delete mode 100644 rust/pgrust/src/connection/openssl.rs create mode 100644 tests/certs/.gitignore create mode 100644 tests/certs/ca.conf create mode 100755 tests/certs/gen.sh diff --git a/Cargo.lock b/Cargo.lock index b49bd696d5b..783105c1f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -173,6 +184,31 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "aws-lc-rs" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" +dependencies = [ + "aws-lc-sys", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "paste", +] + [[package]] name = "backtrace" version = "0.3.73" @@ -223,6 +259,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.89", + "which", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -291,7 +350,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -333,9 +392,26 @@ version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -348,6 +424,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.16" @@ -378,7 +465,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -387,6 +474,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cmake" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.2" @@ -406,6 +502,16 @@ dependencies = [ "unreachable", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "conn_pool" version = "0.1.0" @@ -421,7 +527,7 @@ dependencies = [ "pyo3", "pyo3_util", "rand", - "rstest", + "rstest 0.24.0", "scopeguard", "serde", "serde-pickle", @@ -555,6 +661,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" + [[package]] name = "db_proto" version = "0.1.0" @@ -584,7 +696,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "unicode-xid", ] @@ -613,15 +725,21 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "edb-graphql-parser" version = "0.3.0" source = "git+https://github.com/edgedb/graphql-parser#49f0e0144fac00750db6c448b7050d054e609701" dependencies = [ - "combine", + "combine 3.8.1", "num-bigint 0.2.6", "num-traits", "thiserror 1.0.63", @@ -678,7 +796,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -714,6 +832,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -723,7 +853,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -829,6 +959,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.30" @@ -885,7 +1021,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -924,6 +1060,29 @@ dependencies = [ "slab", ] +[[package]] +name = "gel-stream" +version = "0.1.0" +dependencies = [ + "derive_more", + "foreign-types", + "hickory-resolver", + "ntest", + "openssl", + "openssl-sys", + "rstest 0.24.0", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "rustls-platform-verifier", + "rustls-tokio-stream", + "tempfile", + "thiserror 2.0.3", + "tokio", + "tokio-openssl", + "webpki", +] + [[package]] name = "gel_auth" version = "0.1.0" @@ -937,7 +1096,7 @@ dependencies = [ "pretty_assertions", "rand", "roaring", - "rstest", + "rstest 0.23.0", "sha2", "thiserror 2.0.3", "tracing", @@ -1001,7 +1160,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" name = "graphql-rewrite" version = "0.1.0" dependencies = [ - "combine", + "combine 3.8.1", "edb-graphql-parser", "num-bigint 0.4.6", "num-traits", @@ -1066,6 +1225,51 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hickory-proto" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "thiserror 1.0.63", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "smallvec", + "thiserror 1.0.63", + "tokio", + "tracing", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1075,6 +1279,26 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http" version = "0.1.0" @@ -1084,7 +1308,7 @@ dependencies = [ "pyo3", "pyo3_util", "reqwest", - "rstest", + "rstest 0.23.0", "scopeguard", "tokio", "tracing", @@ -1302,7 +1526,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1342,6 +1566,18 @@ version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1384,6 +1620,37 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine 4.6.7", + "jni-sys", + "log", + "thiserror 1.0.63", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.72" @@ -1399,18 +1666,40 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1423,6 +1712,16 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -1438,6 +1737,21 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -1546,7 +1860,7 @@ checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1571,6 +1885,39 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntest" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb183f0a1da7a937f672e5ee7b7edb727bf52b8a52d531374ba8ebb9345c0330" +dependencies = [ + "ntest_test_cases", + "ntest_timeout", +] + +[[package]] +name = "ntest_test_cases" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ntest_timeout" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1705,7 +2052,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1731,6 +2078,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "paste" version = "1.0.15" @@ -1755,6 +2125,7 @@ dependencies = [ "db_proto", "derive_more", "futures", + "gel-stream", "gel_auth", "hex-literal", "hexdump", @@ -1765,7 +2136,7 @@ dependencies = [ "pretty_assertions", "pyo3", "rand", - "rstest", + "rstest 0.24.0", "scopeguard", "serde", "serde_derive", @@ -1808,7 +2179,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1863,6 +2234,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn 2.0.89", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -1929,7 +2310,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1942,7 +2323,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1958,6 +2339,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.6" @@ -1968,7 +2355,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.0", "rustls", "socket2", "thiserror 2.0.3", @@ -1986,7 +2373,7 @@ dependencies = [ "getrandom", "rand", "ring", - "rustc-hash", + "rustc-hash 2.1.0", "rustls", "rustls-pki-types", "slab", @@ -2085,6 +2472,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.10.6" @@ -2183,6 +2579,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + [[package]] name = "ring" version = "0.17.8" @@ -2216,7 +2622,19 @@ checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" dependencies = [ "futures", "futures-timer", - "rstest_macros", + "rstest_macros 0.23.0", + "rustc_version", +] + +[[package]] +name = "rstest" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros 0.24.0", "rustc_version", ] @@ -2234,7 +2652,25 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.89", + "unicode-ident", +] + +[[package]] +name = "rstest_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.89", "unicode-ident", ] @@ -2256,6 +2692,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.0" @@ -2290,6 +2732,8 @@ version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ + "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -2328,12 +2772,52 @@ dependencies = [ "web-time", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-tokio-stream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33" +dependencies = [ + "futures", + "rustls", + "socket2", + "tokio", +] + [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2360,6 +2844,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.26" @@ -2434,7 +2927,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2488,6 +2981,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "simba" version = "0.8.1" @@ -2530,7 +3032,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2552,7 +3054,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2623,7 +3125,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.89", ] [[package]] @@ -2632,6 +3134,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.89" @@ -2660,7 +3173,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2701,7 +3214,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2730,7 +3243,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2741,7 +3254,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2789,7 +3302,9 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -2803,7 +3318,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -2904,7 +3419,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3071,6 +3586,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3108,7 +3633,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -3142,7 +3667,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3186,6 +3711,37 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-root-certs" +version = "0.26.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "wide" version = "0.7.28" @@ -3196,6 +3752,12 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -3212,6 +3774,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3226,7 +3797,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3235,7 +3806,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3245,7 +3816,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -3254,7 +3843,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3263,7 +3852,37 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3272,28 +3891,64 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3306,24 +3961,72 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3339,6 +4042,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -3386,7 +4099,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -3408,7 +4121,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -3428,7 +4141,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "synstructure", ] @@ -3457,5 +4170,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index b5e8e1d5260..e012e8a583e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,10 @@ members = [ "rust/captive_postgres", "rust/conn_pool", "rust/db_proto", + "rust/gel-stream", "rust/pgrust", "rust/http", - "rust/pyo3_util" + "rust/pyo3_util", ] resolver = "2" @@ -22,6 +23,7 @@ tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["registry", "env-filter"] } gel_auth = { path = "rust/auth" } +gel-stream = { path = "rust/gel-stream" } db_proto = { path = "rust/db_proto" } captive_postgres = { path = "rust/captive_postgres" } conn_pool = { path = "rust/conn_pool" } diff --git a/rust/captive_postgres/src/lib.rs b/rust/captive_postgres/src/lib.rs index 419a87ecfd1..fc10913b979 100644 --- a/rust/captive_postgres/src/lib.rs +++ b/rust/captive_postgres/src/lib.rs @@ -5,7 +5,6 @@ use std::io::{BufReader, Write}; use std::net::{Ipv4Addr, SocketAddr}; use std::num::NonZeroUsize; use std::os::unix::fs::PermissionsExt; -use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::time::{Duration, Instant}; @@ -256,9 +255,6 @@ pub enum ListenAddress { } fn spawn(command: &mut Command) -> std::io::Result<()> { - // Set the process group to 0 to ensure that the child process is killed when the parent process exits. - command.process_group(0); - let program = Path::new(command.get_program()) .file_name() .unwrap_or_default() @@ -418,7 +414,6 @@ fn run_postgres( command.arg("-l"); } - command.process_group(0); eprintln!("postgres command:\n {:?}", command); let mut child = command.spawn()?; diff --git a/rust/gel-stream/Cargo.toml b/rust/gel-stream/Cargo.toml new file mode 100644 index 00000000000..a50e872823b --- /dev/null +++ b/rust/gel-stream/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "gel-stream" +version = "0.1.0" +edition = "2021" +description = "A library for streaming data between clients and servers." + +[features] +# rustls or openssl imply tokio, and tokio is the only stream we support +# at this time. +default = ["tokio", "rustls"] +tokio = ["dep:tokio"] +rustls = ["tokio", "dep:rustls", "dep:rustls-tokio-stream", "dep:rustls-platform-verifier", "dep:webpki"] +openssl = ["tokio", "dep:openssl", "dep:tokio-openssl", "dep:foreign-types", "dep:openssl-sys"] +hickory = ["dep:hickory-resolver"] +__manual_tests = [] + +[dependencies] +derive_more = { version = "1", features = ["full"] } +thiserror = "2" +rustls-pki-types = "1" + +tokio = { version = "1", optional = true, features = ["full"] } +rustls = { version = "0.23", optional = true } +openssl = { version = "0.10.55", optional = true } +tokio-openssl = { version = "0.6.5", optional = true } +hickory-resolver = { version = "0.24.2", optional = true } +rustls-tokio-stream = { version = "0.3.0", optional = true } +rustls-platform-verifier = { version = "0.5.0", optional = true } +webpki = { version = "0.22", optional = true } + +# Get these from openssl +foreign-types = { version = "*", optional = true } +openssl-sys = { version = "*", optional = true } + +[dev-dependencies] +tokio = { version = "1", features = ["full"] } +tempfile = "3" +ntest = "0.9.3" +rustls = "0.23" +rustls-pemfile = "2" + +rstest = "0.24.0" +rustls-tokio-stream = "0.3.0" + +[lints] +workspace = true + +[lib] +name = "gel_stream" diff --git a/rust/gel-stream/src/client/connection.rs b/rust/gel-stream/src/client/connection.rs new file mode 100644 index 00000000000..4b358d4e8a8 --- /dev/null +++ b/rust/gel-stream/src/client/connection.rs @@ -0,0 +1,48 @@ +use std::net::SocketAddr; + +use super::stream::UpgradableStream; +use super::target::{MaybeResolvedTarget, ResolvedTarget}; +use super::tokio_stream::Resolver; +use super::{ConnectionError, Ssl, Target, TlsInit}; + +type Connection = UpgradableStream>; + +/// A connector can be used to connect multiple times to the same target. +pub struct Connector { + target: Target, + resolver: Resolver, +} + +impl Connector { + pub fn new(target: Target) -> Result { + Ok(Self { + target, + resolver: Resolver::new()?, + }) + } + + pub async fn connect(&self) -> Result { + let stream = match self.target.maybe_resolved() { + MaybeResolvedTarget::Resolved(target) => target.connect().await?, + MaybeResolvedTarget::Unresolved(host, port, _) => { + let ip = self + .resolver + .resolve_remote(host.clone().into_owned()) + .await?; + ResolvedTarget::SocketAddr(SocketAddr::new(ip, *port)) + .connect() + .await? + } + }; + + if let Some(ssl) = self.target.maybe_ssl() { + let mut stm = UpgradableStream::new(stream, Some(Ssl::init(ssl, self.target.name())?)); + if !self.target.is_starttls() { + stm.secure_upgrade().await?; + } + Ok(stm) + } else { + Ok(UpgradableStream::new(stream, None)) + } + } +} diff --git a/rust/gel-stream/src/client/mod.rs b/rust/gel-stream/src/client/mod.rs new file mode 100644 index 00000000000..4f1688e18ab --- /dev/null +++ b/rust/gel-stream/src/client/mod.rs @@ -0,0 +1,597 @@ +use std::borrow::Cow; + +#[cfg(feature = "openssl")] +pub mod openssl; +#[cfg(feature = "rustls")] +pub mod rustls; +#[cfg(feature = "tokio")] +pub mod tokio_stream; + +pub mod stream; + +mod connection; +pub(crate) mod target; + +pub use connection::Connector; +pub use target::{ResolvedTarget, Target, TargetName}; + +macro_rules! __invalid_state { + ($error:literal) => {{ + eprintln!( + "Invalid connection state: {}\n{}", + $error, + ::std::backtrace::Backtrace::capture() + ); + #[allow(deprecated)] + $crate::client::ConnectionError::__InvalidState + }}; +} +pub(crate) use __invalid_state as invalid_state; +use rustls_pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer, ServerName}; + +#[derive(Debug, thiserror::Error)] +pub enum ConnectionError { + /// Invalid state error, suggesting a logic error in code rather than a server or client failure. + /// Use the `invalid_state!` macro instead which will print a backtrace. + #[error("Invalid state")] + #[deprecated = "Use invalid_state!"] + __InvalidState, + + /// I/O error encountered during connection operations. + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + /// UTF-8 decoding error. + #[error("UTF8 error: {0}")] + Utf8Error(#[from] std::str::Utf8Error), + + /// SSL-related error. + #[error("SSL error: {0}")] + SslError(#[from] SslError), +} + +#[derive(Debug, thiserror::Error)] +pub enum SslError { + #[error("SSL is not supported by this client transport")] + SslUnsupportedByClient, + + #[cfg(feature = "openssl")] + #[error("OpenSSL error: {0}")] + OpenSslError(#[from] ::openssl::ssl::Error), + #[cfg(feature = "openssl")] + #[error("OpenSSL error: {0}")] + OpenSslErrorStack(#[from] ::openssl::error::ErrorStack), + #[cfg(feature = "openssl")] + #[error("OpenSSL certificate verification error: {0}")] + OpenSslErrorVerify(#[from] ::openssl::x509::X509VerifyResult), + + #[cfg(feature = "rustls")] + #[error("Rustls error: {0}")] + RustlsError(#[from] ::rustls::Error), + + #[cfg(feature = "rustls")] + #[error("Webpki error: {0}")] + WebpkiError(::webpki::Error), + + #[cfg(feature = "rustls")] + #[error("Verifier builder error: {0}")] + VerifierBuilderError(#[from] ::rustls::server::VerifierBuilderError), + + #[error("Invalid DNS name: {0}")] + InvalidDnsNameError(#[from] ::rustls_pki_types::InvalidDnsNameError), + + #[error("SSL I/O error: {0}")] + SslIoError(#[from] std::io::Error), +} + +impl SslError { + /// Returns a common error for any time of crypto backend. + pub fn common_error(&self) -> Option { + match self { + #[cfg(feature = "rustls")] + SslError::RustlsError(::rustls::Error::InvalidCertificate(cert_err)) => { + match cert_err { + ::rustls::CertificateError::NotValidForName => { + Some(CommonError::InvalidCertificateForName) + } + ::rustls::CertificateError::Revoked => Some(CommonError::CertificateRevoked), + ::rustls::CertificateError::Expired => Some(CommonError::CertificateExpired), + ::rustls::CertificateError::UnknownIssuer => Some(CommonError::InvalidIssuer), + _ => None, + } + } + #[cfg(feature = "openssl")] + SslError::OpenSslErrorVerify(e) => match e.as_raw() { + openssl_sys::X509_V_ERR_HOSTNAME_MISMATCH => { + Some(CommonError::InvalidCertificateForName) + } + openssl_sys::X509_V_ERR_IP_ADDRESS_MISMATCH => { + Some(CommonError::InvalidCertificateForName) + } + openssl_sys::X509_V_ERR_CERT_REVOKED => Some(CommonError::CertificateRevoked), + openssl_sys::X509_V_ERR_CERT_HAS_EXPIRED => Some(CommonError::CertificateExpired), + openssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT + | openssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY => { + Some(CommonError::InvalidIssuer) + } + _ => None, + }, + _ => None, + } + } +} + +#[derive(Debug, thiserror::Error, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum CommonError { + #[error("The certificate's subject name(s) do not match the name of the host")] + InvalidCertificateForName, + #[error("The certificate has been revoked")] + CertificateRevoked, + #[error("The certificate has expired")] + CertificateExpired, + #[error("The certificate was issued by an untrusted authority")] + InvalidIssuer, +} + +// Note that we choose rustls when both openssl and rustls are enabled. + +#[cfg(all(feature = "openssl", not(feature = "rustls")))] +pub type Ssl = ::openssl::ssl::Ssl; +#[cfg(feature = "rustls")] +pub type Ssl = ::rustls::ClientConnection; + +#[cfg(feature = "tokio")] +pub type Stream = tokio_stream::TokioStream; + +/// Verification modes for TLS that are a superset of both PostgreSQL and EdgeDB/Gel. +/// +/// Postgres offers six levels: `disable`, `allow`, `prefer`, `require`, `verify-ca` and `verify-full`. +/// +/// EdgeDB/Gel offers three levels: `insecure`, `no_host_verification' and 'strict'. +/// +/// This table maps the various levels: +/// +/// | Postgres | EdgeDB/Gel | `TlsServerCertVerify` enum | +/// | -------- | ----------- | ----------------- | +/// | require | insecure | `Insecure` | +/// | verify-ca | no_host_verification | `IgnoreHostname` | +/// | verify-full | strict | `VerifyFull` | +/// +/// Note that both EdgeDB/Gel and Postgres may alter certificate validation levels +/// when custom root certificates are provided. This must be done in the +/// `TlsParameters` struct by the caller. +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +pub enum TlsServerCertVerify { + /// Do not verify the server's certificate. Only confirm that the server is + /// using TLS. + Insecure, + /// Verify the server's certificate using the CA (ignore hostname). + IgnoreHostname, + /// Verify the server's certificate using the CA and hostname. + #[default] + VerifyFull, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum TlsCert { + /// Use the system's default certificate. + #[default] + System, + /// Use a custom root certificate only. + Custom(CertificateDer<'static>), +} + +#[derive(Default, Debug, PartialEq, Eq)] +pub struct TlsParameters { + pub server_cert_verify: TlsServerCertVerify, + pub cert: Option>, + pub key: Option>, + pub root_cert: TlsCert, + pub crl: Vec>, + pub min_protocol_version: Option, + pub max_protocol_version: Option, + pub enable_keylog: bool, + pub sni_override: Option>, + pub alpn: Option]>>, +} + +impl TlsParameters { + pub fn insecure() -> Self { + Self { + server_cert_verify: TlsServerCertVerify::Insecure, + ..Default::default() + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SslVersion { + Tls1, + Tls1_1, + Tls1_2, + Tls1_3, +} + +trait TlsInit { + type Tls; + fn init(params: &TlsParameters, name: Option) -> Result; +} + +#[cfg(test)] +mod tests { + use std::{net::SocketAddr, sync::Arc}; + + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + use super::*; + + #[cfg(unix)] + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_unix() -> Result<(), std::io::Error> { + use tokio::io::AsyncReadExt; + + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().join("gel-stream-test"); + + // Create a unix socket and connect to it + let socket = tokio::net::UnixListener::bind(&path)?; + + let accept_task = tokio::spawn(async move { + let (mut stream, _) = socket.accept().await.unwrap(); + let mut buf = String::new(); + stream.read_to_string(&mut buf).await.unwrap(); + assert_eq!(buf, "Hello, world!"); + }); + + let connect_task = tokio::spawn(async { + let target = Target::new_unix_path(path)?; + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp() -> Result<(), std::io::Error> { + // Create a TCP listener on a random port + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?; + let addr = listener.local_addr()?; + + let accept_task = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await.unwrap(); + let mut buf = String::new(); + stream.read_to_string(&mut buf).await.unwrap(); + assert_eq!(buf, "Hello, world!"); + }); + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp(("127.0.0.1", addr.port())); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + fn load_test_cert() -> rustls_pki_types::CertificateDer<'static> { + rustls_pemfile::certs( + &mut include_str!("../../../../tests/certs/server.cert.pem").as_bytes(), + ) + .next() + .unwrap() + .unwrap() + } + + fn load_test_ca() -> rustls_pki_types::CertificateDer<'static> { + rustls_pemfile::certs(&mut include_str!("../../../../tests/certs/ca.cert.pem").as_bytes()) + .next() + .unwrap() + .unwrap() + } + + fn load_test_key() -> rustls_pki_types::PrivateKeyDer<'static> { + rustls_pemfile::private_key( + &mut include_str!("../../../../tests/certs/server.key.pem").as_bytes(), + ) + .unwrap() + .unwrap() + } + + fn load_test_crls() -> Vec> { + rustls_pemfile::crls(&mut include_str!("../../../../tests/certs/ca.crl.pem").as_bytes()) + .collect::, _>>() + .unwrap() + } + + async fn spawn_tls_server( + expected_hostname: Option<&str>, + server_alpn: Option<&[&str]>, + expected_alpn: Option<&str>, + ) -> Result< + ( + SocketAddr, + tokio::task::JoinHandle>, + ), + std::io::Error, + > { + use ::rustls::{ServerConfig, ServerConnection}; + + let _ = ::rustls::crypto::ring::default_provider().install_default(); + + // Create a TCP listener on a random port + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?; + let addr = listener.local_addr()?; + + // Load TLS cert and key + let cert = load_test_cert(); + let key = load_test_key(); + + // Configure rustls server + let mut config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(vec![cert], key) + .unwrap(); + config.alpn_protocols = server_alpn + .map(|alpn| alpn.iter().map(|s| s.as_bytes().to_vec()).collect()) + .unwrap_or_default(); + + let tls_config = Arc::new(config); + let expected_alpn = expected_alpn.map(|alpn| alpn.as_bytes().to_vec()); + let expected_hostname = expected_hostname.map(|sni| sni.to_string()); + let accept_task = tokio::spawn(async move { + let (tcp_stream, _) = listener.accept().await.unwrap(); + let tls_conn = ServerConnection::new(tls_config).unwrap(); + let mut stream = + rustls_tokio_stream::TlsStream::new_server_side_from(tcp_stream, tls_conn, None); + let handshake = stream.handshake().await?; + eprintln!("handshake: {:?}", handshake); + assert_eq!(handshake.alpn, expected_alpn); + assert_eq!(handshake.sni, expected_hostname); + let mut buf = String::new(); + stream.read_to_string(&mut buf).await.unwrap(); + assert_eq!(buf, "Hello, world!"); + stream.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + Ok((addr, accept_task)) + } + + /// The certificate is not valid for 127.0.0.1, so the connection should fail. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_verify_full_fails() -> Result<(), std::io::Error> { + let (addr, accept_task) = spawn_tls_server(None, None, None).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("127.0.0.1", addr.port()), + TlsParameters { + ..Default::default() + }, + ); + let stm = Connector::new(target).unwrap().connect().await; + assert!( + matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::InvalidIssuer)), + "{stm:?}" + ); + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap_err(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// The certificate is not valid for 127.0.0.1, so the connection should fail. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_verify_full_fails_name() -> Result<(), std::io::Error> { + let (addr, accept_task) = spawn_tls_server(None, None, None).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("127.0.0.1", addr.port()), + TlsParameters { + root_cert: TlsCert::Custom(load_test_ca()), + ..Default::default() + }, + ); + let stm = Connector::new(target).unwrap().connect().await; + assert!( + matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::InvalidCertificateForName)), + "{stm:?}" + ); + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap_err(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// The certificate is valid for "localhost", so the connection should succeed. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_verify_full_ok() -> Result<(), std::io::Error> { + let (addr, accept_task) = spawn_tls_server(Some("localhost"), None, None).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("localhost", addr.port()), + TlsParameters { + root_cert: TlsCert::Custom(load_test_ca()), + ..Default::default() + }, + ); + let mut stm = Connector::new(target).unwrap().connect().await?; + stm.write_all(b"Hello, world!").await?; + stm.shutdown().await?; + Ok::<_, ConnectionError>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_insecure() -> Result<(), std::io::Error> { + let (addr, accept_task) = spawn_tls_server(None, None, None).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("127.0.0.1", addr.port()), + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + ..Default::default() + }, + ); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await?; + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_crl() -> Result<(), std::io::Error> { + let (addr, accept_task) = spawn_tls_server(Some("localhost"), None, None).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("localhost", addr.port()), + TlsParameters { + root_cert: TlsCert::Custom(load_test_ca()), + crl: load_test_crls(), + ..Default::default() + }, + ); + let stm = Connector::new(target).unwrap().connect().await; + assert!( + matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::CertificateRevoked)), + "{stm:?}" + ); + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap_err(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// Test that we can override the SNI. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_sni_override() -> Result<(), std::io::Error> { + let (addr, accept_task) = spawn_tls_server(Some("www.google.com"), None, None).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("127.0.0.1", addr.port()), + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + sni_override: Some(Cow::Borrowed("www.google.com")), + ..Default::default() + }, + ); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await.unwrap(); + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// Test that we can override the ALPN. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_alpn_override() -> Result<(), std::io::Error> { + let (addr, accept_task) = + spawn_tls_server(None, Some(&["nope", "accepted"]), Some("accepted")).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("127.0.0.1", addr.port()), + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + alpn: Some(Cow::Borrowed(&[ + Cow::Borrowed("accepted"), + Cow::Borrowed("fake"), + ])), + ..Default::default() + }, + ); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await.unwrap(); + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[cfg(feature = "__manual_tests")] + #[tokio::test] + async fn test_live_server_manual_google_com() { + let target = Target::new_tcp_tls(("www.google.com", 443), TlsParameters::default()); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap(); + // HTTP/1. ..... + assert_eq!(stm.read_u8().await.unwrap(), b'H'); + } + + /// Normally connecting to Google's IP will send an invalid SNI and fail. + /// This test ensures that we can override the SNI to the correct hostname. + #[cfg(feature = "__manual_tests")] + #[tokio::test] + async fn test_live_server_google_com_override_sni() { + use std::net::ToSocketAddrs; + + let addr = "www.google.com:443" + .to_socket_addrs() + .unwrap() + .into_iter() + .next() + .unwrap(); + let target = Target::new_tcp_tls( + addr, + TlsParameters { + sni_override: Some(Cow::Borrowed("www.google.com")), + ..Default::default() + }, + ); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap(); + // HTTP/1. ..... + assert_eq!(stm.read_u8().await.unwrap(), b'H'); + } +} diff --git a/rust/gel-stream/src/client/openssl.rs b/rust/gel-stream/src/client/openssl.rs new file mode 100644 index 00000000000..b020b4026e5 --- /dev/null +++ b/rust/gel-stream/src/client/openssl.rs @@ -0,0 +1,177 @@ +use std::pin::Pin; + +use openssl::{ + ssl::{SslContextBuilder, SslMethod, SslVerifyMode}, + x509::{verify::X509VerifyFlags, X509VerifyResult}, +}; +use rustls_pki_types::ServerName; + +use super::{ + stream::{Stream, StreamWithUpgrade}, + SslError, SslVersion, TlsCert, TlsInit, TlsParameters, TlsServerCertVerify, +}; + +impl StreamWithUpgrade for (S, Option) { + type Base = S; + type Config = openssl::ssl::Ssl; + type Upgrade = tokio_openssl::SslStream; + + async fn secure_upgrade(self) -> Result + where + Self: Sized, + { + let Some(tls) = self.1 else { + return Err(super::SslError::SslUnsupportedByClient); + }; + + let mut stream = tokio_openssl::SslStream::new(tls, self.0)?; + let res = Pin::new(&mut stream).do_handshake().await; + if res.is_err() { + if stream.ssl().verify_result() != X509VerifyResult::OK { + return Err(SslError::OpenSslErrorVerify(stream.ssl().verify_result())); + } + } + res.map_err(SslError::OpenSslError)?; + Ok(stream) + } +} + +impl From for openssl::ssl::SslVersion { + fn from(val: SslVersion) -> Self { + match val { + SslVersion::Tls1 => openssl::ssl::SslVersion::TLS1, + SslVersion::Tls1_1 => openssl::ssl::SslVersion::TLS1_1, + SslVersion::Tls1_2 => openssl::ssl::SslVersion::TLS1_2, + SslVersion::Tls1_3 => openssl::ssl::SslVersion::TLS1_3, + } + } +} + +impl TlsInit for openssl::ssl::Ssl { + type Tls = openssl::ssl::Ssl; + + fn init(parameters: &TlsParameters, name: Option) -> Result { + let TlsParameters { + server_cert_verify, + root_cert, + cert, + key, + crl, + min_protocol_version, + max_protocol_version, + alpn, + sni_override, + enable_keylog, + } = parameters; + + let mut ssl = SslContextBuilder::new(SslMethod::tls_client())?; + + // Load root cert + if let TlsCert::Custom(root) = root_cert { + let root = openssl::x509::X509::from_der(root.as_ref())?; + ssl.cert_store_mut().add_cert(root)?; + ssl.set_verify(SslVerifyMode::PEER); + } else if *server_cert_verify == TlsServerCertVerify::Insecure { + ssl.set_verify(SslVerifyMode::NONE); + } + + // Load CRL + if !crl.is_empty() { + // The openssl crate doesn't yet have add_crl, so we need to use the raw FFI + use foreign_types::ForeignTypeRef; + let ptr = ssl.cert_store_mut().as_ptr(); + + extern "C" { + pub fn X509_STORE_add_crl( + store: *mut openssl_sys::X509_STORE, + x: *mut openssl_sys::X509_CRL, + ) -> openssl_sys::c_int; + } + + for crl in crl { + let crl = openssl::x509::X509Crl::from_der(crl.as_ref())?; + let crl_ptr = crl.as_ptr(); + let res = unsafe { X509_STORE_add_crl(ptr, crl_ptr) }; + if res != 1 { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to add CRL to store", + ) + .into()); + } + } + + ssl.verify_param_mut() + .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; + ssl.cert_store_mut() + .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; + } + + // Load certificate chain and private key + if let (Some(cert), Some(key)) = (cert.as_ref(), key.as_ref()) { + let builder = openssl::x509::X509::from_der(cert.as_ref())?; + ssl.set_certificate(&builder)?; + let builder = openssl::pkey::PKey::private_key_from_pem(&key.secret_der())?; + ssl.set_private_key(&builder)?; + } + + // Configure hostname verification + if *server_cert_verify == TlsServerCertVerify::VerifyFull { + ssl.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + } + + ssl.set_min_proto_version(min_protocol_version.map(|s| s.into()))?; + ssl.set_max_proto_version(max_protocol_version.map(|s| s.into()))?; + + // Configure key log filename + if *enable_keylog { + if let Ok(path) = std::env::var("SSLKEYLOGFILE") { + // "The callback is invoked whenever TLS key material is generated, and is passed a line of NSS SSLKEYLOGFILE-formatted text. + // This can be used by tools like Wireshark to decrypt message traffic. The line does not contain a trailing newline. + ssl.set_keylog_callback(move |_ssl, msg| { + let Ok(mut file) = std::fs::OpenOptions::new().append(true).open(&path) else { + return; + }; + let _ = std::io::Write::write_all(&mut file, msg.as_bytes()); + }); + } + } + + if *server_cert_verify == TlsServerCertVerify::VerifyFull { + if let Some(hostname) = sni_override { + ssl.verify_param_mut().set_host(hostname)?; + } else if let Some(ServerName::DnsName(hostname)) = &name { + ssl.verify_param_mut().set_host(hostname.as_ref())?; + } else if let Some(ServerName::IpAddress(ip)) = &name { + ssl.verify_param_mut().set_ip((*ip).into())?; + } + } + + let mut ssl = openssl::ssl::Ssl::new(&ssl.build())?; + ssl.set_connect_state(); + + // Set hostname if it's not an IP address + if let Some(hostname) = sni_override { + ssl.set_hostname(hostname)?; + } else if let Some(ServerName::DnsName(hostname)) = &name { + ssl.set_hostname(hostname.as_ref())?; + } + + if let Some(alpn) = alpn { + let alpn = alpn + .iter() + .map(|s| { + let bytes = s.as_bytes(); + let mut vec = Vec::with_capacity(bytes.len() + 1); + vec.push(bytes.len() as u8); + vec.extend_from_slice(bytes); + vec + }) + .flatten() + .collect::>(); + ssl.set_alpn_protos(&alpn)?; + } + + Ok(ssl) + } +} diff --git a/rust/gel-stream/src/client/rustls.rs b/rust/gel-stream/src/client/rustls.rs new file mode 100644 index 00000000000..456dc4818ec --- /dev/null +++ b/rust/gel-stream/src/client/rustls.rs @@ -0,0 +1,271 @@ +use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; +use rustls::client::WebPkiServerVerifier; +use rustls::{ + ClientConfig, ClientConnection, DigitallySignedStruct, RootCertStore, SignatureScheme, +}; +use rustls_pki_types::{ + CertificateDer, CertificateRevocationListDer, DnsName, ServerName, UnixTime, +}; +use rustls_platform_verifier::Verifier; + +use super::stream::{Stream, StreamWithUpgrade}; +use super::tokio_stream::TokioStream; +use super::{TlsCert, TlsInit, TlsParameters, TlsServerCertVerify}; +use std::any::Any; +use std::net::{IpAddr, Ipv4Addr}; +use std::sync::Arc; + +impl StreamWithUpgrade for (S, Option) { + type Base = S; + type Config = ClientConnection; + type Upgrade = rustls_tokio_stream::TlsStream; + + async fn secure_upgrade(self) -> Result + where + Self: Sized, + { + let Some(tls) = self.1 else { + return Err(super::SslError::SslUnsupportedByClient); + }; + + // Note that we only support Tokio TcpStream for rustls. + let stream = &mut Some(self.0) as &mut dyn Any; + let Some(stream) = stream.downcast_mut::>() else { + return Err(super::SslError::SslUnsupportedByClient); + }; + + let stream = stream.take().unwrap(); + let TokioStream::Tcp(stream) = stream else { + return Err(super::SslError::SslUnsupportedByClient); + }; + + let mut stream = rustls_tokio_stream::TlsStream::new_client_side(stream, tls, None); + let res = stream.handshake().await; + + // Potentially unwrap the error to get the underlying error. + if let Err(e) = res { + let kind = e.kind(); + if let Some(e2) = e.into_inner() { + match e2.downcast::<::rustls::Error>() { + Ok(e) => return Err(super::SslError::RustlsError(*e)), + Err(e) => return Err(std::io::Error::new(kind, e).into()), + } + } else { + return Err(std::io::Error::from(kind).into()); + } + } + + Ok(stream) + } +} + +fn make_verifier( + server_cert_verify: &TlsServerCertVerify, + root_cert: &TlsCert, + crls: Vec>, +) -> Result, super::SslError> { + if *server_cert_verify == TlsServerCertVerify::Insecure { + return Ok(Arc::new(NullVerifier)); + } + + if let TlsCert::Custom(root) = root_cert { + let mut roots = RootCertStore::empty(); + let (loaded, ignored) = roots.add_parsable_certificates([root.clone()]); + if loaded == 0 || ignored > 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid certificate", + ) + .into()); + } + + let verifier = WebPkiServerVerifier::builder(Arc::new(roots)) + .with_crls(crls) + .build()?; + if *server_cert_verify == TlsServerCertVerify::IgnoreHostname { + return Ok(Arc::new(IgnoreHostnameVerifier::new(verifier))); + } + return Ok(verifier); + } + + if *server_cert_verify == TlsServerCertVerify::IgnoreHostname { + return Ok(Arc::new(IgnoreHostnameVerifier::new(Arc::new( + Verifier::new(), + )))); + } + + Ok(Arc::new(Verifier::new())) +} + +impl TlsInit for ClientConnection { + type Tls = ClientConnection; + + fn init( + parameters: &TlsParameters, + name: Option, + ) -> Result { + let _ = ::rustls::crypto::ring::default_provider().install_default(); + + let TlsParameters { + server_cert_verify, + root_cert, + cert, + key, + crl, + min_protocol_version: _, + max_protocol_version: _, + alpn, + enable_keylog, + sni_override, + } = parameters; + + let verifier = make_verifier(server_cert_verify, root_cert, crl.clone())?; + + let config = ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(verifier); + + // Load client certificate and key if provided + let mut config = if let (Some(cert), Some(key)) = (cert, key) { + config + .with_client_auth_cert(vec![cert.clone()], key.clone_key()) + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Failed to set client auth cert", + ) + })? + } else { + config.with_no_client_auth() + }; + + // Configure ALPN if provided + if let Some(alpn_protocols) = alpn { + config.alpn_protocols = alpn_protocols + .iter() + .map(|p| p.as_bytes().to_vec()) + .collect(); + } + + // Configure keylog if provided + if *enable_keylog { + config.key_log = Arc::new(rustls::KeyLogFile::new()); + } + + let name = if let Some(sni_override) = sni_override { + ServerName::try_from(sni_override.to_string())? + } else if let Some(name) = name { + name.to_owned() + } else { + config.enable_sni = false; + ServerName::IpAddress(IpAddr::V4(Ipv4Addr::from_bits(0)).into()) + }; + + Ok(ClientConnection::new(Arc::new(config), name)?) + } +} + +#[derive(Debug)] +struct IgnoreHostnameVerifier { + verifier: Arc, +} + +impl IgnoreHostnameVerifier { + fn new(verifier: Arc) -> Self { + Self { verifier } + } +} + +impl ServerCertVerifier for IgnoreHostnameVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _server_name: &ServerName, + ocsp_response: &[u8], + now: UnixTime, + ) -> Result { + self.verifier.verify_server_cert( + end_entity, + intermediates, + &ServerName::DnsName(DnsName::try_from("").unwrap()), + ocsp_response, + now, + ) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.verifier.verify_tls12_signature(message, cert, dss) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + self.verifier.verify_tls13_signature(message, cert, dss) + } + + fn supported_verify_schemes(&self) -> Vec { + self.verifier.supported_verify_schemes() + } +} + +#[derive(Debug)] +struct NullVerifier; + +impl ServerCertVerifier for NullVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + use SignatureScheme::*; + vec![ + RSA_PKCS1_SHA1, + ECDSA_SHA1_Legacy, + RSA_PKCS1_SHA256, + ECDSA_NISTP256_SHA256, + RSA_PKCS1_SHA384, + ECDSA_NISTP384_SHA384, + RSA_PKCS1_SHA512, + ECDSA_NISTP521_SHA512, + RSA_PSS_SHA256, + RSA_PSS_SHA384, + RSA_PSS_SHA512, + ED25519, + ED448, + ] + } +} diff --git a/rust/pgrust/src/connection/stream.rs b/rust/gel-stream/src/client/stream.rs similarity index 91% rename from rust/pgrust/src/connection/stream.rs rename to rust/gel-stream/src/client/stream.rs index 07cdc91273c..f8e175895ef 100644 --- a/rust/pgrust/src/connection/stream.rs +++ b/rust/gel-stream/src/client/stream.rs @@ -15,7 +15,7 @@ pub trait StreamWithUpgrade: Unpin { /// Perform a secure upgrade operation and return the new, wrapped connection. #[allow(async_fn_in_trait)] - async fn secure_upgrade(self) -> Result + async fn secure_upgrade(self) -> Result where Self: Sized; } @@ -25,11 +25,11 @@ impl StreamWithUpgrade for (S, ()) { type Upgrade = S; type Config = (); - async fn secure_upgrade(self) -> Result + async fn secure_upgrade(self) -> Result where Self: Sized, { - Err(ConnectionError::SslError(SslError::SslUnsupportedByClient)) + Err(SslError::SslUnsupportedByClient) } } @@ -222,3 +222,19 @@ where #[debug("Upgrade(..)")] Upgrade(<(B, C) as StreamWithUpgrade>::Upgrade), } + +impl UpgradableStreamChoice +where + (B, C): StreamWithUpgrade, + B: 'static, + <(B, C) as StreamWithUpgrade>::Base: 'static, + <(B, C) as StreamWithUpgrade>::Upgrade: 'static, +{ + /// Take the inner stream as a boxed `Stream` + pub fn into_boxed(self) -> Box { + match self { + UpgradableStreamChoice::Base(base) => Box::new(base), + UpgradableStreamChoice::Upgrade(upgrade) => Box::new(upgrade), + } + } +} diff --git a/rust/gel-stream/src/client/target.rs b/rust/gel-stream/src/client/target.rs new file mode 100644 index 00000000000..233b03865c5 --- /dev/null +++ b/rust/gel-stream/src/client/target.rs @@ -0,0 +1,315 @@ +use std::{ + borrow::Cow, + net::{IpAddr, SocketAddr}, + path::Path, + sync::Arc, +}; + +use rustls_pki_types::ServerName; + +use super::TlsParameters; + +/// A target name describes the TCP or Unix socket that a client will connect to. +pub struct TargetName { + inner: MaybeResolvedTarget, +} + +impl std::fmt::Debug for TargetName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.inner) + } +} + +impl TargetName { + /// Create a new target for a Unix socket. + #[cfg(unix)] + pub fn new_unix_path(path: impl AsRef) -> Result { + let path = ResolvedTarget::from(std::os::unix::net::SocketAddr::from_pathname(path)?); + Ok(Self { + inner: MaybeResolvedTarget::Resolved(path), + }) + } + + /// Create a new target for a Unix socket. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn new_unix_domain(domain: impl AsRef<[u8]>) -> Result { + use std::os::linux::net::SocketAddrExt; + let domain = + ResolvedTarget::from(std::os::unix::net::SocketAddr::from_abstract_name(domain)?); + Ok(Self { + inner: MaybeResolvedTarget::Resolved(domain), + }) + } + + /// Create a new target for a TCP socket. + #[allow(private_bounds)] + pub fn new_tcp(host: impl TcpResolve) -> Self { + Self { inner: host.into() } + } + + /// Resolves the target addresses for a given host. + pub fn to_addrs_sync(&self) -> Result, std::io::Error> { + use std::net::ToSocketAddrs; + let mut result = Vec::new(); + match &self.inner { + MaybeResolvedTarget::Resolved(addr) => { + return Ok(vec![addr.clone()]); + } + MaybeResolvedTarget::Unresolved(host, port, _interface) => { + let addrs = format!("{}:{}", host, port).to_socket_addrs()?; + result.extend(addrs.map(ResolvedTarget::SocketAddr)); + } + } + Ok(result) + } +} + +pub struct Target { + inner: TargetInner, +} + +#[allow(private_bounds)] +impl Target { + pub fn new(name: TargetName) -> Self { + Self { + inner: TargetInner::NoTls(name.inner), + } + } + + pub fn new_tls(name: TargetName, params: TlsParameters) -> Self { + Self { + inner: TargetInner::Tls(name.inner, params.into()), + } + } + + pub fn new_starttls(name: TargetName, params: TlsParameters) -> Self { + Self { + inner: TargetInner::StartTls(name.inner, params.into()), + } + } + + pub fn new_resolved(target: ResolvedTarget) -> Self { + Self { + inner: TargetInner::NoTls(target.into()), + } + } + + pub fn new_resolved_tls(target: ResolvedTarget, params: TlsParameters) -> Self { + Self { + inner: TargetInner::Tls(target.into(), params.into()), + } + } + + pub fn new_resolved_starttls(target: ResolvedTarget, params: TlsParameters) -> Self { + Self { + inner: TargetInner::StartTls(target.into(), params.into()), + } + } + + /// Create a new target for a Unix socket. + #[cfg(unix)] + pub fn new_unix_path(path: impl AsRef) -> Result { + let path = ResolvedTarget::from(std::os::unix::net::SocketAddr::from_pathname(path)?); + Ok(Self { + inner: TargetInner::NoTls(path.into()), + }) + } + + /// Create a new target for a Unix socket. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn new_unix_domain(domain: impl AsRef<[u8]>) -> Result { + use std::os::linux::net::SocketAddrExt; + let domain = + ResolvedTarget::from(std::os::unix::net::SocketAddr::from_abstract_name(domain)?); + Ok(Self { + inner: TargetInner::NoTls(domain.into()), + }) + } + + /// Create a new target for a TCP socket. + pub fn new_tcp(host: impl TcpResolve) -> Self { + Self { + inner: TargetInner::NoTls(host.into()), + } + } + + /// Create a new target for a TCP socket with TLS. + pub fn new_tcp_tls(host: impl TcpResolve, params: TlsParameters) -> Self { + Self { + inner: TargetInner::Tls(host.into(), params.into()), + } + } + + /// Create a new target for a TCP socket with STARTTLS. + pub fn new_tcp_starttls(host: impl TcpResolve, params: TlsParameters) -> Self { + Self { + inner: TargetInner::StartTls(host.into(), params.into()), + } + } + + /// Get the name of the target. For resolved IP addresses, this is the string representation of the IP address. + /// For unresolved hostnames, this is the hostname. + pub fn name(&self) -> Option { + self.maybe_resolved().name() + } + + pub(crate) fn maybe_resolved(&self) -> &MaybeResolvedTarget { + match &self.inner { + TargetInner::NoTls(target) => target, + TargetInner::Tls(target, _) => target, + TargetInner::StartTls(target, _) => target, + } + } + + pub(crate) fn is_starttls(&self) -> bool { + matches!(self.inner, TargetInner::StartTls(_, _)) + } + + pub(crate) fn maybe_ssl(&self) -> Option<&TlsParameters> { + match &self.inner { + TargetInner::NoTls(_) => None, + TargetInner::Tls(_, params) => Some(params), + TargetInner::StartTls(_, params) => Some(params), + } + } +} + +#[derive(Clone, derive_more::From)] +pub(crate) enum MaybeResolvedTarget { + Resolved(ResolvedTarget), + Unresolved(Cow<'static, str>, u16, Option>), +} + +impl std::fmt::Debug for MaybeResolvedTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => { + if let SocketAddr::V6(addr) = addr { + if addr.scope_id() != 0 { + write!(f, "[{}%{}]:{}", addr.ip(), addr.scope_id(), addr.port()) + } else { + write!(f, "[{}]:{}", addr.ip(), addr.port()) + } + } else { + write!(f, "{}:{}", addr.ip(), addr.port()) + } + } + MaybeResolvedTarget::Resolved(ResolvedTarget::UnixSocketAddr(addr)) => { + if let Some(path) = addr.as_pathname() { + return write!(f, "{}", path.to_string_lossy()); + } else { + #[cfg(any(target_os = "linux", target_os = "android"))] + { + use std::os::linux::net::SocketAddrExt; + if let Some(name) = addr.as_abstract_name() { + return write!(f, "@{}", String::from_utf8_lossy(name)); + } + } + } + Ok(()) + } + MaybeResolvedTarget::Unresolved(host, port, interface) => { + write!(f, "{}:{}", host, port)?; + if let Some(interface) = interface { + write!(f, "%{}", interface)?; + } + Ok(()) + } + } + } +} + +impl MaybeResolvedTarget { + fn name(&self) -> Option { + match self { + MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(addr)) => { + Some(ServerName::IpAddress(addr.ip().into())) + } + MaybeResolvedTarget::Unresolved(host, _, _) => { + Some(ServerName::DnsName(host.to_string().try_into().ok()?)) + } + _ => None, + } + } +} + +/// The type of connection. +#[derive(Clone, Debug)] +enum TargetInner { + NoTls(MaybeResolvedTarget), + Tls(MaybeResolvedTarget, Arc), + StartTls(MaybeResolvedTarget, Arc), +} + +#[derive(Clone, Debug, derive_more::From)] +/// The resolved target of a connection attempt. +pub enum ResolvedTarget { + SocketAddr(std::net::SocketAddr), + #[cfg(unix)] + UnixSocketAddr(std::os::unix::net::SocketAddr), +} + +trait TcpResolve { + fn into(self) -> MaybeResolvedTarget; +} + +impl> TcpResolve for (S, u16) { + fn into(self) -> MaybeResolvedTarget { + if let Ok(addr) = self.0.as_ref().parse::() { + MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(SocketAddr::new(addr, self.1))) + } else { + MaybeResolvedTarget::Unresolved(Cow::Owned(self.0.as_ref().to_owned()), self.1, None) + } + } +} + +impl TcpResolve for SocketAddr { + fn into(self) -> MaybeResolvedTarget { + MaybeResolvedTarget::Resolved(ResolvedTarget::SocketAddr(self)) + } +} + +#[cfg(test)] +mod tests { + use std::net::SocketAddrV6; + + use super::*; + + #[test] + fn test_target() { + let target = Target::new_tcp(("localhost", 5432)); + assert_eq!( + target.name(), + Some(ServerName::DnsName("localhost".try_into().unwrap())) + ); + } + + #[test] + fn test_target_name() { + let target = TargetName::new_tcp(("localhost", 5432)); + assert_eq!(format!("{target:?}"), "localhost:5432"); + + let target = TargetName::new_tcp(("127.0.0.1", 5432)); + assert_eq!(format!("{target:?}"), "127.0.0.1:5432"); + + let target = TargetName::new_tcp(("::1", 5432)); + assert_eq!(format!("{target:?}"), "[::1]:5432"); + + let target = TargetName::new_tcp(SocketAddr::V6(SocketAddrV6::new( + "fe80::1ff:fe23:4567:890a".parse().unwrap(), + 5432, + 0, + 2, + ))); + assert_eq!(format!("{target:?}"), "[fe80::1ff:fe23:4567:890a%2]:5432"); + + let target = TargetName::new_unix_path("/tmp/test.sock").unwrap(); + assert_eq!(format!("{target:?}"), "/tmp/test.sock"); + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + let target = TargetName::new_unix_domain("test").unwrap(); + assert_eq!(format!("{target:?}"), "@test"); + } + } +} diff --git a/rust/pgrust/src/connection/tokio.rs b/rust/gel-stream/src/client/tokio_stream.rs similarity index 72% rename from rust/pgrust/src/connection/tokio.rs rename to rust/gel-stream/src/client/tokio_stream.rs index 8cdf67183be..3b938e86d28 100644 --- a/rust/pgrust/src/connection/tokio.rs +++ b/rust/gel-stream/src/client/tokio_stream.rs @@ -1,5 +1,6 @@ //! This module provides functionality to connect to Tokio TCP and Unix sockets. +use std::net::{IpAddr, ToSocketAddrs}; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite}; @@ -7,7 +8,47 @@ use tokio::net::TcpStream; #[cfg(unix)] use tokio::net::UnixStream; -use super::ResolvedTarget; +use super::target::ResolvedTarget; + +pub(crate) struct Resolver { + #[cfg(feature = "hickory")] + resolver: hickory_resolver::TokioAsyncResolver, +} + +#[allow(unused)] +async fn resolve_host_to_socket_addrs(host: String) -> std::io::Result { + let res = tokio::task::spawn_blocking(move || format!("{}:0", host).to_socket_addrs()) + .await + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Interrupted, e.to_string()))??; + res.into_iter() + .next() + .ok_or(std::io::Error::new( + std::io::ErrorKind::NotFound, + "No address found", + )) + .map(|addr| addr.ip()) +} + +impl Resolver { + pub fn new() -> Result { + Ok(Self { + #[cfg(feature = "hickory")] + resolver: hickory_resolver::AsyncResolver::tokio_from_system_conf()?, + }) + } + + pub async fn resolve_remote(&self, host: String) -> std::io::Result { + #[cfg(feature = "hickory")] + { + let addr = self.resolver.lookup_ip(host).await?.iter().next().unwrap(); + Ok(addr) + } + #[cfg(not(feature = "hickory"))] + { + resolve_host_to_socket_addrs(host).await + } + } +} impl ResolvedTarget { /// Connects to the socket address and returns a TokioStream diff --git a/rust/gel-stream/src/lib.rs b/rust/gel-stream/src/lib.rs new file mode 100644 index 00000000000..b9babe5bc1d --- /dev/null +++ b/rust/gel-stream/src/lib.rs @@ -0,0 +1 @@ +pub mod client; diff --git a/rust/pgrust/Cargo.toml b/rust/pgrust/Cargo.toml index e67df1a7da9..e23839d874e 100644 --- a/rust/pgrust/Cargo.toml +++ b/rust/pgrust/Cargo.toml @@ -18,6 +18,7 @@ pyo3.workspace = true tokio.workspace = true tracing.workspace = true db_proto.workspace = true +gel-stream.workspace = true futures = "0" thiserror = "1" diff --git a/rust/pgrust/examples/connect.rs b/rust/pgrust/examples/connect.rs index d23faaefbe4..1a6d05c6796 100644 --- a/rust/pgrust/examples/connect.rs +++ b/rust/pgrust/examples/connect.rs @@ -4,11 +4,11 @@ use captive_postgres::{ use clap::Parser; use clap_derive::Parser; use gel_auth::AuthType; -use openssl::ssl::{Ssl, SslContext, SslMethod}; +use gel_stream::client::{Connector, ResolvedTarget, Target}; use pgrust::{ connection::{ dsn::parse_postgres_dsn_env, Client, Credentials, ExecuteSink, Format, MaxRows, - PipelineBuilder, Portal, QuerySink, ResolvedTarget, Statement, + PipelineBuilder, Portal, QuerySink, Statement, }, protocol::postgres::data::{CopyData, CopyOutResponse, DataRow, ErrorResponse, RowDescription}, }; @@ -112,7 +112,7 @@ async fn main() -> Result<(), Box> { args.username = conn.user; args.password = conn.password.password().unwrap_or_default().to_string(); if let Some(host) = conn.hosts.first() { - socket_address = ResolvedTarget::to_addrs_sync(host)?.into_iter().next(); + socket_address = host.target_name()?.to_addrs_sync()?.into_iter().next(); } } @@ -136,6 +136,8 @@ async fn main() -> Result<(), Box> { let statements = args .statements .unwrap_or_else(|| vec!["select 1;".to_string()]); + let socket_address = Target::new_resolved(socket_address); + let local = LocalSet::new(); local .run_until(run_queries( @@ -224,16 +226,13 @@ fn logging_sink_execute() -> impl ExecuteSink { } async fn run_queries( - socket_address: ResolvedTarget, + target: Target, credentials: Credentials, statements: Vec, extended: bool, ) -> Result<(), Box> { - let client = socket_address.connect().await?; - let ssl = SslContext::builder(SslMethod::tls_client())?.build(); - let ssl = Ssl::new(&ssl)?; - - let (conn, task) = Client::new(credentials, client, ssl); + let connector = Connector::new(target)?; + let (conn, task) = Client::new(credentials, connector); tokio::task::spawn_local(task); conn.ready().await?; diff --git a/rust/pgrust/src/connection/conn.rs b/rust/pgrust/src/connection/conn.rs index 05be12d3c91..58c6443d175 100644 --- a/rust/pgrust/src/connection/conn.rs +++ b/rust/pgrust/src/connection/conn.rs @@ -1,15 +1,10 @@ use super::{ - connect_raw_ssl, flow::{MessageHandler, MessageResult, Pipeline, QuerySink}, raw_conn::RawClient, - stream::{Stream, StreamWithUpgrade}, - Credentials, + Credentials, PGConnectionError, }; use crate::{ - connection::{ - flow::{QueryMessageHandler, SyncMessageHandler}, - ConnectionError, - }, + connection::flow::{QueryMessageHandler, SyncMessageHandler}, handshake::ConnectionSslRequirement, protocol::postgres::{ builder, @@ -19,6 +14,7 @@ use crate::{ }; use db_proto::StructBuffer; use futures::{future::Either, FutureExt}; +use gel_stream::client::{stream::Stream, Connector}; use std::{ cell::RefCell, future::ready, @@ -31,7 +27,7 @@ use std::{ future::{poll_fn, Future}, rc::Rc, }; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::io::ReadBuf; use tracing::{error, trace, warn, Level}; #[derive(Debug, thiserror::Error)] @@ -41,7 +37,7 @@ pub enum PGConnError { #[error("Postgres error: {0}")] PgError(#[from] crate::errors::PgServerError), #[error("Connection failed: {0}")] - Connection(#[from] ConnectionError), + Connection(#[from] PGConnectionError), #[error("I/O error: {0}")] Io(#[from] std::io::Error), /// If an operation in a pipeline group fails, all operations up to @@ -56,12 +52,11 @@ pub enum PGConnError { /// /// ``` /// # use pgrust::connection::*; +/// # use gel_stream::client::{Target, Connector}; /// # _ = async { -/// # let config = (); /// # let credentials = Credentials::default(); -/// # let (client, server) = ::tokio::io::duplex(64); -/// # let socket = client; -/// let (client, task) = Client::new(credentials, socket, config); +/// # let connector = Connector::new(Target::new_tcp(("localhost", 1234))).unwrap(); +/// let (client, task) = Client::new(credentials, connector); /// ::tokio::task::spawn_local(task); /// /// // Run a basic query @@ -76,17 +71,11 @@ pub enum PGConnError { /// # Ok::<(), PGConnError>(()) /// # } /// ``` -pub struct Client -where - (B, C): StreamWithUpgrade, -{ - conn: Rc>, +pub struct Client { + conn: Rc, } -impl Clone for Client -where - (B, C): StreamWithUpgrade, -{ +impl Clone for Client { fn clone(&self) -> Self { Self { conn: self.conn.clone(), @@ -94,20 +83,14 @@ where } } -impl Client -where - (B, C): StreamWithUpgrade, - B: 'static, - C: 'static, -{ +impl Client { pub fn new( credentials: Credentials, - socket: B, - config: C, + connector: Connector, ) -> (Self, impl Future>) { let conn = Rc::new(PGConn::new_connection(async move { let ssl_mode = ConnectionSslRequirement::Optional; - let raw = connect_raw_ssl(credentials, ssl_mode, config, socket).await?; + let raw = RawClient::connect(credentials, ssl_mode, connector).await?; Ok(raw) })); let task = conn.clone().task(); @@ -115,7 +98,7 @@ where } /// Create a new PostgreSQL client and a background task. - pub fn new_raw(stm: RawClient) -> (Self, impl Future>) { + pub fn new_raw(stm: RawClient) -> (Self, impl Future>) { let conn = Rc::new(PGConn::new_raw(stm)); let task = conn.clone().task(); (Self { conn }, task) @@ -170,16 +153,13 @@ where #[derive(derive_more::Debug)] #[allow(clippy::type_complexity)] -enum ConnState -where - (B, C): StreamWithUpgrade, -{ +enum ConnState { #[debug("Connecting(..)")] #[allow(clippy::type_complexity)] - Connecting(Pin, ConnectionError>>>>), + Connecting(Pin>>>), #[debug("Ready(..)")] Ready { - client: RawClient, + stream: Pin>, handlers: VecDeque<( Box, Option>, @@ -189,23 +169,15 @@ where Closed, } -struct PGConn -where - (B, C): StreamWithUpgrade, -{ - state: RefCell>, +struct PGConn { + state: RefCell, queue: RefCell>>, ready_lock: Arc>, } -impl PGConn -where - (B, C): StreamWithUpgrade, - B: 'static, - C: 'static, -{ +impl PGConn { pub fn new_connection( - future: impl Future, ConnectionError>> + 'static, + future: impl Future> + 'static, ) -> Self { Self { state: ConnState::Connecting(future.boxed_local()).into(), @@ -214,10 +186,11 @@ where } } - pub fn new_raw(stm: RawClient) -> Self { + pub fn new_raw(stm: RawClient) -> Self { + let (stream, _params) = stm.into_parts(); Self { state: ConnState::Ready { - client: stm, + stream, handlers: Default::default(), } .into(), @@ -249,10 +222,10 @@ where fn with_stream(&self, f: F) -> Result where - F: FnOnce(Pin<&mut RawClient>) -> T, + F: FnOnce(Pin<&mut dyn Stream>) -> T, { match &mut *self.state.borrow_mut() { - ConnState::Ready { ref mut client, .. } => Ok(f(Pin::new(client))), + ConnState::Ready { ref mut stream, .. } => Ok(f(stream.as_mut())), _ => Err(PGConnError::InvalidState), } } @@ -399,8 +372,9 @@ where return Poll::Ready(Ok::<_, PGConnError>(())); } }; + let (stream, _params) = raw.into_parts(); *state = ConnState::Ready { - client: raw, + stream, handlers: Default::default(), }; Poll::Ready(Ok::<_, PGConnError>(())) @@ -448,7 +422,7 @@ where } } }; - self.process_message(Some(message.map_err(ConnectionError::ParseError)?)) + self.process_message(Some(message.map_err(PGConnectionError::ParseError)?)) })?; if n == 0 { @@ -505,7 +479,7 @@ mod tests { use hex_literal::hex; use std::{fmt::Write, time::Duration}; use tokio::{ - io::{AsyncReadExt, AsyncWriteExt, DuplexStream}, + io::{AsyncReadExt, AsyncWriteExt}, task::LocalSet, time::timeout, }; @@ -638,7 +612,7 @@ mod tests { /// Perform a test using captured binary protocol data from a real server. async fn run_expect( - query_task: impl FnOnce(Client, Rc>) -> F + 'static, + query_task: impl FnOnce(Client, Rc>) -> F + 'static, expect: &'static [(&[u8], &[u8], &str)], ) { let f = async move { diff --git a/rust/pgrust/src/connection/dsn/host.rs b/rust/pgrust/src/connection/dsn/host.rs index 0b959a19dad..e08b28b8510 100644 --- a/rust/pgrust/src/connection/dsn/host.rs +++ b/rust/pgrust/src/connection/dsn/host.rs @@ -1,10 +1,63 @@ use super::ParseError; +use gel_stream::client::{ResolvedTarget, TargetName}; use serde_derive::Serialize; use std::net::{IpAddr, Ipv6Addr}; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub struct Host(pub HostType, pub u16); +impl Host { + pub fn target_name(&self) -> Result { + match &self.0 { + HostType::Hostname(hostname) => Ok(TargetName::new_tcp((hostname, self.1))), + HostType::IP(ip, Some(interface)) => Ok(TargetName::new_tcp(( + format!("{}%{}", ip, interface), + self.1, + ))), + HostType::IP(ip, None) => Ok(TargetName::new_tcp((format!("{}", ip), self.1))), + HostType::Path(path) => { + TargetName::new_unix_path(format!("{}/.s.PGSQL.{}", path, self.1)) + } + #[allow(unused)] + HostType::Abstract(name) => { + #[cfg(any(target_os = "linux", target_os = "android"))] + { + TargetName::new_unix_domain(format!("{}/.s.PGSQL.{}", name, self.1)) + } + #[cfg(not(any(target_os = "linux", target_os = "android")))] + { + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Abstract sockets unsupported on this platform", + )) + } + } + } + } +} + +pub trait ToAddrsSyncVec { + fn to_addrs_sync(&self) -> Vec<(Host, Result, std::io::Error>)>; +} + +impl ToAddrsSyncVec for Vec { + fn to_addrs_sync(&self) -> Vec<(Host, Result, std::io::Error>)> { + let mut result = Vec::with_capacity(self.len()); + for host in self { + match host.target_name() { + Ok(target_name) => match target_name.to_addrs_sync() { + Ok(addrs) => result.push((host.clone(), Ok(addrs))), + Err(err) => result.push((host.clone(), Err(err))), + }, + Err(err) => { + result.push((host.clone(), Err(err))); + } + } + } + result + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub enum HostType { Hostname(String), diff --git a/rust/pgrust/src/connection/dsn/mod.rs b/rust/pgrust/src/connection/dsn/mod.rs index 033e9154c60..52743943e1a 100644 --- a/rust/pgrust/src/connection/dsn/mod.rs +++ b/rust/pgrust/src/connection/dsn/mod.rs @@ -13,7 +13,7 @@ mod passfile; mod raw_params; mod url; -pub use host::{Host, HostType}; +pub use host::{Host, HostType, ToAddrsSyncVec}; pub use params::{ConnectionParameters, Ssl, SslParameters}; pub use passfile::{Password, PasswordWarning}; pub use raw_params::{RawConnectionParameters, SslMode, SslVersion}; diff --git a/rust/pgrust/src/connection/mod.rs b/rust/pgrust/src/connection/mod.rs index 696367a9a0f..8ceb3085dc8 100644 --- a/rust/pgrust/src/connection/mod.rs +++ b/rust/pgrust/src/connection/mod.rs @@ -5,19 +5,16 @@ use db_proto::ParseError; mod conn; pub mod dsn; mod flow; -pub mod openssl; pub(crate) mod queue; mod raw_conn; -mod stream; -pub mod tokio; pub use conn::{Client, PGConnError}; -use dsn::HostType; pub use flow::{ CopyDataSink, DataSink, DoneHandling, ExecuteSink, FlowAccumulator, Format, MaxRows, Oid, Param, Pipeline, PipelineBuilder, Portal, QuerySink, Statement, }; -pub use raw_conn::connect_raw_ssl; +use gel_stream::client::ConnectionError; +pub use raw_conn::RawClient; macro_rules! __invalid_state { ($error:literal) => {{ @@ -27,19 +24,23 @@ macro_rules! __invalid_state { ::std::backtrace::Backtrace::capture() ); #[allow(deprecated)] - $crate::connection::ConnectionError::__InvalidState + $crate::connection::PGConnectionError::__InvalidState }}; } pub(crate) use __invalid_state as invalid_state; #[derive(Debug, thiserror::Error)] -pub enum ConnectionError { +pub enum PGConnectionError { /// Invalid state error, suggesting a logic error in code rather than a server or client failure. /// Use the `invalid_state!` macro instead which will print a backtrace. #[error("Invalid state")] #[deprecated = "Use invalid_state!"] __InvalidState, + /// Error during connection setup. + #[error("Connection error: {0}")] + ConnectionError(#[from] ConnectionError), + /// Error returned by the server. #[error("Server error: {0}")] ServerError(#[from] PgServerError), @@ -93,84 +94,3 @@ pub struct Credentials { pub database: String, pub server_settings: HashMap, } - -#[derive(Clone, Debug, derive_more::From)] -/// The resolved target of a connection attempt. -pub enum ResolvedTarget { - SocketAddr(std::net::SocketAddr), - #[cfg(unix)] - UnixSocketAddr(std::os::unix::net::SocketAddr), -} - -impl ResolvedTarget { - #[cfg(test)] - pub fn from_captive_server_listen_address(address: captive_postgres::ListenAddress) -> Self { - match address { - captive_postgres::ListenAddress::Tcp(addr) => Self::SocketAddr(addr), - #[cfg(unix)] - captive_postgres::ListenAddress::Unix(path) => { - Self::UnixSocketAddr(std::os::unix::net::SocketAddr::from_pathname(path).unwrap()) - } - } - } - - /// Resolves the target addresses for a given host. - pub fn to_addrs_sync(host: &dsn::Host) -> Result, std::io::Error> { - use std::net::{SocketAddr, ToSocketAddrs}; - - let mut resolved_targets = Vec::new(); - let dsn::Host(host_type, port) = host; - match host_type { - HostType::Hostname(hostname) => { - let socket_addrs = (hostname.as_str(), *port).to_socket_addrs()?; - for addr in socket_addrs { - resolved_targets.push(Self::SocketAddr(addr)); - } - } - HostType::IP(ip, None) => { - resolved_targets.push(ResolvedTarget::SocketAddr(SocketAddr::new(*ip, *port))) - } - HostType::IP(std::net::IpAddr::V4(_), Some(_)) => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Scope IDs only supported for IPv6", - )); - } - HostType::IP(std::net::IpAddr::V6(ip), Some(scope_id)) => { - if let Ok(scope_id) = str::parse::(scope_id) { - resolved_targets.push(ResolvedTarget::SocketAddr( - std::net::SocketAddrV6::new(*ip, *port, 0, scope_id).into(), - )); - } else { - // TODO: Resolve non-numeric scope IDs - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Only numeric scope IDs are supported", - )); - }; - } - HostType::Path(path) => { - use std::os::unix::net::SocketAddr; - resolved_targets.push(ResolvedTarget::UnixSocketAddr(SocketAddr::from_pathname( - std::path::PathBuf::from(path).join(format!(".s.PGSQL.{port}")), - )?)) - } - #[cfg(target_os = "linux")] - HostType::Abstract(abstract_path) => { - use std::os::linux::net::SocketAddrExt; - use std::os::unix::net::SocketAddr; - resolved_targets.push(ResolvedTarget::UnixSocketAddr( - SocketAddr::from_abstract_name(format!("{abstract_path}/.s.PGSQL.{port}"))?, - )) - } - #[cfg(not(target_os = "linux"))] - HostType::Abstract(abstract_path) => { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Abstract unix namespace paths unsupported on this platform", - )); - } - } - Ok(resolved_targets) - } -} diff --git a/rust/pgrust/src/connection/openssl.rs b/rust/pgrust/src/connection/openssl.rs deleted file mode 100644 index 4d01fe1288a..00000000000 --- a/rust/pgrust/src/connection/openssl.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::pin::Pin; - -use openssl::{ - ssl::{SslContextBuilder, SslVerifyMode}, - x509::verify::X509VerifyFlags, -}; - -use super::{ - dsn::{SslMode, SslParameters}, - stream::{Stream, StreamWithUpgrade}, - SslError, -}; - -impl StreamWithUpgrade for (S, openssl::ssl::Ssl) { - type Base = S; - type Config = openssl::ssl::Ssl; - type Upgrade = tokio_openssl::SslStream; - - async fn secure_upgrade(self) -> Result - where - Self: Sized, - { - let mut stream = - tokio_openssl::SslStream::new(self.1, self.0).map_err(SslError::OpenSslErrorStack)?; - Pin::new(&mut stream) - .do_handshake() - .await - .map_err(SslError::OpenSslError)?; - Ok(stream) - } -} - -/// Given a set of [`SslParameters`], configures an OpenSSL context. -pub fn create_ssl_client_context( - mut ssl: SslContextBuilder, - ssl_mode: SslMode, - parameters: SslParameters, -) -> Result> { - let SslParameters { - cert, - key, - password, - rootcert, - crl, - min_protocol_version, - max_protocol_version, - keylog_filename, - } = parameters; - - if ssl_mode >= SslMode::Require { - // Load root cert - if let Some(root) = rootcert { - ssl.set_ca_file(root)?; - ssl.set_verify(SslVerifyMode::PEER); - } else if ssl_mode == SslMode::Require { - ssl.set_verify(SslVerifyMode::NONE); - } - - // Load CRL - if let Some(crl) = &crl { - ssl.set_ca_file(crl)?; - ssl.verify_param_mut() - .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; - } - } - - // Load certificate chain and private key - if let (Some(cert), Some(key)) = (cert.as_ref(), key.as_ref()) { - let builder = openssl::x509::X509::from_pem(&std::fs::read(cert)?)?; - ssl.set_certificate(&builder)?; - let key = std::fs::read(key)?; - let key = if let Some(password) = password { - openssl::pkey::PKey::private_key_from_pem_passphrase(&key, password.as_bytes())? - } else { - openssl::pkey::PKey::private_key_from_pem(&key)? - }; - ssl.set_private_key(&key)?; - } - - // Configure hostname verification - if ssl_mode == SslMode::VerifyFull { - ssl.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); - } - - ssl.set_min_proto_version(min_protocol_version.map(|s| s.into()))?; - ssl.set_max_proto_version(max_protocol_version.map(|s| s.into()))?; - - // Configure key log filename - if let Some(keylog_filename) = &keylog_filename { - let path = keylog_filename.clone(); - // "The callback is invoked whenever TLS key material is generated, and is passed a line of NSS SSLKEYLOGFILE-formatted text. - // This can be used by tools like Wireshark to decrypt message traffic. The line does not contain a trailing newline. - ssl.set_keylog_callback(move |_ssl, msg| { - let Ok(mut file) = std::fs::OpenOptions::new().append(true).open(&path) else { - return; - }; - let _ = std::io::Write::write_all(&mut file, msg.as_bytes()); - }); - } - - Ok(ssl) -} - -#[cfg(test)] -mod tests { - use openssl::ssl::SslMethod; - use std::path::Path; - - use super::*; - - #[test] - fn create_ssl() { - let cert_path = Path::new("../../tests/certs").canonicalize().unwrap(); - - let ssl = SslContextBuilder::new(SslMethod::tls()).unwrap(); - let ssl = create_ssl_client_context( - ssl, - SslMode::VerifyFull, - SslParameters { - cert: Some(cert_path.join("client.cert.pem")), - key: Some(cert_path.join("client.key.pem")), - ..Default::default() - }, - ) - .unwrap(); - - let _context = ssl.build(); - } -} diff --git a/rust/pgrust/src/connection/raw_conn.rs b/rust/pgrust/src/connection/raw_conn.rs index 07eaeec5e7c..fece126c2b3 100644 --- a/rust/pgrust/src/connection/raw_conn.rs +++ b/rust/pgrust/src/connection/raw_conn.rs @@ -1,7 +1,4 @@ -use super::{ - stream::{Stream, StreamWithUpgrade, UpgradableStream, UpgradableStreamChoice}, - ConnectionError, Credentials, -}; +use super::{invalid_state, Credentials, PGConnectionError}; use crate::handshake::{ client::{ ConnectionDrive, ConnectionState, ConnectionStateSend, ConnectionStateType, @@ -13,11 +10,13 @@ use crate::protocol::postgres::{FrontendBuilder, InitialBuilder}; use crate::protocol::{postgres::data::SSLResponse, postgres::meta}; use db_proto::StructBuffer; use gel_auth::AuthType; +use gel_stream::client::{ + stream::{Stream, StreamWithUpgrade, UpgradableStream}, + Connector, +}; use std::collections::HashMap; use std::pin::Pin; -use std::task::{Context, Poll}; use tokio::io::AsyncWriteExt; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tracing::{trace, Level}; #[derive(Clone, Default, Debug)] @@ -81,7 +80,7 @@ impl ConnectionDriver { drive: &[u8], message_buffer: &mut StructBuffer, stream: &mut UpgradableStream, - ) -> Result<(), ConnectionError> + ) -> Result<(), PGConnectionError> where (B, C): StreamWithUpgrade, { @@ -115,7 +114,7 @@ impl ConnectionDriver { state: &mut ConnectionState, drive: ConnectionDrive<'_>, stream: &mut UpgradableStream, - ) -> Result<(), ConnectionError> + ) -> Result<(), PGConnectionError> where (B, C): StreamWithUpgrade, { @@ -143,155 +142,95 @@ impl ConnectionDriver { } } -/// A raw, fully-authenticated stream connection to a backend server. -pub struct RawClient -where - (B, C): StreamWithUpgrade, -{ - stream: UpgradableStreamChoice, +/// A raw client connection stream to a Postgres server, fully authenticated and +/// ready to send queries. +/// +/// This can be connected to a remote server using `connect`, or can be created +/// with a pre-existing, pre-authenticated stream. +#[derive(derive_more::Debug)] +pub struct RawClient { + #[debug(skip)] + stream: Pin>, params: ConnectionParams, } -impl RawClient { - /// Create a new raw client from a stream. The stream must be fully authenticated and ready. - pub fn new(stream: B, params: ConnectionParams) -> Self { +impl RawClient { + /// Create a new `RawClient` from a given fully-authenticated stream. + #[inline] + pub fn new(stream: S, params: ConnectionParams) -> Self { Self { - stream: UpgradableStreamChoice::Base(stream), + stream: Box::pin(stream), params, } } -} - -impl RawClient -where - (B, C): StreamWithUpgrade, -{ - pub fn params(&self) -> &ConnectionParams { - &self.params - } -} - -impl AsyncRead for RawClient -where - (B, C): StreamWithUpgrade, -{ - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - match &mut self.get_mut().stream { - UpgradableStreamChoice::Base(base) => Pin::new(base).poll_read(cx, buf), - UpgradableStreamChoice::Upgrade(upgraded) => Pin::new(upgraded).poll_read(cx, buf), - } - } -} - -impl AsyncWrite for RawClient -where - (B, C): StreamWithUpgrade, -{ - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - match &mut self.get_mut().stream { - UpgradableStreamChoice::Base(base) => Pin::new(base).poll_write(cx, buf), - UpgradableStreamChoice::Upgrade(upgraded) => Pin::new(upgraded).poll_write(cx, buf), - } - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> Poll> { - match &mut self.get_mut().stream { - UpgradableStreamChoice::Base(base) => Pin::new(base).poll_write_vectored(cx, bufs), - UpgradableStreamChoice::Upgrade(upgraded) => { - Pin::new(upgraded).poll_write_vectored(cx, bufs) - } - } - } - - fn is_write_vectored(&self) -> bool { - match &self.stream { - UpgradableStreamChoice::Base(base) => base.is_write_vectored(), - UpgradableStreamChoice::Upgrade(upgraded) => upgraded.is_write_vectored(), - } - } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match &mut self.get_mut().stream { - UpgradableStreamChoice::Base(base) => Pin::new(base).poll_flush(cx), - UpgradableStreamChoice::Upgrade(upgraded) => Pin::new(upgraded).poll_flush(cx), - } - } - - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match &mut self.get_mut().stream { - UpgradableStreamChoice::Base(base) => Pin::new(base).poll_shutdown(cx), - UpgradableStreamChoice::Upgrade(upgraded) => Pin::new(upgraded).poll_shutdown(cx), + /// Create a new `RawClient` from a given fully-authenticated and boxed stream. + #[inline] + pub fn new_boxed(stream: Box, params: ConnectionParams) -> Self { + Self { + stream: Box::into_pin(stream), + params, } } -} -pub async fn connect_raw_ssl( - credentials: Credentials, - ssl_mode: ConnectionSslRequirement, - config: C, - socket: B, -) -> Result, ConnectionError> -where - (B, C): StreamWithUpgrade, -{ - let mut state = ConnectionState::new(credentials, ssl_mode); - let mut stream = UpgradableStream::from((socket, config)); + /// Attempt to connect to a Postgres server using a given connector and SSL requirement. + pub async fn connect( + credentials: Credentials, + ssl_mode: ConnectionSslRequirement, + connector: Connector, + ) -> Result { + let mut state = ConnectionState::new(credentials, ssl_mode); + let mut stream = connector.connect().await?; - let mut update = ConnectionDriver::new(); - update - .drive(&mut state, ConnectionDrive::Initial, &mut stream) - .await?; + let mut update = ConnectionDriver::new(); + update + .drive(&mut state, ConnectionDrive::Initial, &mut stream) + .await?; - let mut struct_buffer: StructBuffer = StructBuffer::::default(); + let mut struct_buffer: StructBuffer = + StructBuffer::::default(); - while !state.is_ready() { - let mut buffer = [0; 1024]; - let n = tokio::io::AsyncReadExt::read(&mut stream, &mut buffer).await?; - if n == 0 { - Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof))?; - } - if tracing::enabled!(Level::TRACE) { - trace!("Read:"); - let bytes: &[u8] = &buffer[..n]; - for s in hexdump::hexdump_iter(bytes) { - trace!("{}", s); + while !state.is_ready() { + let mut buffer = [0; 1024]; + let n = tokio::io::AsyncReadExt::read(&mut stream, &mut buffer).await?; + if n == 0 { + Err(std::io::Error::from(std::io::ErrorKind::UnexpectedEof))?; } - } - if state.read_ssl_response() { - let ssl_response = SSLResponse::new(&buffer)?; + if tracing::enabled!(Level::TRACE) { + trace!("Read:"); + let bytes: &[u8] = &buffer[..n]; + for s in hexdump::hexdump_iter(bytes) { + trace!("{}", s); + } + } + if state.read_ssl_response() { + let ssl_response = SSLResponse::new(&buffer)?; + update + .drive( + &mut state, + ConnectionDrive::SslResponse(ssl_response), + &mut stream, + ) + .await?; + continue; + } + update - .drive( - &mut state, - ConnectionDrive::SslResponse(ssl_response), - &mut stream, - ) + .drive_bytes(&mut state, &buffer[..n], &mut struct_buffer, &mut stream) .await?; - continue; } - update - .drive_bytes(&mut state, &buffer[..n], &mut struct_buffer, &mut stream) - .await?; + // This should not be possible -- we've fully upgraded the stream by now + let Ok(stream) = stream.into_choice() else { + return Err(invalid_state!("Connection was not ready")); + }; + + Ok(RawClient::new_boxed(stream.into_boxed(), update.params)) } - let stream = stream.into_choice().unwrap(); - Ok(RawClient { - stream, - params: update.params, - }) + /// Consume the `RawClient` and return the underlying stream and connection parameters. + #[inline] + pub fn into_parts(self) -> (Pin>, ConnectionParams) { + (self.stream, self.params) + } } diff --git a/rust/pgrust/src/handshake/client_state_machine.rs b/rust/pgrust/src/handshake/client_state_machine.rs index cfba84dda8a..235b0be4bf0 100644 --- a/rust/pgrust/src/handshake/client_state_machine.rs +++ b/rust/pgrust/src/handshake/client_state_machine.rs @@ -1,6 +1,6 @@ use super::ConnectionSslRequirement; use crate::{ - connection::{invalid_state, ConnectionError, Credentials, SslError}, + connection::{invalid_state, Credentials, PGConnectionError, SslError}, errors::PgServerError, protocol::postgres::{ builder, @@ -157,7 +157,7 @@ impl ConnectionState { &mut self, drive: ConnectionDrive, update: &mut impl ConnectionStateUpdate, - ) -> Result<(), ConnectionError> { + ) -> Result<(), PGConnectionError> { use ConnectionStateImpl::*; trace!("Received drive {drive:?} in state {:?}", self.0); match (&mut self.0, drive) { @@ -180,12 +180,12 @@ impl ConnectionState { } else if response.code() == b'N' { // Rejected if *mode == ConnectionSslRequirement::Required { - return Err(ConnectionError::SslError(SslError::SslRequiredByClient)); + return Err(PGConnectionError::SslError(SslError::SslRequiredByClient)); } Self::send_startup_message(credentials, update)?; self.0 = Connecting(std::mem::take(credentials), false); } else { - return Err(ConnectionError::UnexpectedResponse(format!( + return Err(PGConnectionError::UnexpectedResponse(format!( "Unexpected SSL response from server: {:?}", response.code() as char ))); @@ -221,7 +221,7 @@ impl ConnectionState { } } if !found_scram_sha256 { - return Err(ConnectionError::UnexpectedResponse("Server requested SASL authentication but does not support SCRAM-SHA-256".into())); + return Err(PGConnectionError::UnexpectedResponse("Server requested SASL authentication but does not support SCRAM-SHA-256".into())); } let credentials = credentials.clone(); let mut tx = ClientTransaction::new("".into()); diff --git a/rust/pgrust/src/handshake/edgedb_server.rs b/rust/pgrust/src/handshake/edgedb_server.rs index c5cf6915ab7..6aa2106f8b2 100644 --- a/rust/pgrust/src/handshake/edgedb_server.rs +++ b/rust/pgrust/src/handshake/edgedb_server.rs @@ -1,5 +1,5 @@ use crate::{ - connection::ConnectionError, + connection::PGConnectionError, errors::edgedb::EdbError, protocol::edgedb::{data::*, *}, }; @@ -162,7 +162,7 @@ impl ServerState { &mut self, drive: ConnectionDrive, update: &mut impl ConnectionStateUpdate, - ) -> Result<(), ConnectionError> { + ) -> Result<(), PGConnectionError> { trace!("SERVER DRIVE: {:?} {:?}", self.state, drive); let res = match drive { ConnectionDrive::RawMessage(raw) => self.buffer.push_fallible(raw, |message| { diff --git a/rust/pgrust/src/handshake/server_state_machine.rs b/rust/pgrust/src/handshake/server_state_machine.rs index f7aa30f316f..526596017b9 100644 --- a/rust/pgrust/src/handshake/server_state_machine.rs +++ b/rust/pgrust/src/handshake/server_state_machine.rs @@ -1,6 +1,6 @@ use super::ConnectionSslRequirement; use crate::{ - connection::ConnectionError, + connection::PGConnectionError, errors::{ PgError, PgErrorConnectionException, PgErrorFeatureNotSupported, PgErrorInvalidAuthorizationSpecification, PgServerError, PgServerErrorField, @@ -250,7 +250,7 @@ impl ServerState { &mut self, drive: ConnectionDrive, update: &mut impl ConnectionStateUpdate, - ) -> Result<(), ConnectionError> { + ) -> Result<(), PGConnectionError> { trace!("SERVER DRIVE: {:?} {:?}", self.state, drive); let res = match drive { ConnectionDrive::RawMessage(raw) => match self.state { diff --git a/rust/pgrust/src/python.rs b/rust/pgrust/src/python.rs index b3e3f1c8a48..0e14393cf78 100644 --- a/rust/pgrust/src/python.rs +++ b/rust/pgrust/src/python.rs @@ -1,7 +1,7 @@ use crate::{ connection::{ dsn::{ConnectionParameters, RawConnectionParameters, SslMode, *}, - ConnectionError, Credentials, ResolvedTarget, + Credentials, PGConnectionError, }, errors::PgServerError, handshake::{ @@ -14,6 +14,7 @@ use crate::{ protocol::postgres::{data::SSLResponse, meta, FrontendBuilder, InitialBuilder}, }; use db_proto::StructBuffer; +use gel_stream::client::ResolvedTarget; use pyo3::{ buffer::PyBuffer, exceptions::{PyException, PyRuntimeError}, @@ -37,8 +38,8 @@ pub enum SSLMode { VerifyFull, } -impl From for PyErr { - fn from(err: ConnectionError) -> PyErr { +impl From for PyErr { + fn from(err: PGConnectionError) -> PyErr { PyRuntimeError::new_err(err.to_string()) } } @@ -94,12 +95,7 @@ impl PyConnectionParams { // As this might be blocking, drop the GIL while we allow for // resolution to take place. let hosts = self.inner.hosts()?; - let hosts = py.allow_threads(|| { - hosts.into_iter().map(|host| { - let addrs = ResolvedTarget::to_addrs_sync(&host); - (host, addrs) - }) - }); + let hosts = py.allow_threads(|| hosts.to_addrs_sync()); let mut errors = Vec::new(); let mut resolved_hosts = Vec::new(); @@ -156,7 +152,7 @@ impl PyConnectionParams { } if resolved_hosts.is_empty() { - return Err(ConnectionError::Io(std::io::Error::new( + return Err(PGConnectionError::Io(std::io::Error::new( std::io::ErrorKind::NotFound, format!("Could not resolve addresses: {errors:?}"), )) diff --git a/rust/pgrust/tests/query_real_postgres.rs b/rust/pgrust/tests/query_real_postgres.rs index ce6a9bfa41f..4f2ba64b39f 100644 --- a/rust/pgrust/tests/query_real_postgres.rs +++ b/rust/pgrust/tests/query_real_postgres.rs @@ -6,10 +6,9 @@ use std::rc::Rc; // Constants use db_proto::match_message; use gel_auth::AuthType; -use pgrust::connection::tokio::TokioStream; +use gel_stream::client::{Connector, ResolvedTarget, Target}; use pgrust::connection::{ - Client, Credentials, FlowAccumulator, MaxRows, Oid, Param, PipelineBuilder, Portal, - ResolvedTarget, Statement, + Client, Credentials, FlowAccumulator, MaxRows, Oid, Param, PipelineBuilder, Portal, Statement, }; use pgrust::protocol::postgres::data::*; use tokio::task::LocalSet; @@ -28,7 +27,7 @@ fn address(address: &ListenAddress) -> ResolvedTarget { async fn with_postgres(callback: F) -> Result, Box> where - F: FnOnce(Client, Rc>) -> R, + F: FnOnce(Client, Rc>) -> R, R: Future>>, { let Some(postgres_process) = setup_postgres(AuthType::Trust, Mode::Tcp)? else { @@ -42,8 +41,10 @@ where server_settings: Default::default(), }; - let socket = address(&postgres_process.socket_address).connect().await?; - let (client, task) = Client::new(credentials, socket, ()); + let connector = Connector::new(Target::new_resolved(address( + &postgres_process.socket_address, + )))?; + let (client, task) = Client::new(credentials, connector); let accumulator = Rc::new(RefCell::new(FlowAccumulator::default())); let accumulator2 = accumulator.clone(); diff --git a/rust/pgrust/tests/real_postgres.rs b/rust/pgrust/tests/real_postgres.rs index 185bd1bc12a..d0cb92fcf8a 100644 --- a/rust/pgrust/tests/real_postgres.rs +++ b/rust/pgrust/tests/real_postgres.rs @@ -1,6 +1,7 @@ // Constants use gel_auth::AuthType; -use pgrust::connection::{connect_raw_ssl, ConnectionError, Credentials, ResolvedTarget}; +use gel_stream::client::{Connector, ResolvedTarget, Target, TlsParameters}; +use pgrust::connection::{Credentials, PGConnectionError, RawClient}; use pgrust::errors::PgServerError; use pgrust::handshake::ConnectionSslRequirement; use rstest::rstest; @@ -28,7 +29,7 @@ async fn test_auth_noisy() -> Result<(), Box> { .debug_level(5) .server_option("client_min_messages", "debug5"); - let process = builder.build()?; + let postgres_process = builder.build()?; let credentials = Credentials { username: DEFAULT_USERNAME.to_string(), @@ -37,14 +38,14 @@ async fn test_auth_noisy() -> Result<(), Box> { server_settings: Default::default(), }; - let client = address(&process.socket_address).connect().await?; - let ssl_requirement = ConnectionSslRequirement::Optional; - let params = connect_raw_ssl(credentials, ssl_requirement, create_ssl_client()?, client) - .await? - .params() - .clone(); + let connector = Connector::new(Target::new_resolved_starttls( + address(&postgres_process.socket_address), + TlsParameters::insecure(), + ))?; + let raw_client = RawClient::connect(credentials, ssl_requirement, connector).await?; + let params = raw_client.into_parts().1; assert_eq!(params.auth, AuthType::Trust); Ok(()) @@ -68,17 +69,17 @@ async fn test_auth_real( server_settings: Default::default(), }; - let client = address(&postgres_process.socket_address).connect().await?; - let ssl_requirement = match mode { Mode::TcpSsl => ConnectionSslRequirement::Required, _ => ConnectionSslRequirement::Optional, }; - let params = connect_raw_ssl(credentials, ssl_requirement, create_ssl_client()?, client) - .await? - .params() - .clone(); + let connector = Connector::new(Target::new_resolved_starttls( + address(&postgres_process.socket_address), + TlsParameters::insecure(), + ))?; + let raw_client = RawClient::connect(credentials, ssl_requirement, connector).await?; + let params = raw_client.into_parts().1; assert_eq!(matches!(mode, Mode::TcpSsl), params.ssl); assert_eq!(auth, params.auth); @@ -103,16 +104,20 @@ async fn test_bad_password( server_settings: Default::default(), }; - let client = address(&postgres_process.socket_address).connect().await?; - let ssl_requirement = match mode { Mode::TcpSsl => ConnectionSslRequirement::Required, _ => ConnectionSslRequirement::Optional, }; - let params = connect_raw_ssl(credentials, ssl_requirement, create_ssl_client()?, client).await; + let connector = Connector::new(Target::new_resolved_starttls( + address(&postgres_process.socket_address), + TlsParameters::insecure(), + ))?; + let raw_client = RawClient::connect(credentials, ssl_requirement, connector).await; + assert!( - matches!(params, Err(ConnectionError::ServerError(PgServerError { code, .. })) if &code.to_code() == b"28P01") + matches!(raw_client, Err(PGConnectionError::ServerError(PgServerError { code, .. })) if &code.to_code() == b"28P01"), + "Expected server error 28P01, got {raw_client:?}", ); Ok(()) @@ -135,16 +140,21 @@ async fn test_bad_username( server_settings: Default::default(), }; - let client = address(&postgres_process.socket_address).connect().await?; - let ssl_requirement = match mode { Mode::TcpSsl => ConnectionSslRequirement::Required, _ => ConnectionSslRequirement::Optional, }; - let params = connect_raw_ssl(credentials, ssl_requirement, create_ssl_client()?, client).await; + let connector = Connector::new(Target::new_resolved_starttls( + address(&postgres_process.socket_address), + TlsParameters::insecure(), + ))?; + let raw_client = RawClient::connect(credentials, ssl_requirement, connector).await; + assert!( - matches!(params, Err(ConnectionError::ServerError(PgServerError { code, .. })) if &code.to_code() == b"28P01") + matches!(raw_client, Err(PGConnectionError::ServerError(PgServerError { code, .. })) if &code.to_code() == b"28P01"), + "Expected server error 28P01, got {:?}", + raw_client ); Ok(()) @@ -167,16 +177,20 @@ async fn test_bad_database( server_settings: Default::default(), }; - let client = address(&postgres_process.socket_address).connect().await?; - let ssl_requirement = match mode { Mode::TcpSsl => ConnectionSslRequirement::Required, _ => ConnectionSslRequirement::Optional, }; - let params = connect_raw_ssl(credentials, ssl_requirement, create_ssl_client()?, client).await; + let connector = Connector::new(Target::new_resolved_starttls( + address(&postgres_process.socket_address), + TlsParameters::insecure(), + ))?; + let raw_client = RawClient::connect(credentials, ssl_requirement, connector).await; + assert!( - matches!(params, Err(ConnectionError::ServerError(PgServerError { code, .. })) if &code.to_code() == b"3D000") + matches!(raw_client, Err(PGConnectionError::ServerError(PgServerError { code, .. })) if &code.to_code() == b"3D000"), + "Expected server error 3D000, got {raw_client:?}", ); Ok(()) diff --git a/tests/certs/.gitignore b/tests/certs/.gitignore new file mode 100644 index 00000000000..4fc252d6af0 --- /dev/null +++ b/tests/certs/.gitignore @@ -0,0 +1,3 @@ +*.txt* +*.csr* +*.srl diff --git a/tests/certs/ca.cert.pem b/tests/certs/ca.cert.pem index 4a8a7016ef2..63e21bcf524 100644 --- a/tests/certs/ca.cert.pem +++ b/tests/certs/ca.cert.pem @@ -1,35 +1,40 @@ -----BEGIN CERTIFICATE----- -MIIGFjCCA/6gAwIBAgIIDAM+rFY5KqgwDQYJKoZIhvcNAQELBQAwgaExCzAJBgNV -BAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdUb3JvbnRvMRgwFgYD -VQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5jcGcgdGVzdHMxHTAb -BgNVBAMMFGFzeW5jcGcgdGVzdCByb290IGNhMR0wGwYJKoZIhvcNAQkBFg5oZWxs -b0BtYWdpYy5pbzAeFw0yMTA5MTMxNjA2MDFaFw00MDExMTMxNjA2MDFaMIGhMQsw -CQYDVQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzEY -MBYGA1UECgwPTWFnaWNTdGFjayBJbmMuMRYwFAYDVQQLDA1hc3luY3BnIHRlc3Rz -MR0wGwYDVQQDDBRhc3luY3BnIHRlc3Qgcm9vdCBjYTEdMBsGCSqGSIb3DQEJARYO -aGVsbG9AbWFnaWMuaW8wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDK -mu24288Os23VtRf8kp57sj7+s+PSD/8+KiZiJ4sy5KrUUVijVQgfCpxPzpWWtQ/7 -JbjQMt+kZqJwKqdzXAY8osnljpYYvbNWnc0GZY09F6z95GqVgX/81Fe8W3Jz6I9w -S2CXVneKGtux+6fztKbrA2b1kn69b3xClEHRLFZl9hKG8ck2H+gI5AEDgQmhTIXa -pl85bPuh54uKiUGnedPk07biCw3ZE5GTGWzEq5qMqFEfb19/L1vOvgx/Q4aqmjJw -lONB9DzMftetdKaR5SS+vH0QUhiWXwy7j1TjYtJP4M6fLinwguMYG8Qbg7NkL4QC -9T7zR5CZPJ0Q/Npiwv7qdMzyL7QklZ9y3YeA5wceyc2/zh0INN5bf4J1mDZjhYH9 -CIgVHSj6z44rWq9L+OzYT0EMDhZO0OeakTWgqXNICfeEXZ5hy3QVCUvKrgmnqs0f -imdH6dZQIGQIQ8Vcg/psk2hEP1hRWROn/cgCdadcEqbMdbtOUuMcnr0K6B/bVbXx -jAV4eVcCcS3w3wIG4Ki2aIXnXrHyEJmZJb03Ko7VXP0NTGuGfPYQj2ox4a4wViOG -pxxbnGGAFqV+BIVlhUMfL9PlatqsI6kUzJIsJUiyk6oPb3KeNQ5+MtS0S1DV0jA5 -wxDQZyEFiUsl6GLYSm4RajxoHdLR7Xqj3D7EWKGt/wIDAQABo1AwTjAMBgNVHRME -BTADAQH/MB0GA1UdDgQWBBRvLFXv6sI+ePP5aegYUWoVHAfRzTAfBgNVHSMEGDAW -gBRvLFXv6sI+ePP5aegYUWoVHAfRzTANBgkqhkiG9w0BAQsFAAOCAgEAK+QAtzhk -ih8Tng9cOheswrbWf9pclMyfl38+NsJxsZnpa2SlBp3qJl0fymyNLLBfyeRUFr++ -x1cRAEwVv6R6Iepj252+U+Cmz48xIthF29JxoC+x2P2YDGyqVBm4uuw54EIF0r0H -AvjTPSNa54gA3+KiK64ypFdlHZrwx3W9b5tUsfycpj2Jrn2HgTbWQD2gaYeIIdq6 -DNmPCJg6NQE9jlvNmVqlBavjc7MJqqd+0+XtCIWhaoqeu/T6g2Epth25cuqPKc0E -rltKiXNiZHcDfFnu7B6kw2LVA6EQdf5GO9JtAaiwhRugp1dJ5rdQqdaYpJngZtvd -8+PSdDZrXow0a1jW2w+3lM5XW3qtzIKJz4Q8CXL540s+SeRjLRwY02OZCvG4fC8c -D57MIFKoReYy5LgBHdPGmx8Kexo7vk2ib9taQCSd6fh0Ol070pNiOnLP9lE9iEqq -EvU1A+0dtPHbfyXqw9tdY18nxXbooypQZSqfxPSq3Bpv8KTsr9SSG+DV2LcJRfvi -OfVTPeIWW8C8SkbEXaTCUVgaNeYqvFsfsvkTmfhO8GHglDgnsveXHfnAwlC2Uxdq -T64oKToV7N1L2RA0JR9gJ4RQwPfyaFOHOPjd+3t4DFVl54GNbNfvELHRReoyJPse -SZeL4h6T3L17FWzugHMjxFi4f1/nPNk7d5Y= +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 +wuuleg3Qo92tkeoX9SqR -----END CERTIFICATE----- diff --git a/tests/certs/ca.conf b/tests/certs/ca.conf new file mode 100644 index 00000000000..c6f4be83944 --- /dev/null +++ b/tests/certs/ca.conf @@ -0,0 +1,39 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +database = index.txt +serial = serial.txt +crlnumber = crlnumber.txt +crl_extensions = crlext +default_crl_days = 3650 +default_days = 3650 + +[ req ] +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[ req_distinguished_name ] +C = US +ST = California +L = San Francisco +O = EdgeDB Inc. +OU = EdgeDB tests +CN = EdgeDB test root ca +emailAddress = hello@edgedb.com + +[ v3_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical,CA:true + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = localhost + +[ crlext ] +authorityKeyIdentifier = keyid:always,issuer:always diff --git a/tests/certs/ca.crl.pem b/tests/certs/ca.crl.pem index b5eb1d2ede2..5f3b1bac25f 100644 --- a/tests/certs/ca.crl.pem +++ b/tests/certs/ca.crl.pem @@ -1,19 +1,24 @@ -----BEGIN X509 CRL----- -MIIDAjCB6wIBATANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCQ0ExEDAOBgNV -BAgMB09udGFyaW8xEDAOBgNVBAcMB1Rvcm9udG8xGDAWBgNVBAoMD01hZ2ljU3Rh -Y2sgSW5jLjEWMBQGA1UECwwNYXN5bmNwZyB0ZXN0czEdMBsGA1UEAwwUYXN5bmNw -ZyB0ZXN0IHJvb3QgY2ExHTAbBgkqhkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvFw0y -MTA5MTQxNjA2MDFaFw0yMTA5MTUxNjA2MDFaMBUwEwICEAAXDTIxMDkxNDE2MDYw -MVowDQYJKoZIhvcNAQELBQADggIBAL4yfNmvGS8SkIVbRzdAC9+XJPw/dBJOUJwr -EgERICAz7OTqG1PkmMhPL00Dm9fe52+KnSwHgL749W0S/X5rTNMSwLyGiiJ5HYbH -GFRKQ/cvXLi4jYpSI1Ac94kk0japf3SfwEw3+122oba8SiAVP0nY3bHpHvNfOaDV -fhbFTwb5bFm6ThqlKLZxGCKP0fGeQ4homuwgRiLE/UOiue5ted1ph0PkKVui208k -FnhNYXSllakTGT8ZZZZVid/4tSHqJEY9vbdMXNv1GX8mhjoU1Gv9dOuyFGgUc9Vx -e7gzf/Wf36vKI29o8QGkkTslRZpMG59z3sG4Y0vJEoqXMB6eQLOr5iUCyj2CyDha -66pwrdc1fRt3EvNXUWkdHfY3EHb7DxueedDEgtmfSNbEaZTXa5RaZRavNGNTaPDf -UcrDU4w1N0wkYLQxPqd+VPcf1iKyfkAydpeOq9CChqRD0Tx58eTn6N/lLGFPPRfs -x47BA4FmefBeXZzd5HiXCUouk3qHIHs2yCzFs+TEBkx5eV42cP++HxjirPydLf6Y -G/o/TKRnc/2Lw+dCzvUV/p3geuw4+vq1BIFanwB9jp4tGaBrffIAyle8vPQLw6bp -1o1O39pdxniz+c9r0Kw/ETxTqRLbasSib5FHq5G/G9a+QxPsLAzKgwLWhR4fXvbu -YPbhYhRP +MIIEFTCCAf0CAQEwDQYJKoZIhvcNAQELBQAwgaYxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQK +DAtFZGdlREIgSW5jLjEVMBMGA1UECwwMRWRnZURCIHRlc3RzMRwwGgYDVQQDDBNF +ZGdlREIgdGVzdCByb290IGNhMR8wHQYJKoZIhvcNAQkBFhBoZWxsb0BlZGdlZGIu +Y29tFw0yNTAxMjUxNzIxMDdaFw0zNTAxMjMxNzIxMDdaMCcwJQIUZqAmIZS9Cgjj +65btVW6Z6rl4/LEXDTI1MDEyNTE3MjEwN1qggfgwgfUwgeYGA1UdIwSB3jCB24AU +MRyWvFujcw5gH0OGzaaU/kGyTzqhgaykgakwgaYxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQK +DAtFZGdlREIgSW5jLjEVMBMGA1UECwwMRWRnZURCIHRlc3RzMRwwGgYDVQQDDBNF +ZGdlREIgdGVzdCByb290IGNhMR8wHQYJKoZIhvcNAQkBFhBoZWxsb0BlZGdlZGIu +Y29tghQ6qnhbvl7/JQpndCKH4PWGW8dmOjAKBgNVHRQEAwIBATANBgkqhkiG9w0B +AQsFAAOCAgEAdMTcjKyb5EUpJJBMPfDgj2HShsH+lHvlhde0snxSeOLtnDTjL2aq +mN6GgKozo18tcOh9IOVh6gk6erf1dkH+Mr4wn91CEhxOeziLwP4XNhusU1VfrxAA +zoTMC/j2FKGGaajjtgKTobg79HM+5o1qiAbAL+bHUlZ18Up/5/CJmAXXCQnbgEzN +aRfLntdyu76XqDWzCsxOYx4fDELJ86+dixjRRhtARINcxWqN71NDa77Ozdq9KesX +8VEt0i+mqt+VSSQ/b0TEZrXCm3ru4BWNLc1Wclq6C33dUzfcKYaIwJ5kR3y9/vg9 +VZc0zM7B02RSFlAEsLa/MnFHJwFby2/Vq5i31LDNdxM+OxKXQ1FCtuJh1aJo/p1p +155rbLhRDIZojA5x3XtJejt9tyB6CLmcdNWmbyHi8W6wpwlnYaFSsQC4M7S/8zCf +zkWTuzjwC+1wTcXBMm8euWMhDWt/0BSy54OPsqeCGypJ1xv8zEcjz3jZAMMvFWei ++QR4BBiKB0XfzChMJKIdlv8T3FNtkOLFVaiYUWUDQDJyV5hs45ebcb9AFw3Ssi4d +xWVkwzGTV/9ZN+xGP0y2Ni4AKs0n4BVSY0N4uzx8mjc0KU3A6fJ3v7C9163/ZBUe +VYddrkLjrkBaEUeu11EuZcgVUcK6ox2wzon4cCklRwmgaPrEm4Mppeo= -----END X509 CRL----- diff --git a/tests/certs/ca.key.pem b/tests/certs/ca.key.pem index 2d73448f327..718e4c4fdb4 100644 --- a/tests/certs/ca.key.pem +++ b/tests/certs/ca.key.pem @@ -1,51 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAyprtuNvPDrNt1bUX/JKee7I+/rPj0g//PiomYieLMuSq1FFY -o1UIHwqcT86VlrUP+yW40DLfpGaicCqnc1wGPKLJ5Y6WGL2zVp3NBmWNPRes/eRq -lYF//NRXvFtyc+iPcEtgl1Z3ihrbsfun87Sm6wNm9ZJ+vW98QpRB0SxWZfYShvHJ -Nh/oCOQBA4EJoUyF2qZfOWz7oeeLiolBp3nT5NO24gsN2RORkxlsxKuajKhRH29f -fy9bzr4Mf0OGqpoycJTjQfQ8zH7XrXSmkeUkvrx9EFIYll8Mu49U42LST+DOny4p -8ILjGBvEG4OzZC+EAvU+80eQmTydEPzaYsL+6nTM8i+0JJWfct2HgOcHHsnNv84d -CDTeW3+CdZg2Y4WB/QiIFR0o+s+OK1qvS/js2E9BDA4WTtDnmpE1oKlzSAn3hF2e -Yct0FQlLyq4Jp6rNH4pnR+nWUCBkCEPFXIP6bJNoRD9YUVkTp/3IAnWnXBKmzHW7 -TlLjHJ69Cugf21W18YwFeHlXAnEt8N8CBuCotmiF516x8hCZmSW9NyqO1Vz9DUxr -hnz2EI9qMeGuMFYjhqccW5xhgBalfgSFZYVDHy/T5WrarCOpFMySLCVIspOqD29y -njUOfjLUtEtQ1dIwOcMQ0GchBYlLJehi2EpuEWo8aB3S0e16o9w+xFihrf8CAwEA -AQKCAgEApJFdgOdCc415LLpxJl4tzwnEs3yJE8qcp/Dyxo2aOpeUzurYVasu8o/a -0dRam1StC3HjgXGhSNd5ICT1aPWZt0z/M7Ay6RvFfRimPYjlRXdis8QCczgCLuqH -7V5WRCHlyO/hIGxCovIX+6UPEhxt7L0Rt2zr95GD3EyyfWZHM4DCIcxphMY74mTZ -EfCRUuxmWWkENg/5ANSj+r5sjs2dOORjS45xDB8iAtsHB2TgH1pksmTzq8pbBz5F -xmWiEBc520qEocDyVaS+KY1z81OuGiPebhBRGmtQW1UcPaq6a9mN26xSsqKONbnv -++1pHHqf/wsXu+IoaN/cML1B4jDDf1milC7mmgPdETQjbco7PvSsxzG3pZktijoT -8WfCMda4SFgkLMDEKyD5tyUGQFsvijXFf9y+/V0ux3u1Hm6NApDXTf7gX5W0b9tD -uiupzcwCtA5s9AO6G0bQnddwzFGh91/ydyc5DfaRjfrG95zYouwqmMQXTqYG1USX -mLrDgHw3ierlwVWKUR0OnysMeNYtu5782RO3LSdL126PKLd/pLvG7FrETLFECP3B -QgM/vKlNY26mcX4DuALRRLWu+ORrGMclEp7Bw/JPTkFxj2gLrmL6JM1h+CFXDBmk -pE0Cl2PDCVq4aFWZDn4F8ioT4XW/2REtxp7E2wazNnCX+IUap1ECggEBAOeXY9Ib -m0GayJVm7kvvL6pY2e/lHlvi44xcTG3GrkOn/qMLIDkXvUyfjcqHZQhMoYhnYx4K -iyK4D/Mej4Jbj5dyRKHEn8tKGuDrlzFp0CLRQvg1s/LcktX8hdef9IPXHA3y6ML5 -X60KNN1PI/7aINEENn1qOqDvU6X9ST3VGAWbfyM5jOZDHIBkjJuJTUwndaDbIA09 -AqxqQjq6UntCG+seXBmE1OHht++pWgN5rlq1wJ2KJlGR2HdhtIl1JyfU/hisnfFD -ahQMUFoFYS3ecNUNumbQEBaZ66/mHP0p2YhaLK3j3shC8vsN15LOW6Ulzlmw7I3s -tGqcShUaldjQYvkCggEBAN/1dQst70hWLtjRnP/0FidKtq3l2u0Lg6+K7CUsIOEa -QH1s0CobT5j7eWtodPkZkYCzulhiPXk32mW0uKiAglJ+LPaU7HgNrFlJKefCrStP -o8LcdeZujRhBkBvU+xytoxpKIhdie4td106sRCb63F66MtU+dSJqEl6/5Piz0zLT -YgrFitRaRA5/jW47BUV4ZBRnHqrBN4PhoaYPp7oYIue6E1G+REdsL9+I1B1PhUV2 -vmVHvoQkwqa1Ne9AZg1ZmTbnSojKV1c1T/uwwW/UEDo6v3+qMH/wTpXMk7DIE7ih -NW/FADYRHEd1M11zxLOMmq43C9/KD261N97H17NP3rcCggEBAJKdgzJ3C7li1m3P -NjmYeWKs0XxQXwHpCAnKPRCaYaSvbEOoPYQnhU5HDKsVQF8atID4gwV3w1H9mQtf -Y5cxhBxq2QxYwJkglxehzpwX0w7X0D/3L68m+UbDkbBKsa/ttPMXv0gAPBP+jC03 -dyBW08O/mQeZAvjzys8hJQciKw0RvlF8k7kK77ZQ8bteFzOJH6zwTMBUyaaBtuAb -KTCjT61wEPqO338JOTteyX+9vyXqPsD9vviRDqu1jWggZOOQsjTIw00EUtnSWeRD -15wEYQZgpIuGWUkVtOItGlkj73WlMPf9dQLvb4iE4N8uCVLqNlMN8RSAsE92Fmh5 -5jfW5XECggEAQEd5En5aoU5rH7v57dSmzxw4lmzUixi08RtUb87cmP8p51Xl4U/5 -ZpU24kcW27Ak/OWY5Gk9757CRlK6dVJ9FSQ1z4gq3sI951qCdox/m2C+Rd100XCF -eqLGs9ZLRI3ptE/2vPN9NiD2/ROgc/eobF/Q2zeT8w6yuxMkquUiBwJ4r1LHZ++I -fQjLFQpHlwrY3qpCOQw/3NBTzw/LOjRXQF890EZl3oIEs4nYJ5l9TNSqDPOskMzk -OWjlVAgNwmMnAIUd9Wjt7I/WpwyyWGBrT+swr3mvdekJBSG0ehbS4jkS10OZrer3 -TOMsnPPvTwFaHAqck9yw1TuaD40YMdUIvQKCAQAHpX7JP3Qbt7Q+hzq66BVWwlp6 -qdKKjlGGB7ciiFwuZWRI019ilbmmOjCfvFuVh4pyZgQH/TG/9HnZPBmuXd0Jy6VJ -SIQWZQ58G3SmIFqXZYA5Gxk2u4B/bPmptfPX/zxkaSV83dQu3L0PdPVnCTzv1qDn -MdCMbq7K53zF/j05tWRdF4iey64pmoBZx7G3Ky9cwdMsKTm/7AHi0UBTHwGCrDFL -BDS6XW1ylSa0QJrd2+yryae+N0iYXA+5WmY6yuLkUrGXcf96e3ufrs73di5R10IV -D38YeZHQEIK5gmfWC9Ma5HZb6TB/CtweirY4IddUiPEpHJFmOV+TkGBmntF6 ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +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 +9SXjVRx6nxQSlO4HYDaa2aw6W0+wdw== +-----END PRIVATE KEY----- diff --git a/tests/certs/client.cert.pem b/tests/certs/client.cert.pem index b6d9a91a485..14510751712 100644 --- a/tests/certs/client.cert.pem +++ b/tests/certs/client.cert.pem @@ -1,24 +1,33 @@ -----BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIUPfej8IQ/5bCrihqWImrq2vKPOq0wDQYJKoZIhvcNAQEL -BQAwgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU -b3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5j -cGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkq -hkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMB4XDTIxMDgwOTIxNTA1MloXDTMyMDEw -NDIxNTA1MlowgZUxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYD -VQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsM -DWFzeW5jcGcgdGVzdHMxETAPBgNVBAMMCHNzbF91c2VyMR0wGwYJKoZIhvcNAQkB -Fg5oZWxsb0BtYWdpYy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AJjiP9Ik/KRRLK9GMvoH8m1LO+Gyrr8Gz36LpmKJMR/PpwTL+1pOkYSGhOyT3Cw9 -/kWWLJRCvYqKgFtYtbr4S6ReGm3GdSVW+sfVRYDrRQZLPgQSPeq25g2v8UZ63Ota -lPAyUPUZKpxyWz8PL77lV8psb9yv14yBH2kv9BbxKPksWOU8p8OCn1Z3WFFl0ItO -nzMvCp5os+xFrt4SpoRGTx9x4QleY+zrEsYZtmnV4wC+JuJkNw4fuCdrX5k7dghs -uZkcsAZof1nMdYsYiazeDfQKZtJqh5kO7mpwvCudKUWaLJJUwiQA87BwSlnCd/Hh -TZDbC+zeFNjTS49/4Q72xVECAwEAAaM7MDkwHwYDVR0jBBgwFoAUi1jMmAisuOib -mHIE2n0W2WnnaL0wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwDQYJKoZIhvcNAQEL -BQADggEBACbnp5oOp639ko4jn8axF+so91k0vIcgwDg+NqgtSRsuAENGumHAa8ec -YOks0TCTvNN5E6AfNSxRat5CyguIlJ/Vy3KbkkFNXcCIcI/duAJvNphg7JeqYlQM -VIJhrO/5oNQMzzTw8XzTHnciGbrbiZ04hjwrruEkvmIAwgQPhIgq4H6umTZauTvk -DEo7uLm7RuG9hnDyWCdJxLLljefNL/EAuDYpPzgTeEN6JAnOu0ULIbpxpJKiYEId -8I0U2n0I2NTDOHmsAJiXf8BiHHmpK5SXFyY9s2ZuGkCzvmeZlR81tTXmHZ3v1X2z -8NajoAZfJ+QD50DrbF5E00yovZbyIB4= +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 -----END CERTIFICATE----- diff --git a/tests/certs/client.csr.pem b/tests/certs/client.csr.pem index c6a87c655d6..ed38fc1246e 100644 --- a/tests/certs/client.csr.pem +++ b/tests/certs/client.csr.pem @@ -1,18 +1,29 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIC2zCCAcMCAQAwgZUxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAw -DgYDVQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNV -BAsMDWFzeW5jcGcgdGVzdHMxETAPBgNVBAMMCHNzbF91c2VyMR0wGwYJKoZIhvcN -AQkBFg5oZWxsb0BtYWdpYy5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAJjiP9Ik/KRRLK9GMvoH8m1LO+Gyrr8Gz36LpmKJMR/PpwTL+1pOkYSGhOyT -3Cw9/kWWLJRCvYqKgFtYtbr4S6ReGm3GdSVW+sfVRYDrRQZLPgQSPeq25g2v8UZ6 -3OtalPAyUPUZKpxyWz8PL77lV8psb9yv14yBH2kv9BbxKPksWOU8p8OCn1Z3WFFl -0ItOnzMvCp5os+xFrt4SpoRGTx9x4QleY+zrEsYZtmnV4wC+JuJkNw4fuCdrX5k7 -dghsuZkcsAZof1nMdYsYiazeDfQKZtJqh5kO7mpwvCudKUWaLJJUwiQA87BwSlnC -d/HhTZDbC+zeFNjTS49/4Q72xVECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCG -irI2ph09V/4BMe6QMhjBFUatwmTa/05PYGjvT3LAhRzEb3/o/gca0XFSAFrE6zIY -DsgMk1c8aLr9DQsn9cf22oMFImKdnIZ3WLE9MXjN+s1Bjkiqt7uxDpxPo/DdfUTQ -RQC5i/Z2tn29y9K09lEjp35ZhPp3tOA0V4CH0FThAjRR+amwaBjxQ7TTSNfoMUd7 -i/DrylwnNg1iEQmYUwJYopqgxtwseiBUSDXzEvjFPY4AvZKmEQmE5QkybpWIfivt -1kmKhvKKpn5Cb6c0D3XoYqyPN3TxqjH9L8R+tWUCwhYJeDZj5DumFr3Hw/sx8tOL -EctyS6XfO3S2KbmDiyv8 +MIIE4TCCAskCAQAwgZsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKDAtFZGdlREIgSW5jLjEV +MBMGA1UECwwMRWRnZURCIHRlc3RzMREwDwYDVQQDDAhzc2xfdXNlcjEfMB0GCSqG +SIb3DQEJARYQaGVsbG9AZWRnZWRiLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAKI4Gq3AsHQ8sWyVk+bKgeYuoN0NdfXaALQNZiSZRYlzeiKadugm +3B5SVPU3ivIM+DzKxWIuZRq082xxflBGQIS3AARfjgUD4oSlq8kqnqZG8Gm6PgsA +pG64Dmr/jeU33U2KCtCHSS7ciCLMz3fBWsilaztdLFH0psDjFVGQTiWm0WDzgZNN +It7mXcvUoyb3/rFXgeE0mg35PqVhSlWyFrT4Vb7j1bK4T+yW4ulfx3TgzkGaRp4+ +NnoHWE1arDfT4XZxiav5t/S110W/UR1xMY0+wdCIu5Gl+JKUpazikEz+kJAC5OGJ +kW2nzMMYJ/MlnZWK84q+hM8QZshCHQdW5z87BFwe2382zOs5Zzj/w7XD2cI7pw+3 +Z3FAL79sx+quZZRJomaJTj/capBxK426oewcA4Ey2wmZa7zLVX8Xs0+vmWhm2/xC +/p+E5reLI/v0y2YIW0KmyBKU5X1Hew9b27jZgoao7dPIb36NNDIh9MhQ/pBU5PE1 +XGz679OnQJDZVNUNhZosZoBeuMTrp+g3n7Cuim0btsf3+SQBRlZftfBHJwTspc6H +GPmPKiLCOMSYzx+fZ1e4bw4X7wfkBf+C8uxt55mD1vtd5bvU41++97VtwkPt/9aA +ySd4cb7V6YJPA250+AvUUD5qkq54Si0+ZVJ4RdhBQdDma/skHuu+xS6tAgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAgEAdXiLGvPIm4u2XF8OyCi/2wbwT3Hswg8Z7AG+ +F4z6L3B+mDZNVtk+NQr3eAr9Eg5HsjC81VxSwXQtwjnzinAgpoNqRr70qhKeCjXr +szVHdN40L5J2RA+PeB9qFsUBXymt7qr9Cqb6a3s4sHTy8wMaYOmmkNqfdak674Tl +C7+Z/E9saASlgDDrlLLvzvPgC/y5gIhNYK7LIHI8yOtgd96R5Gs9/hyJ9y05+B9i +rnwok1QBkpy2cGnYoX+4Hn2gRIu6wMWfv5mj/wfn/gGihe8Fi2/89u+XRSZaZivZ +9CQzoLu1bajof8lSDPjORrL/ceiz7nNtKlkBCGtsscGUvuFXSMg7ENIukzGhCFI9 +Yo2ZVNnEjzaaBngVSdufVWkUhS/AfxxWKUDq/tS1zzX76PlZ1Cc847x6s+8+28uu +f9IOxwxVMJqEwtvIpG0ECsBBr+tgYIBaaSyUIHRlagOfeZ23HgJjYxa9Zvuxs+gb +GVheIgz1oZRtYL/MUa9UwptQ666gzcBXLQf5oCKzp73WIPVdp99r3y8P0b2X63Xz +Dhtp5dMfSlitOM7a3/Ddqt/iiSsq2caV5hbSElQsorxypbDv+roL851TRzqfHYSA +dZIgI0aWgVfsXDtByf8R7lfZWgwISsIKAomsSXIFihERKkzxWG1swCIs8WcqLcAM +6wHuhd0= -----END CERTIFICATE REQUEST----- diff --git a/tests/certs/client.key.pem b/tests/certs/client.key.pem index 90389cabcc0..aa63c1f5237 100644 --- a/tests/certs/client.key.pem +++ b/tests/certs/client.key.pem @@ -1,27 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAmOI/0iT8pFEsr0Yy+gfybUs74bKuvwbPfoumYokxH8+nBMv7 -Wk6RhIaE7JPcLD3+RZYslEK9ioqAW1i1uvhLpF4abcZ1JVb6x9VFgOtFBks+BBI9 -6rbmDa/xRnrc61qU8DJQ9RkqnHJbPw8vvuVXymxv3K/XjIEfaS/0FvEo+SxY5Tyn -w4KfVndYUWXQi06fMy8Knmiz7EWu3hKmhEZPH3HhCV5j7OsSxhm2adXjAL4m4mQ3 -Dh+4J2tfmTt2CGy5mRywBmh/Wcx1ixiJrN4N9Apm0mqHmQ7uanC8K50pRZosklTC -JADzsHBKWcJ38eFNkNsL7N4U2NNLj3/hDvbFUQIDAQABAoIBAAIMVeqM0E2rQLwA -ZsJuxNKuBVlauXiZsMHzQQFk8SGJ+KTZzr5A+zYZT0KUIIj/M57fCi3aTwvCG0Ie -CCE/HlRPZm8+D2e2qJlwxAOcI0qYS3ZmgCna1W4tgz/8eWU1y3UEV41RDv8VkR9h -JrSaAfkWRtFgEbUyLaeNGuoLxQ7Bggo9zi1/xDJz/aZ/y4L4y8l1xs2eNVmbRGnj -mPr1daeYhsWgaNiT/Wm3CAxvykptHavyWSsrXzCp0bEw6fAXxBqkeDFGIMVC9q3t -ZRFtqMHi9i7SJtH1XauOC6QxLYgSEmNEie1JYbNx2Zf4h2KvSwDxpTqWhOjJ/m5j -/NSkASECgYEAyHQAqG90yz5QaYnC9lgUhGIMokg9O3LcEbeK7IKIPtC9xINOrnj6 -ecCfhfc1aP3wQI+VKC3kiYerfTJvVsU5CEawBQSRiBY/TZZ7hTR7Rkm3s4xeM+o6 -2zADdVUwmTVYwu0gUKCeDKO4iD8Uhh8J54JrKUejuG50VWZQWGVgqo0CgYEAwz+2 -VdYcfuQykMA3jQBnXmMMK92/Toq6FPDgsa45guEFD6Zfdi9347/0Ipt+cTNg0sUZ -YBLOnNPwLn+yInfFa88Myf0UxCAOoZKfpJg/J27soUJzpd/CGx+vaAHrxMP6t/qo -JAGMBIyOoqquId7jvErlC/sGBk/duya7IdiT1tUCgYBuvM8EPhaKlVE9DJL9Hmmv -PK94E2poZiq3SutffzkfYpgDcPrNnh3ZlxVJn+kMqITKVcfz226On7mYP32MtQWt -0cc57m0rfgbYqRJx4y1bBiyK7ze3fGWpYxv1/OsNKJBxlygsAp9toiC2fAqtkYYa -NE1ZD6+dmr9/0jb+rnq5nQKBgQCtZvwsp4ePOmOeItgzJdSoAxdgLgQlYRd6WaN0 -qeLx1Z6FE6FceTPk1SmhQq+9IYAwMFQk+w78QU3iPg6ahfyTjsMw8M9sj3vvCyU1 -LPGJt/34CehjvKHLLQy/NlWJ3vPgSYDi2Wzc7WgQF72m3ykqpOlfBoWHPY8TE4bG -vG4wMQKBgFSq2GDAJ1ovBl7yWYW7w4SM8X96YPOff+OmI4G/8+U7u3dDM1dYeQxD -7BHLuvr4AXg27LC97u8/eFIBXC1elbco/nAKE1YHj2xcIb/4TsgAqkcysGV08ngi -dULh3q0GpTYyuELZV4bfWE8MjSiGAH+nuMdXYDGuY2QnBq8MdSOH ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +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 +g1WA14/4g+NcaVuglAM0na4pouro +-----END PRIVATE KEY----- diff --git a/tests/certs/client.key.protected.pem b/tests/certs/client.key.protected.pem index 0c5a9795465..e2b882cc99b 100644 --- a/tests/certs/client.key.protected.pem +++ b/tests/certs/client.key.protected.pem @@ -1,30 +1,54 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,B222CD7D00828606A07DBC489D400921 - -LRHsNGUsD5bG9+x/1UlzImN0rqEF10sFPBmxKeQpXQ/hy4iR+X/Gagoyagi23wOn -EZf0sCLJx95ixG+4fXJDX0jgBtqeziVNS4FLWHIuf3+blja8nf4tkmmH9pF8jFQ0 -i1an3TP6KRyDKa17gioOdtSsS51BZmPkp3MByJQsrMhyB0txEUsGtUMaBTYmVN/5 -uYHf9MsmfcfQy30nt2t6St6W82QupHHMOx5xyhPJo8cqQncZC7Dwo4hyDV3h3vWn -UjaRZiEMmQ3IgCwfJd1VmMECvrwXd/sTOXNhofWwDQIqmQ3GGWdrRnmgD863BQT3 -V8RVyPLkutOnrZ/kiMSAuiXGsSYK0TV8F9TaP/abLob4P8jbKYLcuR7ws3cu1xBl -XWt9RALxGPUyHIy+BWLXJTYL8T+TVJpiKsAGCQB54j8VQBSArwFL4LnzdUu1txe2 -qa6ZEwt4q6SEwOTJpJWz3oJ1j+OTsRCN+4dlyo7sEZMeyTRp9nUzwulhd+fOdIhY -2UllMG71opKfNxZzEW7lq6E/waf0MmxwjUJmgwVO218yag9oknHnoFwewF42DGY7 -072h23EJeKla7sI+MAB18z01z6C/yHWXLybOlXaGqk6zOm3OvTUFnUXtKzlBO2v3 -FQwrOE5U/VEyQkNWzHzh4j4LxYEL9/B08PxaveUwvNVGn9I3YknE6uMfcU7VuxDq -+6bgM6r+ez+9QLFSjH/gQuPs2DKX0h3b9ppQNx+MANX0DEGbGabJiBp887f8pG6Q -tW0i0+rfzYz3JwnwIuMZjYz6qUlP4bJMEmmDfod3fbnvg3MoCSMTUvi1Tq3Iiv4L -GM5/YNkL0V3PhOI686aBfU7GLGXQFhdbQ9xrSoQRBmmNBqTCSf+iIEoTxlBac8GQ -vSzDO+A+ovBP36K13Yn7gzuN/3PLZXH2TZ8t2b/OkEXOciH5KbycGHQA7gqxX1P4 -J55gpqPAWe8e7wKheWj3BMfmbWuH4rpiEkrLpqbTSfTwIKqplk253chmJj5I82XI -ioFLS5vCi9JJsTrQ720O+VQPVB5xeA80WL8NxamWQb/KkvVnb4dTmaV30RCgLLZC -tuMx8YSW71ALLT15qFB2zlMDKZO1jjunNE71BUFBPIkTKEOCyMAiF60fFeIWezxy -kvBBOg7+MTcZNeW110FqRWNGr2A5KYFN15g+YVpfEoF26slHisSjVW5ndzGh0kaQ -sIOjQitA9JYoLua7sHvsr6H5KdCGjNxv7O7y8wLGBVApRhU0wxZtbClqqEUvCLLP -UiLDp9L34wDL7sGrfNgWA4UuN29XQzTxI5kbv/EPKhyt2oVHLqUiE+eGyvnuYm+X -KqFi016nQaxTU5Kr8Pl0pSHbJMLFDWLSpsbbTB6YJpdEGxJoj3JB3VncOpwcuK+G -xZ1tV2orPt1s/6m+/ihzRgoEkyLwcLRPN7ojgD/sqS679ZGf1IkDMgFCQe4g0UWm -Fw7v816MNCgypUM5hQaU+Jp8vSlEc29RbrdSHbcxrKj/xPCLWrAbvmI5tgonKmuJ -J1LW8AXyh/EUp/uUh++jqVGx+8pFfcmJw6V6JrJzQ7HMlakkry7N1eAGrIJGtYCW ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/certs/client_ca.cert.pem b/tests/certs/client_ca.cert.pem index 17d3f357afc..becca16bea7 100644 --- a/tests/certs/client_ca.cert.pem +++ b/tests/certs/client_ca.cert.pem @@ -1,25 +1,36 @@ -----BEGIN CERTIFICATE----- -MIIEKTCCAxGgAwIBAgIUKmL8tfNS9LIB6GLB9RpZpTyk3uIwDQYJKoZIhvcNAQEL -BQAwgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYDVQQHDAdU -b3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsMDWFzeW5j -cGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0ExHTAbBgkq -hkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMB4XDTIxMDgwOTIxNDQxM1oXDTQxMDgw -NDIxNDQxM1owgaMxCzAJBgNVBAYTAkNBMRAwDgYDVQQIDAdPbnRhcmlvMRAwDgYD -VQQHDAdUb3JvbnRvMRgwFgYDVQQKDA9NYWdpY1N0YWNrIEluYy4xFjAUBgNVBAsM -DWFzeW5jcGcgdGVzdHMxHzAdBgNVBAMMFmFzeW5jcGcgdGVzdCBjbGllbnQgQ0Ex -HTAbBgkqhkiG9w0BCQEWDmhlbGxvQG1hZ2ljLmlvMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAptRYfxKiWExfZguQDva53bIqYa4lJwZA86Qu0peBUcsd -E6zyHNgVv4XSMim1FH12KQ4KPKuQAcVqRMCRAHqB96kUfWQqF//fLajr0umdzcbx -+UTgNux8TkScTl9KNAxhiR/oOGbKFcNSs4raaG8puwwEN66uMhoKk2pN2NwDVfHa -bTekJ3jouTcTCnqCynx4qwI4WStJkuW4IPCmDRVXxOOauT7YalElYLWYtAOqGEvf -noDK2Imhc0h6B5XW8nI54rVCXWwhW1v3RLAJGP+LwSy++bf08xmpHXdKkAj5BmUO -QwJRiJ33Xa17rmi385egx8KpqV04YEAPdV1Z4QM6PQIDAQABo1MwUTAdBgNVHQ4E -FgQUi1jMmAisuOibmHIE2n0W2WnnaL0wHwYDVR0jBBgwFoAUi1jMmAisuOibmHIE -2n0W2WnnaL0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAifNE -ZLZXxECp2Sl6jCViZxgFf2+OHDvRORgI6J0heckYyYF/JHvLaDphh6TkSJAdT6Y3 -hAb7jueTMI+6RIdRzIjTKCGdJqUetiSfAbnQyIp2qmVqdjeFoXTvQL7BdkIE+kOW -0iomMqDB3czTl//LrgVQCYqKM0D/Ytecpg2mbshLfpPxdHyliCJcb4SqfdrDnKoV -HUduBjOVot+6bkB5SEGCrrB4KMFTzbAu+zriKWWz+uycIyeVMLEyhDs59vptOK6e -gWkraG43LZY3cHPiVeN3tA/dWdyJf9rgK21zQDSMB8OSH4yQjdQmkkvRQBjp3Fcy -w2SZIP4o9l1Y7+hMMw== +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 +z1r76p+mCg== -----END CERTIFICATE----- diff --git a/tests/certs/client_ca.cert.srl b/tests/certs/client_ca.cert.srl index 0eae4d30e0f..5e2fab31bcb 100644 --- a/tests/certs/client_ca.cert.srl +++ b/tests/certs/client_ca.cert.srl @@ -1 +1 @@ -3DF7A3F0843FE5B0AB8A1A96226AEADAF28F3AAD +3DF7A3F0843FE5B0AB8A1A96226AEADAF28F3ABD diff --git a/tests/certs/client_ca.key.pem b/tests/certs/client_ca.key.pem index 519c5a09162..ad3a15a3d2f 100644 --- a/tests/certs/client_ca.key.pem +++ b/tests/certs/client_ca.key.pem @@ -1,27 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAptRYfxKiWExfZguQDva53bIqYa4lJwZA86Qu0peBUcsdE6zy -HNgVv4XSMim1FH12KQ4KPKuQAcVqRMCRAHqB96kUfWQqF//fLajr0umdzcbx+UTg -Nux8TkScTl9KNAxhiR/oOGbKFcNSs4raaG8puwwEN66uMhoKk2pN2NwDVfHabTek -J3jouTcTCnqCynx4qwI4WStJkuW4IPCmDRVXxOOauT7YalElYLWYtAOqGEvfnoDK -2Imhc0h6B5XW8nI54rVCXWwhW1v3RLAJGP+LwSy++bf08xmpHXdKkAj5BmUOQwJR -iJ33Xa17rmi385egx8KpqV04YEAPdV1Z4QM6PQIDAQABAoIBABQrKcO7CftoyEO6 -9CCK/W9q4arLddxg6itKVwrInC66QnqlduO7z+1GjWHZHvYqMMXH17778r30EuPa -7+zB4sKBI2QBXwFlwqJvgIsQCS7edVRwWjbpoiGIM+lZpcvjD0uXmuhurNGyumXQ -TJVBkyb0zfG5YX/XHB40RNMJzjFuiMPDLVQmmDE//FOuWqBG88MgJP9Ghk3J7wA2 -JfDPavb49EzOCSh74zJWP7/QyybzF3ABCMu4OFkaOdqso8FS659XI55QReBbUppu -FRkOgao1BclJhbBdrdtLNjlETM82tfVgW56vaIrrU2z7HskihEyMdB4c+CYbBnPx -QqIhkhUCgYEA0SLVExtNy5Gmi6/ZY9tcd3QIuxcN6Xiup+LgIhWK3+GIoVOPsOjN -27dlVRINPKhrCfVbrLxUtDN5PzphwSA2Qddm4jg3d5FzX+FgKHQpoaU1WjtRPP+w -K+t6W/NbZ8Rn4JyhZQ3Yqj264NA2l3QmuTfZSUQ5m4x7EUakfGU7G1sCgYEAzDaU -jHsovn0FedOUaaYl6pgzjFV8ByPeT9usN54PZyuzyc+WunjJkxCQqD88J9jyG8XB -3V3tQj/CNbMczrS2ZaJ29aI4b/8NwBNR9e6t01bY3B90GJi8S4B4Hf8tYyIlVdeL -tCC4FCZhvl4peaK3AWBj4NhjvdB32ThDXSGxLEcCgYEAiA5tKHz+44ziGMZSW1B+ -m4f1liGtf1Jv7fD/d60kJ/qF9M50ENej9Wkel3Wi/u9ik5v4BCyRvpouKyBEMGxQ -YA1OdaW1ECikMqBg+nB4FR1x1D364ABIEIqlk+SCdsOkANBlf2S+rCJ0zYUnvuhl -uOHIjo3AHJ4MAnU+1V7WUTkCgYBkMedioc7U34x/QJNR3sY9ux2Xnh2zdyLNdc+i -njeafDPDMcoXhcoJERiYpCYEuwnXHIlI7pvJZHUKWe4pcTsI1NSfIk+ki7SYaCJP -kyLQTY0rO3d/1fiU5tyIgzomqIs++fm+kEsg/8/3UkXxOyelUkDPAfy2FgGnn1ZV -7ID8YwKBgQCeZCapdGJ6Iu5oYB17TyE5pLwb+QzaofR5uO8H4pXGVQyilKVCG9Dp -GMnlXD7bwXPVKa8Icow2OIbmgrZ2mzOo9BSY3BlkKbpJDy7UNtAhzsHHN5/AEk8z -YycWQtMiXI+cRsYO0eyHhJeSS2hX+JTe++iZX65twV53agzCHWRIbg== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +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 +JMzZVaMH/iiG4xZmm1vh3roPi8Sq +-----END PRIVATE KEY----- diff --git a/tests/certs/gen.py b/tests/certs/gen.py index ef8bd1715e0..e9bf6399f82 100644 --- a/tests/certs/gen.py +++ b/tests/certs/gen.py @@ -185,6 +185,9 @@ def new_crl(path, issuer, cert): .last_update(datetime.datetime.today()) .next_update(datetime.datetime.today() + datetime.timedelta(days=1)) .add_revoked_certificate(revoked_cert) + .add_extension(x509.CRLNumber(1), critical=True) + .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key( + issuer_cert.public_key()), critical=True) ) crl = builder.sign(private_key=signing_key, algorithm=hashes.SHA256()) with open(path + ".crl.pem", "wb") as f: diff --git a/tests/certs/gen.sh b/tests/certs/gen.sh new file mode 100755 index 00000000000..ed53e24218a --- /dev/null +++ b/tests/certs/gen.sh @@ -0,0 +1,45 @@ +#!/bin/sh +set -e -x + +# Save original directory +ORIG_DIR=$(pwd) + +# Ensure we return to original directory even on error +trap 'cd "$ORIG_DIR"' EXIT + +cd tests/certs + +rm index.txt* +rm serial.txt +rm crlnumber.txt + +# Root CA +openssl genrsa -out ca.key.pem 4096 + +# Create empty database files required by ca.conf +touch index.txt +echo "01" > serial.txt +echo "01" > crlnumber.txt + +openssl req -new -x509 -key ca.key.pem -out ca.cert.pem -days 7300 -config ca.conf -batch -subj "/C=US/ST=California/L=San Francisco/O=EdgeDB Inc./OU=EdgeDB tests/CN=EdgeDB test root ca/emailAddress=hello@edgedb.com" + +# Server cert +openssl genrsa -out server.key.pem 4096 +openssl req -new -key server.key.pem -out server.csr.pem -subj "/C=US/ST=California/L=San Francisco/O=EdgeDB Inc./OU=EdgeDB tests/CN=localhost/emailAddress=hello@edgedb.com" -batch +openssl x509 -req -in server.csr.pem -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -out server.cert.pem -days 7300 -extensions v3_req -extfile ca.conf + +# Client CA +openssl genrsa -out client_ca.key.pem 4096 +openssl req -new -x509 -key client_ca.key.pem -out client_ca.cert.pem -days 7300 -subj "/C=US/ST=California/L=San Francisco/O=EdgeDB Inc./OU=EdgeDB tests/CN=EdgeDB test client CA/emailAddress=hello@edgedb.com" -batch + +# Client cert +openssl genrsa -out client.key.pem 4096 +openssl req -new -key client.key.pem -out client.csr.pem -subj "/C=US/ST=California/L=San Francisco/O=EdgeDB Inc./OU=EdgeDB tests/CN=ssl_user/emailAddress=hello@edgedb.com" -batch +openssl x509 -req -in client.csr.pem -CA client_ca.cert.pem -CAkey client_ca.key.pem -CAcreateserial -out client.cert.pem -days 7300 + +# Password protected client key +openssl rsa -aes256 -in client.key.pem -out client.key.protected.pem -passout pass:secret1234 + +# Revoke server cert and generate CRL +openssl ca -config ca.conf -revoke server.cert.pem -keyfile ca.key.pem -cert ca.cert.pem -batch -md sha256 +openssl ca -config ca.conf -gencrl -keyfile ca.key.pem -cert ca.cert.pem -out ca.crl.pem -batch -md sha256 diff --git a/tests/certs/server.cert.pem b/tests/certs/server.cert.pem index a4678151758..970908a810c 100644 --- a/tests/certs/server.cert.pem +++ b/tests/certs/server.cert.pem @@ -1,39 +1,36 @@ -----BEGIN CERTIFICATE----- -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 +m+hGEYaP23LwDLBwv9//+jNASPYLfA== -----END CERTIFICATE----- diff --git a/tests/certs/server.key.pem b/tests/certs/server.key.pem index 9c69c46cf14..65bf666b930 100644 --- a/tests/certs/server.key.pem +++ b/tests/certs/server.key.pem @@ -1,51 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAwvenCzhPXe+m+QEOdqK1YRnhKKGAeRo0oV7BfDAwhrgrnc2R -kGg+T5liQYh3ddj13LHPdLehhVz4B1tNkfZPLSeMDwjU8sNRWkdiAI3ZHRmVIVOh -Ru4BRzI4WqdZpa5cImlFaUjtHa/+w7ekHnllwodpbjH4Vgs9LWQiH8CdTVpj2clq -H78ZShlRvLyjo6OMQ6fbxAFtcYDGHwhR7JZ4VeCBm40O0Fl/c0ckmOtoYd1BTYX9 -RgIzTt0oV6ZiUH/SKRdYyb9GPUlfm0URK5j5MZPn10riACnaNEHytEREQEkpHWiD -RPcmlRCJarg4zhObuI5f6kUX9R1XrIKY4SAyDKzoSdxRFgYEWN6HyfylakU5LFnE -4ZAgihbzuFG4fGOf88F+KqaC6yvz/mvgxB8IPSDaILE37gGuJUTGhDGkKAVIB5Xb -WWR6e4VJcnmveu1z5+M6jwTR2+61y14h3WfACZLbAdPW1ivr6kjbaXlN658NEA1G -I/5eY7kVFAapoGdLOWlI7iXLGHrORLL7l2nh7+cYnHGPT3e5WHJZ67a0Jvtv0K/5 -dBgs2gwB+6FcXe2foKAmQ3/B5rAmshtb/0Ya4wRCglGxXgQQFCZseT5TAJhhHwbB -yqVFOgzvYSFw7gXQcfxfxf0LoUYK2O7WwqDJyargkIMDTZfaL+7ht6pfSmkCAwEA -AQKCAgAujTM1WpyYsUAM9FOfv/nO1X8NVIJ4Z+lpHlbUcC0l/ZNsekjnUfyOxPDQ -9OSRHtyVdV8zXyUR0sDmAMbkswr0nRyz+kfeLwSdqa2ctEHC0PjqnC1F4k4r0bHi -81JUXO1iyf/ow6DaFcuer5pgLFw/tlVWGlhRMx3IWMBNFJB6h7qPpafRLK+9IY6C -ogfwanxzKwEuK6kWEMk9X58v/j19Q72uhl+jH7tuqu3yFUM3Gr0c5YEz1hKqIeQg -CXov/lUPuqNYiHMc7wgE6tjOsBfP3qDcpuSPZW7US2rH4ATr1IwcmXe+X8S2ktw8 -vv/RNJ1Z06TTKuwtenQUnJokJqvMMESqEHdld5wwDo3MxCqvkcSUeS22cKlBZjeF -8/5wqpTMVpWxE7kfZFsMinBIV3gRPh8v87aDjrULJYltLQ6e8Pd0sAO0x0jAby8H -o5mjPSjHsK0m4vJyNB0paiWJcbRMQXpKX7U3smXxxAqWaqRgkkXk6wGICxX2oV34 -T6tvQ7GPCqNR8wnnXDx07imcHGAMeT62Zo15DrupP7eRxtIaO+f94HQiM6aIIcDv -kXyNZP0B1THj1C9eFy2hy6yvVOv1ZTtaXSXCOcY5dstDDKKZiAs2JTgcMtT5AZ7H -Q0JZAulk2AIeLHlNktUOZeYAA7nrJVS+PhsPcOep5N9CeM2EgQKCAQEA5P/I9jv2 -ZLfzTLJq3h9t7HMPGC/u+a7xD6ycTA4h5LAaUoMibkMobG7Sm/idNwvjUK3eobpz -KV01L5B69Om0GlaCn1zyDvPiZ+Z6COqwlHMIQcNR8ohNyGMIzkngJPh8RJN5a1a+ -NkT+lAsxAZx4RUWOs+PboTrqy3YUWQZLbTK5k0nBoAwW6V7PmdrjDAz4AU3nabQ4 -9JXacMd1gzB7/VWFt3rprR39yfmTrT8vR1w/DRnWmYpIx/DZ1MDvkIeWdrzFakyu -ah8HkW+tFB2BajnXfD+GD/L2sdEhez9YVjv/enJrNrsPRRk6yJoUTydkqPejBOOz -DJTfdQknWBnFKwKCAQEA2fSjGu8SS/Ah3DVAdhczX8YDHfJm1ctY5/uGG5TQI8Fn -pN6jBt8e7a5Aqb32naO73xYKQipiJ30Diqp5bPXv/4SJB17qCJvvoIlWXmy6ROnC -a7ndAR7N6VgJHf7uvuPa9CCZQkpP2fG3MPJXAfynVPY+D/xonZBV/o9lioBGEin+ -ENqVYjb7tX7h0Of/IbCzbTMnmEiCaz3Mm/8RME9Mh8BZfbJTUk9Sb/Q6oTMwMd9H -GcsZj4XYbxYGdHA28mFlZoIUdDesd8ZUWka21U6GVdz4OJtfoI7MJdqRzt7uEwJC -UixWWQn+LFpNFjKjKnhFFc4re52MvKB90R+kWErMuwKCAQAp2ZkXbwPrijabmKux -JltHcAudJv1sgg0qCSiTOa32Bjz5QV/keL+FeYdh28CXk8OaNfxO4C01rQQQSj4+ -TguNGKxMhYbzNpz00gkRYhqdNpyWsCRkx4Y3jenJEXU2LHdBbRYuiK7AakGAOr9d -BQRx3HFk7Mpxn7vTLSQw1NaqATAq+7q4Dh2Nzrbv7jG6PRCB5IPbLIWQJWbDX6BZ -Nl4igSOr0XmtGqML62GSss5oIzKeqU8vxjbg22Jj4FKnvi/ASWVmtNbXLA6NBLTD -zVSeXi3EVjOg7I0rGAYfaQcy00owTYLMgMkcnqzAhnAZuyBJROB0/0v0i6x+zgpz -rln7AoIBAQCHK1TMK2ApgC8/pjboBdNynhbox6BHDumAVVkCWfQLeLKSaRCp/k3s -EZlAq/L6KMUmwUBzcF2XJ8y+fqL3lD7XNJbW32I9HJgr84CA5uVOP7q3nHkXbMc+ -474jwCrIb/8mT+E8X2HORD3cOS8EqHAOHPi4aU1oCk+Ko9vRXWQXd7t9MFJcqsTH -9nyNVpO/jRp5qrPvmWhoodb3F+TNFSDdP8lATwuljFQP4mNJ/bjx9QrfUDn17Igh -vIMcS0uIXibIv/t3Z9+qGHHP2vMgrqZZMcUvNgzEQksRXs/2gAMd/tSqqZyTc8MS -Np6AGb9fY19U+pu0+iyB/vaIbxs5NoppAoIBAQCdpwKUiaGERg7f6I8f3fy+vvYQ -RyeNbizFKSEBwyE0b9izImUuKtlOqpK2XbuFZkEXVJ8juWU/7YurMIsBdosFegPu -qxtLEq2AOBtxxRWsLWZAaaesLh6MS0YJ6YjibuK1ITfiKInIkXdc65TQ6BXXsZme -4tQmnCY+C70iG5Xnt6ImH0/FEgnyBbbTHYvFqPTxDFy5Xu0cbtRgEu6rFK5GoYur -35BGoV1tYa50y3dHR79cDYp5sPM/qZ9teEnV++dQKCRJ4oOcGsYBHqc6tEjCLWpv -ji6ZAgx0TbI3oQtECNdpT2cSvYRdSrKQth7fPVo/FhLMrmc6d18cnZswXNYQ ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +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 +VOSY1MQIxTgVpg1s9emO9LzO+FedN5k= +-----END PRIVATE KEY----- diff --git a/tests/test_backend_connect.py b/tests/test_backend_connect.py index 01751eb4cf1..23c78f2831c 100644 --- a/tests/test_backend_connect.py +++ b/tests/test_backend_connect.py @@ -937,7 +937,7 @@ async def test_ssl_connection_client_auth_custom_context(self): sslmode=SSLMode.require, sslcert=CLIENT_SSL_CERT_FILE, sslrootcert=SSL_CA_CERT_FILE, - sslpassword='secRet', + sslpassword='secret1234', sslkey=key_file ) @@ -953,7 +953,7 @@ async def test_ssl_connection_client_auth_dsn(self): await self._test_works(dsn=dsn) params['sslkey'] = CLIENT_SSL_PROTECTED_KEY_FILE - params['sslpassword'] = 'secRet' + params['sslpassword'] = 'secret1234' params_str = urllib.parse.urlencode(params) dsn = 'postgres://ssl_user@localhost/postgres?' + params_str await self._test_works(dsn=dsn) @@ -970,14 +970,14 @@ async def test_ssl_connection_client_auth_env(self): env['PGSSLKEY'] = CLIENT_SSL_PROTECTED_KEY_FILE with unittest.mock.patch.dict('os.environ', env): - await self._test_works(dsn=dsn + '&sslpassword=secRet') + await self._test_works(dsn=dsn + '&sslpassword=secret1234') async def test_ssl_connection_client_auth_dot_postgresql(self): dsn = 'postgres://ssl_user@localhost/postgres?sslmode=verify-full' with mock_dot_postgresql(client=True): await self._test_works(dsn=dsn) with mock_dot_postgresql(client=True, protected=True): - await self._test_works(dsn=dsn + '&sslpassword=secRet') + await self._test_works(dsn=dsn + '&sslpassword=secret1234') class TestNoSSLConnection(BaseTestSSLConnection): From 0ca04ff611b088bb5406402c862dedf8f583ac51 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Mon, 27 Jan 2025 19:57:39 -0800 Subject: [PATCH 041/154] Produce proper errors on SQL syntax errors on native protocol (#8262) Fixes #8257. --- edb/pgsql/parser/exceptions.py | 6 +++++- edb/pgsql/parser/parser.pyx | 6 +++--- edb/server/compiler/errormech.py | 17 +++++++++++++++++ edb/server/protocol/execute.pyx | 5 +++++ edb/server/protocol/pg_ext.pyx | 2 +- tests/test_sql_query.py | 9 +++++++++ 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/edb/pgsql/parser/exceptions.py b/edb/pgsql/parser/exceptions.py index 82053a8f364..21e21320931 100644 --- a/edb/pgsql/parser/exceptions.py +++ b/edb/pgsql/parser/exceptions.py @@ -21,6 +21,10 @@ class PSqlParseError(Exception): + pass + + +class PSqlSyntaxError(PSqlParseError): def __init__(self, message, lineno, cursorpos): self.message = message self.lineno = lineno @@ -30,7 +34,7 @@ def __str__(self): return self.message -class PSqlUnsupportedError(Exception): +class PSqlUnsupportedError(PSqlParseError): def __init__(self, node: Optional[Any] = None, feat: Optional[str] = None): self.node = node self.location = None diff --git a/edb/pgsql/parser/parser.pyx b/edb/pgsql/parser/parser.pyx index b6ee5ada89c..51458f2a704 100644 --- a/edb/pgsql/parser/parser.pyx +++ b/edb/pgsql/parser/parser.pyx @@ -25,7 +25,7 @@ from typing import ( import enum import hashlib -from .exceptions import PSqlParseError +from .exceptions import PSqlSyntaxError from edb.server.pgproto.pgproto cimport ( @@ -88,7 +88,7 @@ def pg_parse(query) -> str: result = pg_query_parse(query) if result.error: - error = PSqlParseError( + error = PSqlSyntaxError( result.error.message.decode('utf8'), result.error.lineno, result.error.cursorpos ) @@ -139,7 +139,7 @@ def pg_normalize(query: str) -> NormalizedQuery: try: if result.error: - error = PSqlParseError( + error = PSqlSyntaxError( result.error.message.decode('utf8'), result.error.lineno, result.error.cursorpos ) diff --git a/edb/server/compiler/errormech.py b/edb/server/compiler/errormech.py index cbcbdd4ca56..2a361e0721a 100644 --- a/edb/server/compiler/errormech.py +++ b/edb/server/compiler/errormech.py @@ -31,6 +31,8 @@ from edb.graphql import types as gql_types +from edb.pgsql.parser import exceptions as parser_errors + from edb.schema import name as sn from edb.schema import objtypes as s_objtypes from edb.schema import pointers as s_pointers @@ -748,3 +750,18 @@ def _interpret_wrong_object_type( ) return errors.InternalServerError(err_details.message) + + +def static_interpret_psql_parse_error( + exc: parser_errors.PSqlParseError +) -> errors.EdgeDBError: + res: errors.EdgeDBError + if isinstance(exc, parser_errors.PSqlSyntaxError): + res = errors.EdgeQLSyntaxError(str(exc)) + res.set_position(exc.lineno, 0, exc.cursorpos - 1, None) + elif isinstance(exc, parser_errors.PSqlUnsupportedError): + res = errors.UnsupportedFeatureError(str(exc)) + else: + res = errors.InternalServerError(str(exc)) + + return res diff --git a/edb/server/protocol/execute.pyx b/edb/server/protocol/execute.pyx index 6f38e239055..9ec263f8c87 100644 --- a/edb/server/protocol/execute.pyx +++ b/edb/server/protocol/execute.pyx @@ -39,6 +39,8 @@ from edb.common import debug from edb import edgeql from edb.edgeql import qltypes +from edb.pgsql.parser import exceptions as parser_errors + from edb.server import compiler from edb.server import config from edb.server import defines as edbdef @@ -1009,6 +1011,9 @@ async def interpret_error( 'unhandled error while calling interpret_backend_error(); ' 'run with EDGEDB_DEBUG_SERVER to debug.') + elif isinstance(exc, parser_errors.PSqlParseError): + exc = errormech.static_interpret_psql_parse_error(exc) + return _check_for_ise(exc) diff --git a/edb/server/protocol/pg_ext.pyx b/edb/server/protocol/pg_ext.pyx index 59e699c36fd..8bd6728dfca 100644 --- a/edb/server/protocol/pg_ext.pyx +++ b/edb/server/protocol/pg_ext.pyx @@ -425,7 +425,7 @@ cdef class PgConnection(frontend.FrontendConnection): ) elif isinstance(exc, parser_errors.PSqlUnsupportedError): exc = pgerror.FeatureNotSupported(str(exc)) - elif isinstance(exc, parser_errors.PSqlParseError): + elif isinstance(exc, parser_errors.PSqlSyntaxError): exc = pgerror.new( pgerror.ERROR_SYNTAX_ERROR, str(exc), diff --git a/tests/test_sql_query.py b/tests/test_sql_query.py index 0b1a6f5d6ce..1c16454d580 100644 --- a/tests/test_sql_query.py +++ b/tests/test_sql_query.py @@ -2947,6 +2947,15 @@ async def test_sql_native_query_26(self): apply_access_policies=False, ) + async def test_sql_native_query_27(self): + with self.assertRaisesRegex( + edgedb.errors.EdgeQLSyntaxError, + 'syntax error at or near', + ): + await self.con.query_sql(''' + select (), asdf + ''') + class TestSQLQueryNonTransactional(tb.SQLQueryTestCase): From 67c2ac524fc8d11153034e2d07f95ba83c235f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Tue, 28 Jan 2025 19:23:33 +0100 Subject: [PATCH 042/154] Heed debug.disable_normalization over SQL adapter (#8265) --- edb/server/protocol/pg_ext.pxd | 1 + edb/server/protocol/pg_ext.pyx | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/edb/server/protocol/pg_ext.pxd b/edb/server/protocol/pg_ext.pxd index d8f897a5229..357849c6307 100644 --- a/edb/server/protocol/pg_ext.pxd +++ b/edb/server/protocol/pg_ext.pxd @@ -75,5 +75,6 @@ cdef class PgConnection(frontend.FrontendConnection): object endpoint_security bint is_tls bint _disable_cache + bint _disable_normalization cdef inline WriteBuffer ready_for_query(self) diff --git a/edb/server/protocol/pg_ext.pyx b/edb/server/protocol/pg_ext.pyx index 8bd6728dfca..0c1b71a78a2 100644 --- a/edb/server/protocol/pg_ext.pyx +++ b/edb/server/protocol/pg_ext.pyx @@ -381,6 +381,7 @@ cdef class PgConnection(frontend.FrontendConnection): self.is_tls = False self._disable_cache = debug.flags.disable_qcache + self._disable_normalization = debug.flags.edgeql_disable_normalization cdef _main_task_created(self): self.server.on_pgext_client_connected(self) @@ -939,7 +940,10 @@ cdef class PgConnection(frontend.FrontendConnection): actions = [] dbv = self._dbview - source = pg_parser.NormalizedSource.from_string(query_str) + if self._disable_normalization: + source = pg_parser.Source.from_string(query_str) + else: + source = pg_parser.NormalizedSource.from_string(query_str) query_units = await self.compile(source, dbv) # TODO: currently, normalization does not work with multiple queries @@ -1461,7 +1465,10 @@ cdef class PgConnection(frontend.FrontendConnection): """ stmts = set() - source = pg_parser.NormalizedSource.from_string(query_str) + if self._disable_normalization: + source = pg_parser.Source.from_string(query_str) + else: + source = pg_parser.NormalizedSource.from_string(query_str) query_units = await self.compile( source, dbv, ignore_cache=force_recompilation From e2893a3e5387f3b63f9f5765b7e4e7a4fb684a4a Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 28 Jan 2025 17:28:39 -0800 Subject: [PATCH 043/154] [sql] Fix native SQL protocol on old postgres versions (#8268) I broke it in #8256 by adding a RangeSubselect without an alias. --- edb/pgsql/ast.py | 5 +++++ edb/pgsql/resolver/__init__.py | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/edb/pgsql/ast.py b/edb/pgsql/ast.py index b683edba82b..345f3e7faa3 100644 --- a/edb/pgsql/ast.py +++ b/edb/pgsql/ast.py @@ -972,6 +972,11 @@ class WindowDef(ImmutableBase): class RangeSubselect(PathRangeVar): """Subquery appearing in FROM clauses.""" + # Before postgres 16, an alias is always required on selects from + # a subquery. Try to catch that with the typechecker by getting + # rid of the default value. + alias: Alias + lateral: bool = False subquery: Query diff --git a/edb/pgsql/resolver/__init__.py b/edb/pgsql/resolver/__init__.py index b3304b6c7a4..6dc622a955f 100644 --- a/edb/pgsql/resolver/__init__.py +++ b/edb/pgsql/resolver/__init__.py @@ -149,7 +149,10 @@ def resolve( val=expr.construct_row_expr(columns, ctx=ctx) ) ], - from_clause=[pgast.RangeSubselect(subquery=e)], + from_clause=[pgast.RangeSubselect( + subquery=e, + alias=pgast.Alias(aliasname='r'), + )], ctes=e.ctes, ) e.ctes = [] From 6d79b4987e2bad9a521bb947729d183665fec76d Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Wed, 29 Jan 2025 01:48:45 -0500 Subject: [PATCH 044/154] Remove aws-lc-* packages (#8270) These were causing issues on certain Ubuntu runners. As a bonus, it also removes a number of build support crates. --- Cargo.lock | 159 +------------------------------------ rust/gel-stream/Cargo.toml | 3 +- 2 files changed, 3 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 783105c1f30..46ad27739c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,31 +184,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "aws-lc-rs" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" -dependencies = [ - "aws-lc-sys", - "paste", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", - "paste", -] - [[package]] name = "backtrace" version = "0.3.73" @@ -259,29 +234,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools 0.10.5", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.89", - "which", -] - [[package]] name = "bitflags" version = "2.6.0" @@ -392,8 +344,6 @@ version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ - "jobserver", - "libc", "shlex", ] @@ -403,15 +353,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -424,17 +365,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.16" @@ -474,15 +404,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "cmake" -version = "0.1.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.2" @@ -728,12 +649,6 @@ dependencies = [ "syn 2.0.89", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "edb-graphql-parser" version = "0.3.0" @@ -959,12 +874,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures" version = "0.3.30" @@ -1279,15 +1188,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "hostname" version = "0.3.1" @@ -1642,15 +1542,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.72" @@ -1666,28 +1557,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" -[[package]] -name = "libloading" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - [[package]] name = "libm" version = "0.2.8" @@ -2234,16 +2109,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" -dependencies = [ - "proc-macro2", - "syn 2.0.89", -] - [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -2355,7 +2220,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", + "rustc-hash", "rustls", "socket2", "thiserror 2.0.3", @@ -2373,7 +2238,7 @@ dependencies = [ "getrandom", "rand", "ring", - "rustc-hash 2.1.0", + "rustc-hash", "rustls", "rustls-pki-types", "slab", @@ -2692,12 +2557,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.0" @@ -2732,7 +2591,6 @@ version = "0.23.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", @@ -2817,7 +2675,6 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3730,18 +3587,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "wide" version = "0.7.28" diff --git a/rust/gel-stream/Cargo.toml b/rust/gel-stream/Cargo.toml index a50e872823b..164d21125bd 100644 --- a/rust/gel-stream/Cargo.toml +++ b/rust/gel-stream/Cargo.toml @@ -20,7 +20,7 @@ thiserror = "2" rustls-pki-types = "1" tokio = { version = "1", optional = true, features = ["full"] } -rustls = { version = "0.23", optional = true } +rustls = { version = "0.23", optional = true, default-features = false, features = ["ring", "logging", "std", "tls12"] } openssl = { version = "0.10.55", optional = true } tokio-openssl = { version = "0.6.5", optional = true } hickory-resolver = { version = "0.24.2", optional = true } @@ -36,7 +36,6 @@ openssl-sys = { version = "*", optional = true } tokio = { version = "1", features = ["full"] } tempfile = "3" ntest = "0.9.3" -rustls = "0.23" rustls-pemfile = "2" rstest = "0.24.0" From 0fe5693ea2f9a888c838fa6eb0c3ee5fa97c95a3 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 29 Jan 2025 12:12:34 -0800 Subject: [PATCH 045/154] Introduce a lru_method_cache decorator for caching methods (#8267) The decorator arranges for a cache to be stored inside the object on first call. Inspired by #8261 but without per-object toil. Fixes #5377. --- edb/common/lru.py | 52 ++++++++++++++++++++++++++++++ edb/schema/schema.py | 9 +++--- edb/server/compiler/sertypes.py | 5 +-- edb/server/compiler_pool/pool.py | 7 ++-- edb/server/compiler_pool/server.py | 3 +- edb/server/pgcluster.py | 4 +-- edb/server/tenant.py | 4 +-- pyproject.toml | 4 --- 8 files changed, 70 insertions(+), 18 deletions(-) diff --git a/edb/common/lru.py b/edb/common/lru.py index 89963fbd9d8..2bbb29c120c 100644 --- a/edb/common/lru.py +++ b/edb/common/lru.py @@ -20,6 +20,10 @@ from __future__ import annotations import collections.abc +import functools + + +from typing import TypeVar, Callable class LRUMapping(collections.abc.MutableMapping): @@ -75,3 +79,51 @@ def __len__(self): def __iter__(self): return iter(self._dict) + + +Tf = TypeVar('Tf', bound=Callable) + + +class _NoPickle: + def __init__(self, obj): + self.obj = obj + + def __bool__(self): + return bool(self.obj) + + def __getstate__(self): + return () + + def __setstate__(self, _d): + self.obj = None + + +def lru_method_cache(size: int | None=128) -> Callable[[Tf], Tf]: + """A version of lru_cache for methods that shouldn't leak memory. + + Basically the idea is that we generate a per-object lru-cached + partially applied method. + + Since pickling an lru_cache of a lambda or a functools.partial + doesn't work, we wrap it in a _NoPickle object that doesn't pickle + its contents. + """ + def transformer(f: Tf) -> Tf: + key = f'__{f.__name__}_cached' + + def func(self, *args, **kwargs): + _m = getattr(self, key, None) + if not _m: + _m = _NoPickle( + functools.lru_cache(size)(functools.partial(f, self)) + ) + setattr(self, key, _m) + return _m.obj(*args, **kwargs) + + return func # type: ignore + + return transformer + + +def method_cache(f: Tf) -> Tf: + return lru_method_cache(None)(f) diff --git a/edb/schema/schema.py b/edb/schema/schema.py index e0098fc607e..8967207ea3b 100644 --- a/edb/schema/schema.py +++ b/edb/schema/schema.py @@ -51,6 +51,7 @@ from edb import errors from edb.common import adapter from edb.common import english +from edb.common import lru from . import casts as s_casts from . import functions as s_func @@ -1218,7 +1219,7 @@ def get_operators( type=s_oper.Operator, ) - @functools.lru_cache() + @lru.lru_method_cache() def _get_casts( self, stype: s_types.Type, @@ -1274,7 +1275,7 @@ def get_referrers( return self._get_referrers( scls, scls_type=scls_type, field_name=field_name) - @functools.lru_cache() + @lru.lru_method_cache() def _get_referrers( self, scls: so.Object, @@ -1312,8 +1313,8 @@ def _get_referrers( return frozenset(referrers) # type: ignore - @functools.lru_cache() - def get_referrers_ex( # type: ignore + @lru.lru_method_cache() + def get_referrers_ex( self, scls: so.Object, *, diff --git a/edb/server/compiler/sertypes.py b/edb/server/compiler/sertypes.py index dcc83fdeb49..5a1b5a3a562 100644 --- a/edb/server/compiler/sertypes.py +++ b/edb/server/compiler/sertypes.py @@ -45,8 +45,9 @@ from edb import errors from edb.common import binwrapper -from edb.common import value_dispatch +from edb.common import lru from edb.common import uuidgen +from edb.common import value_dispatch from edb.protocol import enums as p_enums from edb.server import config @@ -1900,7 +1901,7 @@ def get_global_type_rep( class CompilationConfigSerializer(InputShapeSerializer): - @functools.lru_cache(64) + @lru.lru_method_cache(64) def encode_configs( self, *configs: immutables.Map[str, config.SettingValue] | None ) -> bytes: diff --git a/edb/server/compiler_pool/pool.py b/edb/server/compiler_pool/pool.py index 4d3d69ca91c..9023024e6b8 100644 --- a/edb/server/compiler_pool/pool.py +++ b/edb/server/compiler_pool/pool.py @@ -37,6 +37,7 @@ import immutables from edb.common import debug +from edb.common import lru from edb.pgsql import params as pgparams @@ -189,7 +190,7 @@ def _get_init_args(self): assert self._dbindex is not None return self._make_init_args(*self._dbindex.get_cached_compiler_args()) - @functools.lru_cache(1) + @lru.lru_method_cache(1) def _make_init_args(self, dbs, global_schema_pickle, system_config): init_args = ( dbs, @@ -1103,7 +1104,7 @@ async def stop(self): if worker.done(): (await worker).close() - @functools.lru_cache(1) + @lru.lru_method_cache(1) def _make_init_args(self, dbs, global_schema_pickle, system_config): init_args = ( dbs, @@ -1337,7 +1338,7 @@ def drop_tenant(self, client_id: int): for worker in self._workers.values(): worker.invalidate(client_id) - @functools.cache + @lru.method_cache def _get_init_args(self): init_args = ( self._backend_runtime_params, diff --git a/edb/server/compiler_pool/server.py b/edb/server/compiler_pool/server.py index d5a5f42eca9..4596e226c8f 100644 --- a/edb/server/compiler_pool/server.py +++ b/edb/server/compiler_pool/server.py @@ -36,6 +36,7 @@ import immutables from edb.common import debug +from edb.common import lru from edb.common import markup from .. import metrics @@ -204,7 +205,7 @@ def _init(self, kwargs: dict[str, typing.Any]) -> None: # this is deferred to _init_server() pass - @functools.cache + @lru.method_cache def _get_init_args(self): init_args = ( self._backend_runtime_params, diff --git a/edb/server/pgcluster.py b/edb/server/pgcluster.py index c863772b107..b745173ec9e 100644 --- a/edb/server/pgcluster.py +++ b/edb/server/pgcluster.py @@ -36,7 +36,6 @@ import asyncio import copy -import functools import hashlib import json import logging @@ -53,6 +52,7 @@ from edb import buildmeta from edb import errors +from edb.common import lru from edb.common import supervisor from edb.common import uuidgen @@ -925,7 +925,7 @@ def stop_watching(self) -> None: if self._ha_backend is not None: self._ha_backend.stop_watching() - @functools.cache + @lru.method_cache def get_client_id(self) -> int: tenant_id = self._instance_params.tenant_id if self._ha_backend is not None: diff --git a/edb/server/tenant.py b/edb/server/tenant.py index 50130f2a17f..bbdb0939e69 100644 --- a/edb/server/tenant.py +++ b/edb/server/tenant.py @@ -35,7 +35,6 @@ import asyncio import contextlib import dataclasses -import functools import json import logging import os @@ -54,6 +53,7 @@ from edb import buildmeta from edb import errors from edb.common import asyncutil +from edb.common import lru from edb.common import retryloop from edb.common import verutils from edb.common.log import current_tenant @@ -355,7 +355,7 @@ def get_pg_dbname(self, dbname: str) -> str: def get_pgaddr(self) -> pgconnparams.ConnectionParams: return self._cluster.get_pgaddr() - @functools.lru_cache + @lru.method_cache def get_backend_runtime_params(self) -> pgparams.BackendRuntimeParams: return self._cluster.get_runtime_params() diff --git a/pyproject.toml b/pyproject.toml index a27450d7db9..6d601b285b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -259,10 +259,6 @@ ignore = [ # TODO: enable this "B905", # zip() without an explicit strict= parameter - - # TODO: enable this (this was tried before - it is non-trivial) - "B019", # Use of functools.lru_cache or functools.cache on methods can lead - # to memory leaks ] flake8-bugbear.extend-immutable-calls = ["immutables.Map"] From b3a3ed734d8fbf404c471f1bd056d8cbf19a0fb3 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 29 Jan 2025 12:19:56 -0800 Subject: [PATCH 046/154] Use better histogram buckets (#8263) Use a method of generating buckets based on specifying how many buckets to use per order of magnitude. For our bytes field, we use: (32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576) And for querys per connection, we use: (1.0, 5.0, 10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0) Fixes #8245. --- edb/common/prometheus.py | 33 ++++++++++++++++++++++++++++----- edb/server/metrics.py | 9 +++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/edb/common/prometheus.py b/edb/common/prometheus.py index 27999e206f8..58dff21b579 100644 --- a/edb/common/prometheus.py +++ b/edb/common/prometheus.py @@ -70,6 +70,7 @@ def calc_buckets( """Calculate histogram buckets on a logarithmic scale.""" # See https://amplitude.com/blog/2014/08/06/optimal-streaming-histograms # for more details. + # (Says a long standing comment, but this isn't what that post recommends!) result: list[float] = [] while start <= upper_bound: result.append(start) @@ -77,6 +78,28 @@ def calc_buckets( return tuple(result) +def per_order_buckets( + start: float, end: float, + *, + base: float=10.0, + entries_per_order=4, +) -> tuple[float, ...]: + # See https://amplitude.com/blog/2014/08/06/optimal-streaming-histograms + # for more details. + # (Actually, for this one.) + result: list[float] = [start] + + next = start * base + while next <= end: + for i in range(1, entries_per_order): + val = next / entries_per_order * i + if val > result[-1]: + result.append(val) + result.append(next) + next *= base + return tuple(result) + + class Unit(enum.Enum): # https://prometheus.io/docs/practices/naming/#base-units @@ -174,7 +197,7 @@ def new_histogram( /, *, unit: Unit | None = None, - buckets: list[float] | None = None, + buckets: typing.Sequence[float] | None = None, ) -> Histogram: hist = Histogram(self, name, desc, unit, buckets=buckets) self._add_metric(hist) @@ -187,7 +210,7 @@ def new_labeled_histogram( /, *, unit: Unit | None = None, - buckets: list[float] | None = None, + buckets: typing.Sequence[float] | None = None, labels: tuple[str, ...], ) -> LabeledHistogram: hist = LabeledHistogram( @@ -505,7 +528,7 @@ class BaseHistogram(BaseMetric): ] def __init__( - self, *args: typing.Any, buckets: list[float] | None = None + self, *args: typing.Any, buckets: typing.Sequence[float] | None = None ) -> None: if buckets is None: buckets = self.DEFAULT_BUCKETS @@ -530,7 +553,7 @@ class Histogram(BaseHistogram): _sum: float def __init__( - self, *args: typing.Any, buckets: list[float] | None = None + self, *args: typing.Any, buckets: typing.Sequence[float] | None = None ) -> None: super().__init__(*args, buckets=buckets) self._sum = 0.0 @@ -581,7 +604,7 @@ class LabeledHistogram(BaseHistogram): def __init__( self, *args: typing.Any, - buckets: list[float] | None = None, + buckets: typing.Sequence[float] | None = None, labels: tuple[str, ...], ) -> None: super().__init__(*args, buckets=buckets) diff --git a/edb/server/metrics.py b/edb/server/metrics.py index b2a72ade09d..c76b62378e3 100644 --- a/edb/server/metrics.py +++ b/edb/server/metrics.py @@ -24,6 +24,13 @@ registry = prom.Registry(prefix='edgedb_server') +COUNT_BUCKETS = prom.per_order_buckets( + 1, 10000, entries_per_order=2, +) +BYTES_BUCKETS = prom.per_order_buckets( + 32, 2**20, entries_per_order=1, base=2, +) + compiler_process_spawns = registry.new_counter( 'compiler_process_spawns_total', 'Total number of compiler processes spawned.' @@ -144,6 +151,7 @@ queries_per_connection = registry.new_labeled_histogram( 'queries_per_connection', 'Number of queries per connection.', + buckets=COUNT_BUCKETS, labels=('tenant', 'interface'), ) @@ -151,6 +159,7 @@ 'query_size', 'The size of a query.', unit=prom.Unit.BYTES, + buckets=BYTES_BUCKETS, labels=('tenant', 'interface'), ) From 221388e41a8e889add6d8049106a4eaeb75b5a75 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 29 Jan 2025 14:48:07 -0800 Subject: [PATCH 047/154] Try to leave more backend connections free in test suite (#8274) In our "Tests on PostgreSQL Versions", we regularly have failures due to being out of backend connections in start_edgedb_server tests that share the backend. Leave some more reserved for those tests and try to use few connections in them also. --- edb/server/args.py | 4 ++-- edb/testbase/server.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edb/server/args.py b/edb/server/args.py index 6428dbf33a2..ceed1d4f6f4 100644 --- a/edb/server/args.py +++ b/edb/server/args.py @@ -414,11 +414,11 @@ def compute_default_max_backend_connections() -> int: def adjust_testmode_max_connections(max_conns): # Some test cases will start a second EdgeDB server (default - # max_backend_connections=10), so we should reserve some backend + # max_backend_connections=5), so we should reserve some backend # connections for that. This is ideally calculated upon the edb test -j # option, but that also depends on the total available memory. We are # hard-coding 15 reserved connections here for simplicity. - return max(1, max_conns // 2, max_conns - 15) + return max(1, max_conns // 2, max_conns - 30) def _validate_compiler_pool_size(ctx, param, value): diff --git a/edb/testbase/server.py b/edb/testbase/server.py index ed2f28aabe1..c563684a284 100644 --- a/edb/testbase/server.py +++ b/edb/testbase/server.py @@ -2726,7 +2726,7 @@ def start_edgedb_server( bind_addrs: tuple[str, ...] = ('localhost',), auto_shutdown_after: Optional[int]=None, bootstrap_command: Optional[str]=None, - max_allowed_connections: Optional[int]=10, + max_allowed_connections: Optional[int]=5, compiler_pool_size: int=2, compiler_pool_mode: Optional[edgedb_args.CompilerPoolMode] = None, adjacent_to: Optional[tconn.Connection]=None, From a6b9ec5c6aa52a42173c3c05b7e227b857da0501 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 30 Jan 2025 08:32:26 -0800 Subject: [PATCH 048/154] Fix `ADMINISTER schema_repair()` (#8279) It got fully broken by an extra argument being added to some (but not all) s_ddl functions. Since arguments were being passed in a big untyped kwargs and there wasn't a test, it got missed. Add a test, too. --- edb/schema/ddl.py | 2 ++ tests/test_edgeql_ddl.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/edb/schema/ddl.py b/edb/schema/ddl.py index 3b8278917bb..cc2f0e65323 100644 --- a/edb/schema/ddl.py +++ b/edb/schema/ddl.py @@ -618,6 +618,7 @@ def apply_ddl_script_ex( stdmode: bool = False, internal_schema_mode: bool = False, testmode: bool = False, + store_migration_sdl: bool=False, schema_object_ids: Optional[ Mapping[Tuple[sn.Name, Optional[str]], uuid.UUID] ]=None, @@ -639,6 +640,7 @@ def apply_ddl_script_ex( stdmode=stdmode, internal_schema_mode=internal_schema_mode, testmode=testmode, + store_migration_sdl=store_migration_sdl, schema_object_ids=schema_object_ids, compat_ver=compat_ver, ) diff --git a/tests/test_edgeql_ddl.py b/tests/test_edgeql_ddl.py index 2962bd51042..3d569848ceb 100644 --- a/tests/test_edgeql_ddl.py +++ b/tests/test_edgeql_ddl.py @@ -17415,6 +17415,16 @@ async def test_edgeql_ddl_rebase_views_02(self): [{}], ) + async def test_edgeql_ddl_schema_repair(self): + await self.con.execute(''' + create type Tgt { + create property lol := count(Object) + } + ''') + await self.con.execute(''' + administer schema_repair() + ''') + async def test_edgeql_ddl_alias_and_create_set_required(self): await self.con.execute(r""" create type T; From 9a22e77d9ce4e2ef984aeb0787037d69ff62dd89 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Thu, 30 Jan 2025 08:32:55 -0800 Subject: [PATCH 049/154] Remove insertion of extra repairs in patch queue (#8280) Currently, if there is *any* repair in a patch queue, we insert a repair after *every* patch that modifies the std schema. This was in combination with deferring all repairs until the last one in the queue, which allowed us to avoid needing to load the current standard schema in order to do a repair. But we implemented support for doing that as part of patching user extensions, so merge all of the schema repair code into that. I tested this on 5.x's substantial collection of complex patches, and then forward ported. Since I had to resolve more conflicts there than I expected, I then also injected a testing repair into the 6.x backport (#8278), which shook out that `schema_repair()` is fully broken on 6.x (fixed in #8279), but no patch-specific issues. --- edb/pgsql/patches.py | 22 ++---------------- edb/server/bootstrap.py | 51 +++++++++++++++++++++++++++++++---------- edb/server/server.py | 50 +++++----------------------------------- 3 files changed, 47 insertions(+), 76 deletions(-) diff --git a/edb/pgsql/patches.py b/edb/pgsql/patches.py index 212b7e1b0e9..c3a23a933d4 100644 --- a/edb/pgsql/patches.py +++ b/edb/pgsql/patches.py @@ -43,24 +43,6 @@ def get_version_key(num_patches: int): return f'_v{num_major}' -def _setup_patches(patches: list[tuple[str, str]]) -> list[tuple[str, str]]: - """Do postprocessing on the patches list - - For technical reasons, we can't run a user schema repair if there - is a pending standard schema change, so when applying repairs we - always defer them to the *last* repair patch, and we ensure that - edgeql+schema is followed by a repair if necessary. - """ - seen_repair = False - npatches = [] - for kind, patch in patches: - npatches.append((kind, patch)) - if kind.startswith('edgeql+schema') and seen_repair: - npatches.append(('repair', '')) - seen_repair |= kind == 'repair' - return npatches - - """ The actual list of patches. The patches are (kind, script) pairs. @@ -76,5 +58,5 @@ def _setup_patches(patches: list[tuple[str, str]]) -> list[tuple[str, str]]: * repair - fix up inconsistencies in *user* schemas * sql-introspection - refresh all sql introspection views """ -PATCHES: list[tuple[str, str]] = _setup_patches([ -]) +PATCHES: list[tuple[str, str]] = [ +] diff --git a/edb/server/bootstrap.py b/edb/server/bootstrap.py index 0c6f11e16ce..21735b58004 100644 --- a/edb/server/bootstrap.py +++ b/edb/server/bootstrap.py @@ -686,8 +686,7 @@ def prepare_repair_patch( globalschema: s_schema.Schema, schema_class_layout: s_refl.SchemaClassLayout, backend_params: params.BackendRuntimeParams, - config: Any, -) -> bytes: +) -> str: compiler = edbcompiler.new_compiler( std_schema=stdschema, reflection_schema=reflschema, @@ -701,13 +700,13 @@ def prepare_repair_patch( ) res = edbcompiler.repair_schema(compilerctx) if not res: - return b"" + return "" sql, _, _ = res - return sql + return sql.decode('utf-8') -PatchEntry = tuple[tuple[str, ...], tuple[str, ...], dict[str, Any], bool] +PatchEntry = tuple[tuple[str, ...], tuple[str, ...], dict[str, Any]] async def gather_patch_info( @@ -786,6 +785,8 @@ def prepare_patch( patch_info: Optional[dict[str, list[str]]], user_schema: Optional[s_schema.Schema]=None, global_schema: Optional[s_schema.Schema]=None, + *, + dbname: Optional[str]=None, ) -> PatchEntry: val = f'{pg_common.quote_literal(json.dumps(num + 1))}::jsonb' # TODO: This is an INSERT because 2.0 shipped without num_patches. @@ -801,7 +802,7 @@ def prepare_patch( # Pure SQL patches are simple if kind == 'sql': - return (patch, update), (), {}, False + return (patch, update), (), {} # metaschema-sql: just recreate a function from metaschema if kind == 'metaschema-sql': @@ -809,11 +810,37 @@ def prepare_patch( create = dbops.CreateFunction(func(), or_replace=True) block = dbops.PLTopBlock() create.generate(block) - return (block.to_string(), update), (), {}, False + return (block.to_string(), update), (), {} if kind == 'repair': assert not patch - return (update,), (), {}, True + if not user_schema: + return (update,), (), dict(is_user_update=True) + assert global_schema + + # TODO: Implement the last-repair-only optimization? + try: + logger.info("repairing database '%s'", dbname) + sql = prepare_repair_patch( + schema, + reflschema, + user_schema, + global_schema, + schema_class_layout, + backend_params + ) + except errors.EdgeDBError as e: + if isinstance(e, errors.InternalServerError): + raise + raise errors.SchemaError( + f'Could not repair schema inconsistencies in ' + f'database branch "{dbname}". Probably the schema is ' + f'no longer valid due to a bug fix.\n' + f'Downgrade to the last working version, fix ' + f'the schema issue, and try again.' + ) from e + + return (update, sql), (), {} # EdgeQL and reflection schema patches need to be compiled. current_block = dbops.PLTopBlock() @@ -874,7 +901,7 @@ def prepare_patch( # There isn't anything to do on the system database for # userext updates. if user_schema is None: - return (update,), (), dict(is_user_ext_update=True), False + return (update,), (), dict(is_user_update=True) # Only run a userext update if the extension we are trying to # update is installed. @@ -883,7 +910,7 @@ def prepare_patch( s_exts.Extension, extension_name, default=None) if not extension: - return (update,), (), {}, False + return (update,), (), {} assert global_schema cschema = s_schema.ChainedSchema( @@ -1097,13 +1124,13 @@ def prepare_patch( sys_updates = (patch,) + sys_updates else: regular_updates = spatches + (update,) - # FIXME: This is a hack to make the is_user_ext_update cases + # FIXME: This is a hack to make the is_user_update cases # work (by ensuring we can always read their current state), # but this is actually a pretty dumb approach and we can do # better. regular_updates += sys_updates - return regular_updates, sys_updates, updates, False + return regular_updates, sys_updates, updates async def create_branch( diff --git a/edb/server/server.py b/edb/server/server.py index 9af2b193560..664d6402a8c 100644 --- a/edb/server/server.py +++ b/edb/server/server.py @@ -1343,7 +1343,7 @@ async def _prepare_patches( conn, f'patch_log_{idx}', pickle.dumps(entry)) patches[num] = entry - _, _, updates, _ = entry + _, _, updates = entry if 'std_and_reflection_schema' in updates: self._std_schema, self._refl_schema = updates[ 'std_and_reflection_schema'] @@ -1378,16 +1378,16 @@ async def _maybe_apply_patches( ) -> None: """Apply any un-applied patches to the database.""" num_patches = await self.get_patch_count(conn) - for num, (sql_b, syssql, keys, repair) in patches.items(): + for num, (sql_b, syssql, keys) in patches.items(): if num_patches <= num: if sys: sql_b += syssql logger.info("applying patch %d to database '%s'", num, dbname) sql = tuple(x.encode('utf-8') for x in sql_b) - # If we are doing a user_ext update, we need to - # actually run that against each user database. - if keys.get('is_user_ext_update'): + # For certain things, we need to actually run it + # against each user database. + if keys.get('is_user_update'): from . import bootstrap kind, patch = pg_patches.PATCHES[num] @@ -1421,49 +1421,11 @@ async def _maybe_apply_patches( patch_info=patch_info, user_schema=user_schema, global_schema=global_schema, + dbname=dbname, ) sql += tuple(x.encode('utf-8') for x in entry[0]) - # Only do repairs when they are the *last* pending - # repair in the patch queue. We make sure that every - # patch that changes the user schema is followed by a - # repair, so this allows us to only ever have to do - # repairs on up-to-date std schemas. - last_repair = repair and not any( - patches[i][3] for i in range(num + 1, len(patches)) - ) - if last_repair: - from . import bootstrap - - global_schema = await self.introspect_global_schema(conn) - user_schema = await self._introspect_user_schema( - conn, global_schema) - config_json = await self.introspect_db_config(conn) - db_config = self._parse_db_config(config_json, user_schema) - try: - logger.info("repairing database '%s'", dbname) - rep_sql = bootstrap.prepare_repair_patch( - self._std_schema, - self._refl_schema, - user_schema, - global_schema, - self._schema_class_layout, - self._tenant.get_backend_runtime_params(), - db_config, - ) - sql += (rep_sql,) - except errors.EdgeDBError as e: - if isinstance(e, errors.InternalServerError): - raise - raise errors.SchemaError( - f'Could not repair schema inconsistencies in ' - f'database "{dbname}". Probably the schema is ' - f'no longer valid due to a bug fix.\n' - f'Downgrade to the last working version, fix ' - f'the schema issue, and try again.' - ) from e - if sql: await conn.sql_execute(sql) logger.info( From 2d6035b2c3320dafb6fa81880774e4a413e4d729 Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus Date: Thu, 30 Jan 2025 10:44:27 -0800 Subject: [PATCH 050/154] config: Add support for remapping Postgres configs into Gel enums (#8275) We have a long-standing rule that configuration parameters should avoid using a boolean type and use enums instead (though we've not generally been super diligent about this always). Postgres, on the other hand, has lots of boolean settings. To solve this, teach the config framework how to remap backend config values onto arbitrary frontend enums. In general the below is all that is required: class EnabledDisabledEnum(enum.StrEnum): Enabled = "Enabled" Disabled = "Disabled" class EnabledDisabledType( EnumScalarType[EnabledDisabledEnum], edgeql_type="cfg::TestEnabledDisabledEnum", ): @classmethod def get_translation_map(cls) -> Mapping[EnabledDisabledEnum, str]: return { EnabledDisabledEnum.Enabled: "true", EnabledDisabledEnum.Disabled: "false", } --- edb/buildmeta.py | 2 +- edb/ir/staeval.py | 44 +++--- edb/ir/statypes.py | 234 +++++++++++++++++++++++++++++++- edb/lib/_testmode.edgeql | 10 +- edb/pgsql/metaschema.py | 128 ++++++++++++----- edb/schema/utils.py | 9 ++ edb/server/bootstrap.py | 19 +-- edb/server/compiler/sertypes.py | 26 +++- edb/server/config/ops.py | 7 + edb/server/config/spec.py | 25 +++- edb/server/config/types.py | 5 + tests/test_server_config.py | 91 +++++++++++++ 12 files changed, 515 insertions(+), 85 deletions(-) diff --git a/edb/buildmeta.py b/edb/buildmeta.py index a932bf85d71..6b8f53ab59c 100644 --- a/edb/buildmeta.py +++ b/edb/buildmeta.py @@ -60,7 +60,7 @@ # The merge conflict there is a nice reminder that you probably need # to write a patch in edb/pgsql/patches.py, and then you should preserve # the old value. -EDGEDB_CATALOG_VERSION = 2024_01_10_00_00 +EDGEDB_CATALOG_VERSION = 2024_01_28_00_00 EDGEDB_MAJOR_VERSION = 7 diff --git a/edb/ir/staeval.py b/edb/ir/staeval.py index 81fcbd32ce0..7ce152d8af8 100644 --- a/edb/ir/staeval.py +++ b/edb/ir/staeval.py @@ -43,7 +43,6 @@ from edb.common import typeutils from edb.common import parsing -from edb.common import uuidgen from edb.common import value_dispatch from edb.edgeql import ast as qlast from edb.edgeql import compiler as qlcompiler @@ -495,6 +494,9 @@ def bool_const_to_python( def cast_const_to_python(ir: irast.TypeCast, schema: s_schema.Schema) -> Any: schema, stype = irtyputils.ir_typeref_to_type(schema, ir.to_type) + if not isinstance(stype, s_scalars.ScalarType): + raise UnsupportedExpressionError( + "non-scalar casts are not supported in Python eval") pytype = scalar_type_to_python_type(stype, schema) sval = evaluate_to_python_val(ir.expr, schema=schema) return python_cast(sval, pytype) @@ -544,31 +546,23 @@ def schema_type_to_python_type( f'{stype.get_displayname(schema)} is not representable in Python') -typemap = { - 'std::str': str, - 'std::anyint': int, - 'std::anyfloat': float, - 'std::decimal': decimal.Decimal, - 'std::bigint': decimal.Decimal, - 'std::bool': bool, - 'std::json': str, - 'std::uuid': uuidgen.UUID, - 'std::duration': statypes.Duration, - 'cfg::memory': statypes.ConfigMemory, -} - - def scalar_type_to_python_type( - stype: s_types.Type, + stype: s_scalars.ScalarType, schema: s_schema.Schema, ) -> type: - for basetype_name, pytype in typemap.items(): - basetype = schema.get( - basetype_name, type=s_scalars.ScalarType, default=None) - if basetype and stype.issubclass(schema, basetype): - return pytype - - if stype.is_enum(schema): + typname = stype.get_name(schema) + pytype = statypes.maybe_get_python_type_for_scalar_type_name(str(typname)) + if pytype is None: + for ancestor in stype.get_ancestors(schema).objects(schema): + typname = ancestor.get_name(schema) + pytype = statypes.maybe_get_python_type_for_scalar_type_name( + str(typname)) + if pytype is not None: + break + + if pytype is not None: + return pytype + elif stype.is_enum(schema): return str raise UnsupportedExpressionError( @@ -618,8 +612,10 @@ def object_type_to_spec( ptype, schema, spec_class=spec_class, parent=parent, _memo=_memo) _memo[ptype] = pytype - else: + elif isinstance(ptype, s_scalars.ScalarType): pytype = scalar_type_to_python_type(ptype, schema) + else: + raise UnsupportedExpressionError(f"unsupported cast type: {ptype}") ptr_card: qltypes.SchemaCardinality = p.get_cardinality(schema) if ptr_card.is_known(): diff --git a/edb/ir/statypes.py b/edb/ir/statypes.py index 237d21b6cb9..8e86853f3b4 100644 --- a/edb/ir/statypes.py +++ b/edb/ir/statypes.py @@ -18,17 +18,34 @@ from __future__ import annotations -from typing import Any, Optional +from typing import ( + Any, + Callable, + ClassVar, + Generic, + Mapping, + Optional, + Self, + TypeVar, +) import dataclasses +import datetime +import decimal +import enum import functools import re import struct -import datetime +import uuid import immutables from edb import errors +from edb.common import parametric +from edb.common import uuidgen + +from edb.schema import name as s_name +from edb.schema import objects as s_obj MISSING: Any = object() @@ -100,6 +117,14 @@ def __init__(self, val: str, /) -> None: def to_backend_str(self) -> str: raise NotImplementedError + @classmethod + def to_backend_expr(cls, expr: str) -> str: + raise NotImplementedError("{cls}.to_backend_expr()") + + @classmethod + def to_frontend_expr(cls, expr: str) -> Optional[str]: + raise NotImplementedError("{cls}.to_frontend_expr()") + def to_json(self) -> str: raise NotImplementedError @@ -375,6 +400,14 @@ def to_timedelta(self) -> datetime.timedelta: def to_backend_str(self) -> str: return f'{self.to_microseconds()}us' + @classmethod + def to_backend_expr(cls, expr: str) -> str: + return f"edgedb_VER._interval_to_ms(({expr})::interval)::text || 'ms'" + + @classmethod + def to_frontend_expr(cls, expr: str) -> Optional[str]: + return None + def to_json(self) -> str: return self.to_iso8601() @@ -494,6 +527,14 @@ def to_backend_str(self) -> str: return f'{self._value}B' + @classmethod + def to_backend_expr(cls, expr: str) -> str: + return f"edgedb_VER.cfg_memory_to_str({expr})" + + @classmethod + def to_frontend_expr(cls, expr: str) -> Optional[str]: + return f"(edgedb_VER.str_to_cfg_memory({expr})::text || 'B')" + def to_json(self) -> str: return self.to_str() @@ -503,8 +544,195 @@ def __repr__(self) -> str: def __hash__(self) -> int: return hash(self._value) - def __eq__(self, other: object) -> bool: + def __eq__(self, other: Any) -> bool: if isinstance(other, ConfigMemory): return self._value == other._value else: return False + + +typemap = { + 'std::str': str, + 'std::anyint': int, + 'std::anyfloat': float, + 'std::decimal': decimal.Decimal, + 'std::bigint': decimal.Decimal, + 'std::bool': bool, + 'std::json': str, + 'std::uuid': uuidgen.UUID, + 'std::duration': Duration, + 'cfg::memory': ConfigMemory, +} + + +def maybe_get_python_type_for_scalar_type_name(name: str) -> Optional[type]: + return typemap.get(name) + + +E = TypeVar("E", bound=enum.StrEnum) + + +class EnumScalarType( + ScalarType, + parametric.SingleParametricType[E], + Generic[E], +): + """Configuration value represented by a custom string enum type that + supports arbitrary value mapping to backend (Postgres) configuration + values, e.g mapping "Enabled"/"Disabled" enum to a bool value, etc. + + We use SingleParametricType to obtain runtime access to the Generic + type arg to avoid having to copy-paste the constructors. + """ + + _val: E + _eql_type: ClassVar[Optional[s_name.QualName]] + + def __init_subclass__( + cls, + *, + edgeql_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + global typemap + super().__init_subclass__(**kwargs) + if edgeql_type is not None: + if edgeql_type in typemap: + raise TypeError( + f"{edgeql_type} is already a registered EnumScalarType") + typemap[edgeql_type] = cls + cls._eql_type = s_name.QualName.from_string(edgeql_type) + + def __init__( + self, + val: E | str, + ) -> None: + if isinstance(val, self.type): + self._val = val + elif isinstance(val, str): + try: + self._val = self.type(val) + except ValueError: + raise errors.InvalidValueError( + f'unexpected backend value for ' + f'{self.__class__.__name__}: {val!r}' + ) from None + + def to_str(self) -> str: + return str(self._val) + + def to_json(self) -> str: + return self._val + + def encode(self) -> bytes: + return self._val.encode("utf8") + + @classmethod + def get_translation_map(cls) -> Mapping[E, str]: + raise NotImplementedError + + @classmethod + def decode(cls, data: bytes) -> Self: + return cls(val=cls.type(data.decode("utf8"))) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash(self._val) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, type(self)): + return self._val == other._val + else: + return NotImplemented + + def __reduce__(self) -> tuple[ + Callable[..., EnumScalarType[Any]], + tuple[ + Optional[tuple[type, ...] | type], + E, + ], + ]: + assert type(self).is_fully_resolved(), \ + f'{type(self)} parameters are not resolved' + + cls: type[EnumScalarType[E]] = self.__class__ + types: Optional[tuple[type, ...]] = self.orig_args + if types is None or not cls.is_anon_parametrized(): + typeargs = None + else: + typeargs = types[0] if len(types) == 1 else types + return (cls.__restore__, (typeargs, self._val)) + + @classmethod + def __restore__( + cls, + typeargs: Optional[tuple[type, ...] | type], + val: E, + ) -> Self: + if typeargs is None or cls.is_anon_parametrized(): + obj = cls(val) + else: + obj = cls[typeargs](val) # type: ignore[index] + + return obj + + @classmethod + def get_edgeql_typeid(cls) -> uuid.UUID: + return s_obj.get_known_type_id('std::str') + + @classmethod + def get_edgeql_type(cls) -> s_name.QualName: + """Return fully-qualified name of the scalar type for this setting.""" + assert cls._eql_type is not None + return cls._eql_type + + def to_backend_str(self) -> str: + """Convert static frontend config value to backend config value.""" + return self.get_translation_map()[self._val] + + @classmethod + def to_backend_expr(cls, expr: str) -> str: + """Convert dynamic backend config value to frontend config value.""" + cases_list = [] + for fe_val, be_val in cls.get_translation_map().items(): + cases_list.append(f"WHEN lower('{fe_val}') THEN '{be_val}'") + cases = "\n".join(cases_list) + errmsg = f"unexpected frontend value for {cls.__name__}: %s" + err = f"edgedb_VER.raise(NULL::text, msg => format('{errmsg}', v))" + return ( + f"(SELECT CASE v\n{cases}\nELSE\n{err}\nEND " + f"FROM lower(({expr})) AS f(v))" + ) + + @classmethod + def to_frontend_expr(cls, expr: str) -> Optional[str]: + """Convert dynamic frontend config value to backend config value.""" + cases_list = [] + for fe_val, be_val in cls.get_translation_map().items(): + cases_list.append(f"WHEN lower('{be_val}') THEN '{fe_val}'") + cases = "\n".join(cases_list) + errmsg = f"unexpected backend value for {cls.__name__}: %s" + err = f"edgedb_VER.raise(NULL::text, msg => format('{errmsg}', v))" + return ( + f"(SELECT CASE v\n{cases}\nELSE\n{err}\nEND " + f"FROM lower(({expr})) AS f(v))" + ) + + +class EnabledDisabledEnum(enum.StrEnum): + Enabled = "Enabled" + Disabled = "Disabled" + + +class EnabledDisabledType( + EnumScalarType[EnabledDisabledEnum], + edgeql_type="cfg::TestEnabledDisabledEnum", +): + @classmethod + def get_translation_map(cls) -> Mapping[EnabledDisabledEnum, str]: + return { + EnabledDisabledEnum.Enabled: "true", + EnabledDisabledEnum.Disabled: "false", + } diff --git a/edb/lib/_testmode.edgeql b/edb/lib/_testmode.edgeql index 6fb29b1f620..761a5dc53bb 100644 --- a/edb/lib/_testmode.edgeql +++ b/edb/lib/_testmode.edgeql @@ -56,7 +56,9 @@ CREATE TYPE cfg::TestInstanceConfigStatTypes EXTENDING cfg::TestInstanceConfig { }; -CREATE SCALAR TYPE cfg::TestEnum extending enum; +CREATE SCALAR TYPE cfg::TestEnum EXTENDING enum; +CREATE SCALAR TYPE cfg::TestEnabledDisabledEnum + EXTENDING enum; ALTER TYPE cfg::AbstractConfig { @@ -141,6 +143,12 @@ ALTER TYPE cfg::AbstractConfig { CREATE ANNOTATION cfg::internal := 'true'; CREATE ANNOTATION cfg::backend_setting := '"max_connections"'; }; + + CREATE PROPERTY __check_function_bodies -> cfg::TestEnabledDisabledEnum { + CREATE ANNOTATION cfg::internal := 'true'; + CREATE ANNOTATION cfg::backend_setting := '"check_function_bodies"'; + SET default := cfg::TestEnabledDisabledEnum.Enabled; + }; }; diff --git a/edb/pgsql/metaschema.py b/edb/pgsql/metaschema.py index 8fcd01f5ed7..0a696b43072 100644 --- a/edb/pgsql/metaschema.py +++ b/edb/pgsql/metaschema.py @@ -1747,11 +1747,11 @@ class AssertJSONTypeFunction(trampoline.VersionedFunction): 'wrong_object_type', msg => coalesce( msg, - ( - 'expected JSON ' - || array_to_string(typenames, ' or ') - || '; got JSON ' - || coalesce(jsonb_typeof(val), 'UNKNOWN') + format( + 'expected JSON %s; got JSON %s: %s', + array_to_string(typenames, ' or '), + coalesce(jsonb_typeof(val), 'UNKNOWN'), + val::text ) ), detail => detail @@ -3392,7 +3392,7 @@ class InterpretConfigValueToJsonFunction(trampoline.VersionedFunction): - for memory size: we always convert to kilobytes; - already unitless numbers are left as is. - See https://www.postgresql.org/docs/12/config-setting.html + See https://www.postgresql.org/docs/current/config-setting.html for information about the units Postgres config system has. """ @@ -3444,6 +3444,53 @@ def __init__(self) -> None: ) +class PostgresJsonConfigValueToFrontendConfigValueFunction( + trampoline.VersionedFunction, +): + """Convert a Postgres config value to frontend config value. + + Most values are retained as-is, but some need translation, which + is implemented as a to_frontend_expr() on the corresponding + setting ScalarType. + """ + + def __init__(self, config_spec: edbconfig.Spec) -> None: + variants_list = [] + for setting in config_spec.values(): + if ( + setting.backend_setting + and isinstance(setting.type, type) + and issubclass(setting.type, statypes.ScalarType) + ): + conv_expr = setting.type.to_frontend_expr('"value"->>0') + if conv_expr is not None: + variants_list.append(f""" + WHEN {ql(setting.backend_setting)} + THEN to_jsonb({conv_expr}) + """) + + variants = "\n".join(variants_list) + text = f""" + SELECT ( + CASE "setting_name" + {variants} + ELSE "value" + END + ) + """ + + super().__init__( + name=('edgedb', '_postgres_json_config_value_to_fe_config_value'), + args=[ + ('setting_name', ('text',)), + ('value', ('jsonb',)) + ], + returns=('jsonb',), + volatility='immutable', + text=text, + ) + + class PostgresConfigValueToJsonFunction(trampoline.VersionedFunction): """Convert a Postgres setting to JSON value. @@ -3471,26 +3518,10 @@ class PostgresConfigValueToJsonFunction(trampoline.VersionedFunction): text = r""" SELECT - (CASE - - WHEN parsed_value.unit != '' - THEN - edgedb_VER._interpret_config_value_to_json( - parsed_value.val, - settings.vartype, - 1, - parsed_value.unit - ) - - ELSE - edgedb_VER._interpret_config_value_to_json( - "setting_value", - settings.vartype, - settings.multiplier, - settings.unit - ) - - END) + edgedb_VER._postgres_json_config_value_to_fe_config_value( + "setting_name", + backend_json_value.value + ) FROM LATERAL ( SELECT regexp_match( @@ -3521,8 +3552,29 @@ class PostgresConfigValueToJsonFunction(trampoline.VersionedFunction): as vartype, COALESCE(settings_in.multiplier, '1') as multiplier, COALESCE(settings_in.unit, '') as unit - ) as settings + ) AS settings + CROSS JOIN LATERAL + (SELECT + (CASE + WHEN parsed_value.unit != '' + THEN + edgedb_VER._interpret_config_value_to_json( + parsed_value.val, + settings.vartype, + 1, + parsed_value.unit + ) + + ELSE + edgedb_VER._interpret_config_value_to_json( + "setting_value", + settings.vartype, + settings.multiplier, + settings.unit + ) + END) AS value + ) AS backend_json_value """ def __init__(self) -> None: @@ -3748,11 +3800,14 @@ class SysConfigFullFunction(trampoline.VersionedFunction): pg_config AS ( SELECT spec.name, - edgedb_VER._interpret_config_value_to_json( - settings.setting, - settings.vartype, - settings.multiplier, - settings.unit + edgedb_VER._postgres_json_config_value_to_fe_config_value( + settings.name, + edgedb_VER._interpret_config_value_to_json( + settings.setting, + settings.vartype, + settings.multiplier, + settings.unit + ) ) AS value, source AS source, TRUE AS is_backend @@ -4123,12 +4178,9 @@ def __init__(self, config_spec: edbconfig.Spec) -> None: valql = '"value"->>0' if ( isinstance(setting.type, type) - and issubclass(setting.type, statypes.Duration) + and issubclass(setting.type, statypes.ScalarType) ): - valql = f""" - edgedb_VER._interval_to_ms(({valql})::interval)::text \ - || 'ms' - """ + valql = setting.type.to_backend_expr(valql) variants_list.append(f''' WHEN "name" = {ql(setting_name)} @@ -5244,6 +5296,8 @@ def get_bootstrap_commands( dbops.CreateFunction(TypeIDToConfigType()), dbops.CreateFunction(ConvertPostgresConfigUnitsFunction()), dbops.CreateFunction(InterpretConfigValueToJsonFunction()), + dbops.CreateFunction( + PostgresJsonConfigValueToFrontendConfigValueFunction(config_spec)), dbops.CreateFunction(PostgresConfigValueToJsonFunction()), dbops.CreateFunction(SysConfigFullFunction()), dbops.CreateFunction(SysConfigUncachedFunction()), diff --git a/edb/schema/utils.py b/edb/schema/utils.py index 88ddac369dc..bc98b25d250 100644 --- a/edb/schema/utils.py +++ b/edb/schema/utils.py @@ -1379,6 +1379,15 @@ def const_ast_from_python(val: Any, with_secrets: bool=False) -> qlast.Expr: ), expr=qlast.Constant.string(value=val.to_iso8601()), ) + elif isinstance(val, statypes.EnumScalarType): + qltype = val.get_edgeql_type() + return qlast.TypeCast( + type=qlast.TypeName( + maintype=qlast.ObjectRef( + module=qltype.module, name=qltype.name), + ), + expr=qlast.Constant.string(value=val.to_str()), + ) elif isinstance(val, statypes.CompositeType): return qlast.InsertQuery( subject=name_to_ast_ref(sn.name_from_string(val._tspec.name)), diff --git a/edb/server/bootstrap.py b/edb/server/bootstrap.py index 21735b58004..e7a526de6f4 100644 --- a/edb/server/bootstrap.py +++ b/edb/server/bootstrap.py @@ -50,9 +50,10 @@ from edb import errors from edb import edgeql -from edb.ir import statypes from edb.ir import typeutils as irtyputils from edb.edgeql import ast as qlast +from edb.edgeql import codegen as qlcodegen +from edb.edgeql import qltypes from edb.common import debug from edb.common import devmode @@ -71,6 +72,7 @@ from edb.schema import schema as s_schema from edb.schema import std as s_std from edb.schema import types as s_types +from edb.schema import utils as s_utils from edb.server import args as edbargs from edb.server import config @@ -614,7 +616,6 @@ def compile_bootstrap_script( expected_cardinality_one: bool = False, output_format: edbcompiler.OutputFormat = edbcompiler.OutputFormat.JSON, ) -> Tuple[s_schema.Schema, str]: - ctx = edbcompiler.new_compiler_context( compiler_state=compiler.state, user_schema=schema, @@ -1956,13 +1957,13 @@ async def _configure( backend_params.has_configfile_access ) ): - if isinstance(setting.default, statypes.Duration): - val = f'"{setting.default.to_iso8601()}"' - else: - val = repr(setting.default) - script = f''' - CONFIGURE INSTANCE SET {setting.name} := {val}; - ''' + script = qlcodegen.generate_source( + qlast.ConfigSet( + name=qlast.ObjectRef(name=setting.name), + scope=qltypes.ConfigScope.INSTANCE, + expr=s_utils.const_ast_from_python(setting.default), + ) + ) schema, sql = compile_bootstrap_script(compiler, schema, script) await _execute(ctx.conn, sql) diff --git a/edb/server/compiler/sertypes.py b/edb/server/compiler/sertypes.py index 5a1b5a3a562..b4aaba23112 100644 --- a/edb/server/compiler/sertypes.py +++ b/edb/server/compiler/sertypes.py @@ -2099,11 +2099,29 @@ class EnumDesc(SchemaTypeDesc): names: list[str] ancestors: Optional[list[TypeDesc]] - def encode(self, data: str) -> bytes: - return _encode_str(data) + @functools.cached_property + def _decoder(self) -> Callable[[bytes], Any]: + assert self.name is not None + pytype = statypes.maybe_get_python_type_for_scalar_type_name(self.name) + if pytype is not None and issubclass(pytype, statypes.ScalarType): + return pytype.decode + else: + return _decode_str + + @functools.cached_property + def _encoder(self) -> Callable[[Any], bytes]: + assert self.name is not None + pytype = statypes.maybe_get_python_type_for_scalar_type_name(self.name) + if pytype is not None and issubclass(pytype, statypes.ScalarType): + return pytype.encode + else: + return _encode_str + + def encode(self, data: Any) -> bytes: + return self._encoder(data) - def decode(self, data: bytes) -> str: - return _decode_str(data) + def decode(self, data: bytes) -> Any: + return self._decoder(data) @dataclasses.dataclass(frozen=True, kw_only=True) diff --git a/edb/server/config/ops.py b/edb/server/config/ops.py index fb66e90ea31..b9f7dab744b 100644 --- a/edb/server/config/ops.py +++ b/edb/server/config/ops.py @@ -98,6 +98,9 @@ def coerce_single_value(setting: spec.Setting, value: Any) -> Any: elif (isinstance(value, (str, int)) and _issubclass(setting.type, statypes.ConfigMemory)): return statypes.ConfigMemory(value) + elif (isinstance(value, str) and + _issubclass(setting.type, statypes.EnumScalarType)): + return setting.type(value) else: raise errors.ConfigurationError( f'invalid value type for the {setting.name!r} setting') @@ -352,6 +355,8 @@ def spec_to_json(spec: spec.Spec): typeid = s_obj.get_known_type_id('std::duration') elif _issubclass(setting.type, statypes.ConfigMemory): typeid = s_obj.get_known_type_id('cfg::memory') + elif _issubclass(setting.type, statypes.EnumScalarType): + typeid = setting.type.get_edgeql_typeid() elif isinstance(setting.type, types.ConfigTypeSpec): typeid = types.CompositeConfigType.get_edgeql_typeid() else: @@ -420,6 +425,8 @@ def value_from_json_value(spec: spec.Spec, setting: spec.Setting, value: Any): return statypes.Duration.from_iso8601(value) elif _issubclass(setting.type, statypes.ConfigMemory): return statypes.ConfigMemory(value) + elif _issubclass(setting.type, statypes.EnumScalarType): + return setting.type(value) else: return value diff --git a/edb/server/config/spec.py b/edb/server/config/spec.py index 6288b08978b..e41b8ef8af8 100644 --- a/edb/server/config/spec.py +++ b/edb/server/config/spec.py @@ -40,8 +40,12 @@ from . import types -SETTING_TYPES = {str, int, bool, float, - statypes.Duration, statypes.ConfigMemory} +SETTING_TYPES = { + str, + int, + bool, + float, +} @dataclasses.dataclass(frozen=True, eq=True) @@ -64,13 +68,20 @@ class Setting: protected: bool = False def __post_init__(self) -> None: - if (self.type not in SETTING_TYPES and - not isinstance(self.type, types.ConfigTypeSpec)): + if ( + self.type not in SETTING_TYPES + and not isinstance(self.type, types.ConfigTypeSpec) + and not ( + isinstance(self.type, type) + and issubclass(self.type, statypes.ScalarType) + ) + ): raise ValueError( f'invalid config setting {self.name!r}: ' f'type is expected to be either one of ' f'{{str, int, bool, float}} ' - f'or an edb.server.config.types.ConfigType subclass') + f'or an edb.server.config.types.ConfigType ', + f'or edb.ir.statypes.ScalarType subclass') if self.set_of: if not isinstance(self.default, frozenset): @@ -269,8 +280,10 @@ def _load_spec_from_type( ptype, schema, spec_class=types.ConfigTypeSpec, ) - else: + elif isinstance(ptype, s_scalars.ScalarType): pytype = staeval.scalar_type_to_python_type(ptype, schema) + else: + raise RuntimeError(f"unsupported config value type: {ptype}") attributes = {} for a, v in p.get_annotations(schema).items(schema): diff --git a/edb/server/config/types.py b/edb/server/config/types.py index 975cbbd8dbb..168bcfdd61f 100644 --- a/edb/server/config/types.py +++ b/edb/server/config/types.py @@ -203,6 +203,11 @@ def from_pyvalue( and isinstance(value, str | int) ): value = statypes.ConfigMemory(value) + elif ( + _issubclass(f_type, statypes.EnumScalarType) + and isinstance(value, str) + ): + value = f_type(value) elif not isinstance(f_type, type) or not isinstance(value, f_type): raise cls._err( diff --git a/tests/test_server_config.py b/tests/test_server_config.py index bb897039171..f59660f8fb9 100644 --- a/tests/test_server_config.py +++ b/tests/test_server_config.py @@ -40,6 +40,7 @@ from edb.protocol import messages from edb.testbase import server as tb + from edb.schema import objects as s_obj from edb.ir import statypes @@ -2140,6 +2141,96 @@ async def test_server_config_query_timeout(self): new_aborted += float(line.split(' ')[1]) self.assertEqual(orig_aborted, new_aborted) + @unittest.skipIf( + "EDGEDB_SERVER_MULTITENANT_CONFIG_FILE" in os.environ, + "cannot use CONFIGURE INSTANCE in multi-tenant mode", + ) + async def test_server_config_custom_enum(self): + async def assert_conf(con, name, expected_val): + val = await con.query_single(f''' + select assert_single(cfg::Config.{name}) + ''') + + self.assertEqual( + str(val), + expected_val + ) + + async with tb.start_edgedb_server( + security=args.ServerSecurityMode.InsecureDevMode, + ) as sd: + c1 = await sd.connect() + c2 = await sd.connect() + + await c2.query('create database test') + t1 = await sd.connect(database='test') + + # check that the default was set correctly + await assert_conf( + c1, '__check_function_bodies', 'Enabled') + + #### + + await c1.query(''' + configure instance set + __check_function_bodies + := cfg::TestEnabledDisabledEnum.Disabled; + ''') + + for c in {c1, c2, t1}: + await assert_conf( + c, '__check_function_bodies', 'Disabled') + + #### + + await t1.query(''' + configure current database set + __check_function_bodies := + cfg::TestEnabledDisabledEnum.Enabled; + ''') + + for c in {c1, c2}: + await assert_conf( + c, '__check_function_bodies', 'Disabled') + + await assert_conf( + t1, '__check_function_bodies', 'Enabled') + + #### + + await c2.query(''' + configure session set + __check_function_bodies := + cfg::TestEnabledDisabledEnum.Disabled; + ''') + await assert_conf( + c1, '__check_function_bodies', 'Disabled') + await assert_conf( + t1, '__check_function_bodies', 'Enabled') + await assert_conf( + c2, '__check_function_bodies', 'Disabled') + + #### + await c1.query(''' + configure instance reset + __check_function_bodies; + ''') + await t1.query(''' + configure session set + __check_function_bodies := + cfg::TestEnabledDisabledEnum.Disabled; + ''') + await assert_conf( + c1, '__check_function_bodies', 'Enabled') + await assert_conf( + t1, '__check_function_bodies', 'Disabled') + await assert_conf( + c2, '__check_function_bodies', 'Disabled') + + await c1.aclose() + await c2.aclose() + await t1.aclose() + class TestStaticServerConfig(tb.TestCase): @test.xerror("static config args not supported") From 38d246d886953dcf3fa2ca233ff85cd002c2879d Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Fri, 31 Jan 2025 09:34:55 -0500 Subject: [PATCH 051/154] Add a server mode to gel-stream (#8282) This adds a server mode to `gel-stream`, gated on a feature. We wrap up a significant amount of network and TLS complexity behind two simple concepts: the `Connector` and the `Acceptor`. OpenSSL and Rustls are supported for both client and server modes. Tests were added to ensure that they work with all combinations (which also shook out some additional bugs). In addition, synchronous lookup of TLS parameters by SNI is supported -- async lookup will probably be necessary in the future but is currently not implemented yet. In addition, `tests/certs` were regenerated as one of the certs wasn't v3, so rustls didn't like it. Note that client-side certificate validation is implemented, but there is no current way to read the accepted certificate on the server side. This will be added in a later patch. Accept and connect have similar APIs. Both also have TLS override forms where keys, verification modes and more may be specified. ``` let mut acceptor = Acceptor::new_tcp(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0)) .bind() .await?; let addr = acceptor.local_address()?; let accept_task = tokio::spawn(async move { let mut connection = acceptor.next().await.unwrap().unwrap(); let mut buf = String::new(); connection.read_to_string(&mut buf).await.unwrap(); assert_eq!(buf, "Hello, world!"); }); let connect_task = tokio::spawn(async move { let target = Target::new_resolved(addr); let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); stm.write_all(b"Hello, world!").await?; Ok::<_, std::io::Error>(()) }); ``` --- Cargo.lock | 398 ++++++------ rust/auth/src/lib.rs | 5 +- rust/gel-stream/Cargo.toml | 34 +- rust/gel-stream/src/client/connection.rs | 31 +- rust/gel-stream/src/client/mod.rs | 594 ------------------ rust/gel-stream/src/client/openssl.rs | 177 ------ rust/gel-stream/src/client/stream.rs | 240 ------- rust/gel-stream/src/common/mod.rs | 16 + rust/gel-stream/src/common/openssl.rs | 521 +++++++++++++++ .../src/{client => common}/rustls.rs | 285 ++++++--- rust/gel-stream/src/common/stream.rs | 343 ++++++++++ .../src/{client => common}/target.rs | 15 +- rust/gel-stream/src/common/tls.rs | 259 ++++++++ .../src/{client => common}/tokio_stream.rs | 79 ++- rust/gel-stream/src/lib.rs | 128 +++- rust/gel-stream/src/server/acceptor.rs | 178 ++++++ rust/gel-stream/src/server/mod.rs | 6 + rust/gel-stream/tests/socket.rs | 66 ++ rust/gel-stream/tests/tls.rs | 451 +++++++++++++ rust/pgrust/Cargo.toml | 3 +- rust/pgrust/examples/connect.rs | 2 +- rust/pgrust/src/connection/conn.rs | 4 +- rust/pgrust/src/connection/dsn/host.rs | 2 +- rust/pgrust/src/connection/mod.rs | 3 +- rust/pgrust/src/connection/raw_conn.rs | 37 +- rust/pgrust/src/handshake/edgedb_server.rs | 2 + rust/pgrust/src/handshake/mod.rs | 2 +- rust/pgrust/src/python.rs | 2 +- rust/pgrust/tests/query_real_postgres.rs | 2 +- rust/pgrust/tests/real_postgres.rs | 2 +- tests/certs/ca.cert.pem | 58 +- tests/certs/ca.crl.pem | 30 +- tests/certs/ca.key.pem | 100 +-- tests/certs/client.cert.pem | 65 +- tests/certs/client.csr.pem | 46 +- tests/certs/client.key.pem | 100 +-- tests/certs/client.key.protected.pem | 104 +-- tests/certs/client_ca.cert.pem | 56 +- tests/certs/client_ca.cert.srl | 2 +- tests/certs/client_ca.key.pem | 100 +-- tests/certs/gen.sh | 2 +- tests/certs/server.cert.pem | 54 +- tests/certs/server.key.pem | 100 +-- 43 files changed, 2922 insertions(+), 1782 deletions(-) delete mode 100644 rust/gel-stream/src/client/openssl.rs delete mode 100644 rust/gel-stream/src/client/stream.rs create mode 100644 rust/gel-stream/src/common/mod.rs create mode 100644 rust/gel-stream/src/common/openssl.rs rename rust/gel-stream/src/{client => common}/rustls.rs (53%) create mode 100644 rust/gel-stream/src/common/stream.rs rename rust/gel-stream/src/{client => common}/target.rs (97%) create mode 100644 rust/gel-stream/src/common/tls.rs rename rust/gel-stream/src/{client => common}/tokio_stream.rs (61%) create mode 100644 rust/gel-stream/src/server/acceptor.rs create mode 100644 rust/gel-stream/src/server/mod.rs create mode 100644 rust/gel-stream/tests/socket.rs create mode 100644 rust/gel-stream/tests/tls.rs diff --git a/Cargo.lock b/Cargo.lock index 46ad27739c5..1eab733d036 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,37 +4,19 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -61,15 +43,15 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -82,49 +64,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "append-only-vec" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d9f7083455f1a474276ccd32374958d2cb591024aac45101c7623b10271347" +checksum = "7992085ec035cfe96992dd31bfd495a2ebd31969bb95f624471cb6c0b349e571" [[package]] name = "approx" @@ -149,9 +131,9 @@ checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" [[package]] name = "async-compression" -version = "0.4.13" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "brotli", "flate2", @@ -180,23 +162,23 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -213,9 +195,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" dependencies = [ "autocfg", "libm", @@ -287,9 +269,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -313,9 +295,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "captive_postgres" @@ -340,9 +322,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] @@ -367,18 +349,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.16" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -388,9 +370,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -400,15 +382,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -456,7 +438,7 @@ dependencies = [ "statrs", "strum", "test-log", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -500,9 +482,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -595,7 +577,7 @@ dependencies = [ "derive_more", "paste", "pretty_assertions", - "thiserror 1.0.63", + "thiserror 1.0.69", "uuid", ] @@ -657,13 +639,13 @@ dependencies = [ "combine 3.8.1", "num-bigint 0.2.6", "num-traits", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] name = "edgedb-errors" version = "0.4.2" -source = "git+https://github.com/edgedb/edgedb-rust#b38fb4af07ae0017329eb3cce30ca37fe12acd29" +source = "git+https://github.com/edgedb/edgedb-rust#84c2fa7d78fe8dbbc8e3e4da136ad2eff2f2e465" dependencies = [ "bytes", ] @@ -671,7 +653,7 @@ dependencies = [ [[package]] name = "edgedb-protocol" version = "0.6.1" -source = "git+https://github.com/edgedb/edgedb-rust#b38fb4af07ae0017329eb3cce30ca37fe12acd29" +source = "git+https://github.com/edgedb/edgedb-rust#84c2fa7d78fe8dbbc8e3e4da136ad2eff2f2e465" dependencies = [ "bigdecimal", "bitflags", @@ -701,7 +683,7 @@ dependencies = [ "serde_json", "sha2", "snafu", - "thiserror 1.0.63", + "thiserror 1.0.69", "unicode-width", ] @@ -830,18 +812,18 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -850,6 +832,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -876,9 +864,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -891,9 +879,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -901,15 +889,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -918,15 +906,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -935,15 +923,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -953,9 +941,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -975,6 +963,8 @@ version = "0.1.0" dependencies = [ "derive_more", "foreign-types", + "futures", + "gel-stream", "hickory-resolver", "ntest", "openssl", @@ -1055,9 +1045,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -1075,7 +1065,7 @@ dependencies = [ "num-traits", "pretty_assertions", "pyo3", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -1099,12 +1089,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -1151,7 +1142,7 @@ dependencies = [ "ipnet", "once_cell", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -1174,7 +1165,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -1256,9 +1247,9 @@ checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -1452,9 +1443,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -1492,9 +1483,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "iter-read" -version = "0.3.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c397ca3ea05ad509c4ec451fea28b4771236a376ca1c69fd5143aae0cf8f93c4" +checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" [[package]] name = "itertools" @@ -1516,9 +1507,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni" @@ -1531,7 +1522,7 @@ dependencies = [ "combine 4.6.7", "jni-sys", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -1559,15 +1550,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.165" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linked-hash-map" @@ -1605,9 +1596,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ "hashbrown", ] @@ -1679,15 +1670,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1891,24 +1873,24 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags", "cfg-if", @@ -1937,9 +1919,9 @@ source = "git+https://github.com/edgedb/openssl-probe/?rev=e5ed593600d1f81286295 [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -2016,7 +1998,7 @@ dependencies = [ "serde", "serde_derive", "test-log", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tokio-openssl", "tracing", @@ -2068,9 +2050,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -2080,15 +2062,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "ppv-lite86" @@ -2101,9 +2083,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -2198,7 +2180,7 @@ dependencies = [ "nix", "pyo3", "scopeguard", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", "tracing-subscriber", @@ -2348,14 +2330,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2369,13 +2351,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -2386,9 +2368,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" @@ -2574,9 +2556,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags", "errno", @@ -2659,9 +2641,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-tokio-stream" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33" +checksum = "faa7dc7c991d9164e55bbf1558029eb5b84d32cc4d61a7df5b8641b2deedc4b3" dependencies = [ "futures", "rustls", @@ -2682,9 +2664,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -2712,9 +2694,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -2756,18 +2738,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde-pickle" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762ad136a26407c6a80825813600ceeab5e613660d93d79a41f0ec877171e71" +checksum = "b641fdc8bcf2781ee78b30c599700d64ad4f412976143e4c5d0b9df906bb4843" dependencies = [ "byteorder", "iter-read", @@ -2778,9 +2760,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -2789,9 +2771,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "indexmap", "itoa", @@ -2894,9 +2876,9 @@ dependencies = [ [[package]] name = "snafu" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b835cb902660db3415a672d862905e791e54d306c6e8189168c7f3d9ae1c79d" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" dependencies = [ "backtrace", "snafu-derive", @@ -2904,9 +2886,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1e02fca405f6280643174a50c942219f0bbf4dbf7d480f1dd864d6f211ae5" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" dependencies = [ "heck", "proc-macro2", @@ -3015,9 +2997,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -3041,15 +3023,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.11.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3076,11 +3058,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.63", + "thiserror-impl 1.0.69", ] [[package]] @@ -3094,9 +3076,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3221,9 +3203,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", @@ -3281,9 +3263,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -3332,36 +3314,36 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unindent" @@ -3415,9 +3397,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" [[package]] name = "valuable" @@ -3537,9 +3519,9 @@ checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -3589,9 +3571,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.28" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" dependencies = [ "bytemuck", "safe_arch", @@ -3880,9 +3862,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -3920,9 +3902,9 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" diff --git a/rust/auth/src/lib.rs b/rust/auth/src/lib.rs index b4b51a700d3..4310b15e060 100644 --- a/rust/auth/src/lib.rs +++ b/rust/auth/src/lib.rs @@ -34,7 +34,7 @@ pub enum AuthType { ScramSha256, } -#[derive(Debug, Clone)] +#[derive(derive_more::Debug, Clone)] pub enum CredentialData { /// A credential that always succeeds, regardless of input password. Due to /// the design of SCRAM-SHA-256, this cannot be used with that auth type. @@ -42,10 +42,13 @@ pub enum CredentialData { /// A credential that always fails, regardless of the input password. Deny, /// A plain-text password. + #[debug("Plain(...)")] Plain(String), /// A stored MD5 hash + salt. + #[debug("Md5(...)")] Md5(md5::StoredHash), /// A stored SCRAM-SHA-256 key. + #[debug("Scram(...)")] Scram(scram::StoredKey), } diff --git a/rust/gel-stream/Cargo.toml b/rust/gel-stream/Cargo.toml index 164d21125bd..d18dacf3a6e 100644 --- a/rust/gel-stream/Cargo.toml +++ b/rust/gel-stream/Cargo.toml @@ -7,7 +7,9 @@ description = "A library for streaming data between clients and servers." [features] # rustls or openssl imply tokio, and tokio is the only stream we support # at this time. -default = ["tokio", "rustls"] +default = ["tokio"] +client = [] +server = [] tokio = ["dep:tokio"] rustls = ["tokio", "dep:rustls", "dep:rustls-tokio-stream", "dep:rustls-platform-verifier", "dep:webpki"] openssl = ["tokio", "dep:openssl", "dep:tokio-openssl", "dep:foreign-types", "dep:openssl-sys"] @@ -17,29 +19,39 @@ __manual_tests = [] [dependencies] derive_more = { version = "1", features = ["full"] } thiserror = "2" -rustls-pki-types = "1" +futures = "0.3" -tokio = { version = "1", optional = true, features = ["full"] } +# Given that this library may be used in multiple contexts, we want to limit the +# features we enable by default. + +rustls-pki-types = { version = "1", default-features = false, features = ["std"] } + +# feature = "tokio" +tokio = { version = "1", optional = true, default-features = false, features = ["net", "rt"] } +hickory-resolver = { version = "0.24.2", optional = true, default-features = false, features = ["tokio-runtime", "system-config"] } + +# feature = "rustls" rustls = { version = "0.23", optional = true, default-features = false, features = ["ring", "logging", "std", "tls12"] } -openssl = { version = "0.10.55", optional = true } -tokio-openssl = { version = "0.6.5", optional = true } -hickory-resolver = { version = "0.24.2", optional = true } -rustls-tokio-stream = { version = "0.3.0", optional = true } +rustls-tokio-stream = { version = "0.5.0", optional = true } rustls-platform-verifier = { version = "0.5.0", optional = true } webpki = { version = "0.22", optional = true } +# feature = "openssl" +openssl = { version = "0.10.55", optional = true, default-features = false } +tokio-openssl = { version = "0.6.5", optional = true, default-features = false } # Get these from openssl -foreign-types = { version = "*", optional = true } -openssl-sys = { version = "*", optional = true } +foreign-types = { version = "*", optional = true, default-features = false } +openssl-sys = { version = "*", optional = true, default-features = false } [dev-dependencies] +# Run tests with all features enabled +gel-stream = { workspace = true, features = ["client", "server", "tokio", "rustls", "openssl"] } + tokio = { version = "1", features = ["full"] } tempfile = "3" ntest = "0.9.3" rustls-pemfile = "2" - rstest = "0.24.0" -rustls-tokio-stream = "0.3.0" [lints] workspace = true diff --git a/rust/gel-stream/src/client/connection.rs b/rust/gel-stream/src/client/connection.rs index 4b358d4e8a8..a51611ea453 100644 --- a/rust/gel-stream/src/client/connection.rs +++ b/rust/gel-stream/src/client/connection.rs @@ -1,27 +1,37 @@ +use std::marker::PhantomData; use std::net::SocketAddr; -use super::stream::UpgradableStream; -use super::target::{MaybeResolvedTarget, ResolvedTarget}; -use super::tokio_stream::Resolver; -use super::{ConnectionError, Ssl, Target, TlsInit}; +use crate::common::tokio_stream::{Resolver, TokioStream}; +use crate::{ConnectionError, Ssl, StreamUpgrade, TlsDriver, UpgradableStream}; +use crate::{MaybeResolvedTarget, ResolvedTarget, Target}; -type Connection = UpgradableStream>; +type Connection = UpgradableStream; /// A connector can be used to connect multiple times to the same target. -pub struct Connector { +#[allow(private_bounds)] +pub struct Connector { target: Target, resolver: Resolver, + driver: PhantomData, } -impl Connector { +impl Connector { pub fn new(target: Target) -> Result { + Self::new_explicit(target) + } +} + +#[allow(private_bounds)] +impl Connector { + pub fn new_explicit(target: Target) -> Result { Ok(Self { target, resolver: Resolver::new()?, + driver: PhantomData, }) } - pub async fn connect(&self) -> Result { + pub async fn connect(&self) -> Result, ConnectionError> { let stream = match self.target.maybe_resolved() { MaybeResolvedTarget::Resolved(target) => target.connect().await?, MaybeResolvedTarget::Unresolved(host, port, _) => { @@ -36,13 +46,14 @@ impl Connector { }; if let Some(ssl) = self.target.maybe_ssl() { - let mut stm = UpgradableStream::new(stream, Some(Ssl::init(ssl, self.target.name())?)); + let ssl = D::init_client(ssl, self.target.name())?; + let mut stm = UpgradableStream::new_client(stream, Some(ssl)); if !self.target.is_starttls() { stm.secure_upgrade().await?; } Ok(stm) } else { - Ok(UpgradableStream::new(stream, None)) + Ok(UpgradableStream::new_client(stream, None)) } } } diff --git a/rust/gel-stream/src/client/mod.rs b/rust/gel-stream/src/client/mod.rs index 4f1688e18ab..93d91fc99a6 100644 --- a/rust/gel-stream/src/client/mod.rs +++ b/rust/gel-stream/src/client/mod.rs @@ -1,597 +1,3 @@ -use std::borrow::Cow; - -#[cfg(feature = "openssl")] -pub mod openssl; -#[cfg(feature = "rustls")] -pub mod rustls; -#[cfg(feature = "tokio")] -pub mod tokio_stream; - -pub mod stream; - mod connection; -pub(crate) mod target; pub use connection::Connector; -pub use target::{ResolvedTarget, Target, TargetName}; - -macro_rules! __invalid_state { - ($error:literal) => {{ - eprintln!( - "Invalid connection state: {}\n{}", - $error, - ::std::backtrace::Backtrace::capture() - ); - #[allow(deprecated)] - $crate::client::ConnectionError::__InvalidState - }}; -} -pub(crate) use __invalid_state as invalid_state; -use rustls_pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer, ServerName}; - -#[derive(Debug, thiserror::Error)] -pub enum ConnectionError { - /// Invalid state error, suggesting a logic error in code rather than a server or client failure. - /// Use the `invalid_state!` macro instead which will print a backtrace. - #[error("Invalid state")] - #[deprecated = "Use invalid_state!"] - __InvalidState, - - /// I/O error encountered during connection operations. - #[error("I/O error: {0}")] - Io(#[from] std::io::Error), - - /// UTF-8 decoding error. - #[error("UTF8 error: {0}")] - Utf8Error(#[from] std::str::Utf8Error), - - /// SSL-related error. - #[error("SSL error: {0}")] - SslError(#[from] SslError), -} - -#[derive(Debug, thiserror::Error)] -pub enum SslError { - #[error("SSL is not supported by this client transport")] - SslUnsupportedByClient, - - #[cfg(feature = "openssl")] - #[error("OpenSSL error: {0}")] - OpenSslError(#[from] ::openssl::ssl::Error), - #[cfg(feature = "openssl")] - #[error("OpenSSL error: {0}")] - OpenSslErrorStack(#[from] ::openssl::error::ErrorStack), - #[cfg(feature = "openssl")] - #[error("OpenSSL certificate verification error: {0}")] - OpenSslErrorVerify(#[from] ::openssl::x509::X509VerifyResult), - - #[cfg(feature = "rustls")] - #[error("Rustls error: {0}")] - RustlsError(#[from] ::rustls::Error), - - #[cfg(feature = "rustls")] - #[error("Webpki error: {0}")] - WebpkiError(::webpki::Error), - - #[cfg(feature = "rustls")] - #[error("Verifier builder error: {0}")] - VerifierBuilderError(#[from] ::rustls::server::VerifierBuilderError), - - #[error("Invalid DNS name: {0}")] - InvalidDnsNameError(#[from] ::rustls_pki_types::InvalidDnsNameError), - - #[error("SSL I/O error: {0}")] - SslIoError(#[from] std::io::Error), -} - -impl SslError { - /// Returns a common error for any time of crypto backend. - pub fn common_error(&self) -> Option { - match self { - #[cfg(feature = "rustls")] - SslError::RustlsError(::rustls::Error::InvalidCertificate(cert_err)) => { - match cert_err { - ::rustls::CertificateError::NotValidForName => { - Some(CommonError::InvalidCertificateForName) - } - ::rustls::CertificateError::Revoked => Some(CommonError::CertificateRevoked), - ::rustls::CertificateError::Expired => Some(CommonError::CertificateExpired), - ::rustls::CertificateError::UnknownIssuer => Some(CommonError::InvalidIssuer), - _ => None, - } - } - #[cfg(feature = "openssl")] - SslError::OpenSslErrorVerify(e) => match e.as_raw() { - openssl_sys::X509_V_ERR_HOSTNAME_MISMATCH => { - Some(CommonError::InvalidCertificateForName) - } - openssl_sys::X509_V_ERR_IP_ADDRESS_MISMATCH => { - Some(CommonError::InvalidCertificateForName) - } - openssl_sys::X509_V_ERR_CERT_REVOKED => Some(CommonError::CertificateRevoked), - openssl_sys::X509_V_ERR_CERT_HAS_EXPIRED => Some(CommonError::CertificateExpired), - openssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT - | openssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY => { - Some(CommonError::InvalidIssuer) - } - _ => None, - }, - _ => None, - } - } -} - -#[derive(Debug, thiserror::Error, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] -pub enum CommonError { - #[error("The certificate's subject name(s) do not match the name of the host")] - InvalidCertificateForName, - #[error("The certificate has been revoked")] - CertificateRevoked, - #[error("The certificate has expired")] - CertificateExpired, - #[error("The certificate was issued by an untrusted authority")] - InvalidIssuer, -} - -// Note that we choose rustls when both openssl and rustls are enabled. - -#[cfg(all(feature = "openssl", not(feature = "rustls")))] -pub type Ssl = ::openssl::ssl::Ssl; -#[cfg(feature = "rustls")] -pub type Ssl = ::rustls::ClientConnection; - -#[cfg(feature = "tokio")] -pub type Stream = tokio_stream::TokioStream; - -/// Verification modes for TLS that are a superset of both PostgreSQL and EdgeDB/Gel. -/// -/// Postgres offers six levels: `disable`, `allow`, `prefer`, `require`, `verify-ca` and `verify-full`. -/// -/// EdgeDB/Gel offers three levels: `insecure`, `no_host_verification' and 'strict'. -/// -/// This table maps the various levels: -/// -/// | Postgres | EdgeDB/Gel | `TlsServerCertVerify` enum | -/// | -------- | ----------- | ----------------- | -/// | require | insecure | `Insecure` | -/// | verify-ca | no_host_verification | `IgnoreHostname` | -/// | verify-full | strict | `VerifyFull` | -/// -/// Note that both EdgeDB/Gel and Postgres may alter certificate validation levels -/// when custom root certificates are provided. This must be done in the -/// `TlsParameters` struct by the caller. -#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] -pub enum TlsServerCertVerify { - /// Do not verify the server's certificate. Only confirm that the server is - /// using TLS. - Insecure, - /// Verify the server's certificate using the CA (ignore hostname). - IgnoreHostname, - /// Verify the server's certificate using the CA and hostname. - #[default] - VerifyFull, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub enum TlsCert { - /// Use the system's default certificate. - #[default] - System, - /// Use a custom root certificate only. - Custom(CertificateDer<'static>), -} - -#[derive(Default, Debug, PartialEq, Eq)] -pub struct TlsParameters { - pub server_cert_verify: TlsServerCertVerify, - pub cert: Option>, - pub key: Option>, - pub root_cert: TlsCert, - pub crl: Vec>, - pub min_protocol_version: Option, - pub max_protocol_version: Option, - pub enable_keylog: bool, - pub sni_override: Option>, - pub alpn: Option]>>, -} - -impl TlsParameters { - pub fn insecure() -> Self { - Self { - server_cert_verify: TlsServerCertVerify::Insecure, - ..Default::default() - } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum SslVersion { - Tls1, - Tls1_1, - Tls1_2, - Tls1_3, -} - -trait TlsInit { - type Tls; - fn init(params: &TlsParameters, name: Option) -> Result; -} - -#[cfg(test)] -mod tests { - use std::{net::SocketAddr, sync::Arc}; - - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - - use super::*; - - #[cfg(unix)] - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_unix() -> Result<(), std::io::Error> { - use tokio::io::AsyncReadExt; - - let tempdir = tempfile::tempdir().unwrap(); - let path = tempdir.path().join("gel-stream-test"); - - // Create a unix socket and connect to it - let socket = tokio::net::UnixListener::bind(&path)?; - - let accept_task = tokio::spawn(async move { - let (mut stream, _) = socket.accept().await.unwrap(); - let mut buf = String::new(); - stream.read_to_string(&mut buf).await.unwrap(); - assert_eq!(buf, "Hello, world!"); - }); - - let connect_task = tokio::spawn(async { - let target = Target::new_unix_path(path)?; - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"Hello, world!").await?; - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp() -> Result<(), std::io::Error> { - // Create a TCP listener on a random port - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?; - let addr = listener.local_addr()?; - - let accept_task = tokio::spawn(async move { - let (mut stream, _) = listener.accept().await.unwrap(); - let mut buf = String::new(); - stream.read_to_string(&mut buf).await.unwrap(); - assert_eq!(buf, "Hello, world!"); - }); - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp(("127.0.0.1", addr.port())); - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"Hello, world!").await?; - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - fn load_test_cert() -> rustls_pki_types::CertificateDer<'static> { - rustls_pemfile::certs( - &mut include_str!("../../../../tests/certs/server.cert.pem").as_bytes(), - ) - .next() - .unwrap() - .unwrap() - } - - fn load_test_ca() -> rustls_pki_types::CertificateDer<'static> { - rustls_pemfile::certs(&mut include_str!("../../../../tests/certs/ca.cert.pem").as_bytes()) - .next() - .unwrap() - .unwrap() - } - - fn load_test_key() -> rustls_pki_types::PrivateKeyDer<'static> { - rustls_pemfile::private_key( - &mut include_str!("../../../../tests/certs/server.key.pem").as_bytes(), - ) - .unwrap() - .unwrap() - } - - fn load_test_crls() -> Vec> { - rustls_pemfile::crls(&mut include_str!("../../../../tests/certs/ca.crl.pem").as_bytes()) - .collect::, _>>() - .unwrap() - } - - async fn spawn_tls_server( - expected_hostname: Option<&str>, - server_alpn: Option<&[&str]>, - expected_alpn: Option<&str>, - ) -> Result< - ( - SocketAddr, - tokio::task::JoinHandle>, - ), - std::io::Error, - > { - use ::rustls::{ServerConfig, ServerConnection}; - - let _ = ::rustls::crypto::ring::default_provider().install_default(); - - // Create a TCP listener on a random port - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?; - let addr = listener.local_addr()?; - - // Load TLS cert and key - let cert = load_test_cert(); - let key = load_test_key(); - - // Configure rustls server - let mut config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(vec![cert], key) - .unwrap(); - config.alpn_protocols = server_alpn - .map(|alpn| alpn.iter().map(|s| s.as_bytes().to_vec()).collect()) - .unwrap_or_default(); - - let tls_config = Arc::new(config); - let expected_alpn = expected_alpn.map(|alpn| alpn.as_bytes().to_vec()); - let expected_hostname = expected_hostname.map(|sni| sni.to_string()); - let accept_task = tokio::spawn(async move { - let (tcp_stream, _) = listener.accept().await.unwrap(); - let tls_conn = ServerConnection::new(tls_config).unwrap(); - let mut stream = - rustls_tokio_stream::TlsStream::new_server_side_from(tcp_stream, tls_conn, None); - let handshake = stream.handshake().await?; - eprintln!("handshake: {:?}", handshake); - assert_eq!(handshake.alpn, expected_alpn); - assert_eq!(handshake.sni, expected_hostname); - let mut buf = String::new(); - stream.read_to_string(&mut buf).await.unwrap(); - assert_eq!(buf, "Hello, world!"); - stream.shutdown().await?; - Ok::<_, std::io::Error>(()) - }); - Ok((addr, accept_task)) - } - - /// The certificate is not valid for 127.0.0.1, so the connection should fail. - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_verify_full_fails() -> Result<(), std::io::Error> { - let (addr, accept_task) = spawn_tls_server(None, None, None).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("127.0.0.1", addr.port()), - TlsParameters { - ..Default::default() - }, - ); - let stm = Connector::new(target).unwrap().connect().await; - assert!( - matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::InvalidIssuer)), - "{stm:?}" - ); - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap().unwrap_err(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - /// The certificate is not valid for 127.0.0.1, so the connection should fail. - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_verify_full_fails_name() -> Result<(), std::io::Error> { - let (addr, accept_task) = spawn_tls_server(None, None, None).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("127.0.0.1", addr.port()), - TlsParameters { - root_cert: TlsCert::Custom(load_test_ca()), - ..Default::default() - }, - ); - let stm = Connector::new(target).unwrap().connect().await; - assert!( - matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::InvalidCertificateForName)), - "{stm:?}" - ); - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap().unwrap_err(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - /// The certificate is valid for "localhost", so the connection should succeed. - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_verify_full_ok() -> Result<(), std::io::Error> { - let (addr, accept_task) = spawn_tls_server(Some("localhost"), None, None).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("localhost", addr.port()), - TlsParameters { - root_cert: TlsCert::Custom(load_test_ca()), - ..Default::default() - }, - ); - let mut stm = Connector::new(target).unwrap().connect().await?; - stm.write_all(b"Hello, world!").await?; - stm.shutdown().await?; - Ok::<_, ConnectionError>(()) - }); - - accept_task.await.unwrap().unwrap(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_insecure() -> Result<(), std::io::Error> { - let (addr, accept_task) = spawn_tls_server(None, None, None).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("127.0.0.1", addr.port()), - TlsParameters { - server_cert_verify: TlsServerCertVerify::Insecure, - ..Default::default() - }, - ); - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"Hello, world!").await?; - stm.shutdown().await?; - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap().unwrap(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_crl() -> Result<(), std::io::Error> { - let (addr, accept_task) = spawn_tls_server(Some("localhost"), None, None).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("localhost", addr.port()), - TlsParameters { - root_cert: TlsCert::Custom(load_test_ca()), - crl: load_test_crls(), - ..Default::default() - }, - ); - let stm = Connector::new(target).unwrap().connect().await; - assert!( - matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::CertificateRevoked)), - "{stm:?}" - ); - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap().unwrap_err(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - /// Test that we can override the SNI. - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_sni_override() -> Result<(), std::io::Error> { - let (addr, accept_task) = spawn_tls_server(Some("www.google.com"), None, None).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("127.0.0.1", addr.port()), - TlsParameters { - server_cert_verify: TlsServerCertVerify::Insecure, - sni_override: Some(Cow::Borrowed("www.google.com")), - ..Default::default() - }, - ); - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"Hello, world!").await.unwrap(); - stm.shutdown().await?; - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap().unwrap(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - /// Test that we can override the ALPN. - #[tokio::test] - #[ntest::timeout(30_000)] - async fn test_target_tcp_tls_alpn_override() -> Result<(), std::io::Error> { - let (addr, accept_task) = - spawn_tls_server(None, Some(&["nope", "accepted"]), Some("accepted")).await?; - - let connect_task = tokio::spawn(async move { - let target = Target::new_tcp_tls( - ("127.0.0.1", addr.port()), - TlsParameters { - server_cert_verify: TlsServerCertVerify::Insecure, - alpn: Some(Cow::Borrowed(&[ - Cow::Borrowed("accepted"), - Cow::Borrowed("fake"), - ])), - ..Default::default() - }, - ); - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"Hello, world!").await.unwrap(); - stm.shutdown().await?; - Ok::<_, std::io::Error>(()) - }); - - accept_task.await.unwrap().unwrap(); - connect_task.await.unwrap().unwrap(); - - Ok(()) - } - - #[cfg(feature = "__manual_tests")] - #[tokio::test] - async fn test_live_server_manual_google_com() { - let target = Target::new_tcp_tls(("www.google.com", 443), TlsParameters::default()); - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap(); - // HTTP/1. ..... - assert_eq!(stm.read_u8().await.unwrap(), b'H'); - } - - /// Normally connecting to Google's IP will send an invalid SNI and fail. - /// This test ensures that we can override the SNI to the correct hostname. - #[cfg(feature = "__manual_tests")] - #[tokio::test] - async fn test_live_server_google_com_override_sni() { - use std::net::ToSocketAddrs; - - let addr = "www.google.com:443" - .to_socket_addrs() - .unwrap() - .into_iter() - .next() - .unwrap(); - let target = Target::new_tcp_tls( - addr, - TlsParameters { - sni_override: Some(Cow::Borrowed("www.google.com")), - ..Default::default() - }, - ); - let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); - stm.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap(); - // HTTP/1. ..... - assert_eq!(stm.read_u8().await.unwrap(), b'H'); - } -} diff --git a/rust/gel-stream/src/client/openssl.rs b/rust/gel-stream/src/client/openssl.rs deleted file mode 100644 index b020b4026e5..00000000000 --- a/rust/gel-stream/src/client/openssl.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::pin::Pin; - -use openssl::{ - ssl::{SslContextBuilder, SslMethod, SslVerifyMode}, - x509::{verify::X509VerifyFlags, X509VerifyResult}, -}; -use rustls_pki_types::ServerName; - -use super::{ - stream::{Stream, StreamWithUpgrade}, - SslError, SslVersion, TlsCert, TlsInit, TlsParameters, TlsServerCertVerify, -}; - -impl StreamWithUpgrade for (S, Option) { - type Base = S; - type Config = openssl::ssl::Ssl; - type Upgrade = tokio_openssl::SslStream; - - async fn secure_upgrade(self) -> Result - where - Self: Sized, - { - let Some(tls) = self.1 else { - return Err(super::SslError::SslUnsupportedByClient); - }; - - let mut stream = tokio_openssl::SslStream::new(tls, self.0)?; - let res = Pin::new(&mut stream).do_handshake().await; - if res.is_err() { - if stream.ssl().verify_result() != X509VerifyResult::OK { - return Err(SslError::OpenSslErrorVerify(stream.ssl().verify_result())); - } - } - res.map_err(SslError::OpenSslError)?; - Ok(stream) - } -} - -impl From for openssl::ssl::SslVersion { - fn from(val: SslVersion) -> Self { - match val { - SslVersion::Tls1 => openssl::ssl::SslVersion::TLS1, - SslVersion::Tls1_1 => openssl::ssl::SslVersion::TLS1_1, - SslVersion::Tls1_2 => openssl::ssl::SslVersion::TLS1_2, - SslVersion::Tls1_3 => openssl::ssl::SslVersion::TLS1_3, - } - } -} - -impl TlsInit for openssl::ssl::Ssl { - type Tls = openssl::ssl::Ssl; - - fn init(parameters: &TlsParameters, name: Option) -> Result { - let TlsParameters { - server_cert_verify, - root_cert, - cert, - key, - crl, - min_protocol_version, - max_protocol_version, - alpn, - sni_override, - enable_keylog, - } = parameters; - - let mut ssl = SslContextBuilder::new(SslMethod::tls_client())?; - - // Load root cert - if let TlsCert::Custom(root) = root_cert { - let root = openssl::x509::X509::from_der(root.as_ref())?; - ssl.cert_store_mut().add_cert(root)?; - ssl.set_verify(SslVerifyMode::PEER); - } else if *server_cert_verify == TlsServerCertVerify::Insecure { - ssl.set_verify(SslVerifyMode::NONE); - } - - // Load CRL - if !crl.is_empty() { - // The openssl crate doesn't yet have add_crl, so we need to use the raw FFI - use foreign_types::ForeignTypeRef; - let ptr = ssl.cert_store_mut().as_ptr(); - - extern "C" { - pub fn X509_STORE_add_crl( - store: *mut openssl_sys::X509_STORE, - x: *mut openssl_sys::X509_CRL, - ) -> openssl_sys::c_int; - } - - for crl in crl { - let crl = openssl::x509::X509Crl::from_der(crl.as_ref())?; - let crl_ptr = crl.as_ptr(); - let res = unsafe { X509_STORE_add_crl(ptr, crl_ptr) }; - if res != 1 { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to add CRL to store", - ) - .into()); - } - } - - ssl.verify_param_mut() - .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; - ssl.cert_store_mut() - .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; - } - - // Load certificate chain and private key - if let (Some(cert), Some(key)) = (cert.as_ref(), key.as_ref()) { - let builder = openssl::x509::X509::from_der(cert.as_ref())?; - ssl.set_certificate(&builder)?; - let builder = openssl::pkey::PKey::private_key_from_pem(&key.secret_der())?; - ssl.set_private_key(&builder)?; - } - - // Configure hostname verification - if *server_cert_verify == TlsServerCertVerify::VerifyFull { - ssl.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); - } - - ssl.set_min_proto_version(min_protocol_version.map(|s| s.into()))?; - ssl.set_max_proto_version(max_protocol_version.map(|s| s.into()))?; - - // Configure key log filename - if *enable_keylog { - if let Ok(path) = std::env::var("SSLKEYLOGFILE") { - // "The callback is invoked whenever TLS key material is generated, and is passed a line of NSS SSLKEYLOGFILE-formatted text. - // This can be used by tools like Wireshark to decrypt message traffic. The line does not contain a trailing newline. - ssl.set_keylog_callback(move |_ssl, msg| { - let Ok(mut file) = std::fs::OpenOptions::new().append(true).open(&path) else { - return; - }; - let _ = std::io::Write::write_all(&mut file, msg.as_bytes()); - }); - } - } - - if *server_cert_verify == TlsServerCertVerify::VerifyFull { - if let Some(hostname) = sni_override { - ssl.verify_param_mut().set_host(hostname)?; - } else if let Some(ServerName::DnsName(hostname)) = &name { - ssl.verify_param_mut().set_host(hostname.as_ref())?; - } else if let Some(ServerName::IpAddress(ip)) = &name { - ssl.verify_param_mut().set_ip((*ip).into())?; - } - } - - let mut ssl = openssl::ssl::Ssl::new(&ssl.build())?; - ssl.set_connect_state(); - - // Set hostname if it's not an IP address - if let Some(hostname) = sni_override { - ssl.set_hostname(hostname)?; - } else if let Some(ServerName::DnsName(hostname)) = &name { - ssl.set_hostname(hostname.as_ref())?; - } - - if let Some(alpn) = alpn { - let alpn = alpn - .iter() - .map(|s| { - let bytes = s.as_bytes(); - let mut vec = Vec::with_capacity(bytes.len() + 1); - vec.push(bytes.len() as u8); - vec.extend_from_slice(bytes); - vec - }) - .flatten() - .collect::>(); - ssl.set_alpn_protos(&alpn)?; - } - - Ok(ssl) - } -} diff --git a/rust/gel-stream/src/client/stream.rs b/rust/gel-stream/src/client/stream.rs deleted file mode 100644 index f8e175895ef..00000000000 --- a/rust/gel-stream/src/client/stream.rs +++ /dev/null @@ -1,240 +0,0 @@ -use super::{invalid_state, ConnectionError, SslError}; -use std::pin::Pin; - -pub trait Stream: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin {} -impl Stream for T where T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin {} - -/// A trait for streams that can be upgraded to a secure connection. -/// -/// This trait is usually implemented by tuples that represent a connection that can be -/// upgraded from an insecure to a secure state, typically through SSL/TLS. -pub trait StreamWithUpgrade: Unpin { - type Base: Stream; - type Upgrade: Stream; - type Config: Unpin; - - /// Perform a secure upgrade operation and return the new, wrapped connection. - #[allow(async_fn_in_trait)] - async fn secure_upgrade(self) -> Result - where - Self: Sized; -} - -impl StreamWithUpgrade for (S, ()) { - type Base = S; - type Upgrade = S; - type Config = (); - - async fn secure_upgrade(self) -> Result - where - Self: Sized, - { - Err(SslError::SslUnsupportedByClient) - } -} - -#[derive(derive_more::Debug)] -pub struct UpgradableStream -where - (B, C): StreamWithUpgrade, -{ - inner: UpgradableStreamInner, -} - -impl From<(B, C)> for UpgradableStream -where - (B, C): StreamWithUpgrade, -{ - #[inline(always)] - fn from(value: (B, C)) -> Self { - Self::new(value.0, value.1) - } -} - -impl UpgradableStream -where - (B, C): StreamWithUpgrade, -{ - #[inline(always)] - pub fn new(base: B, config: C) -> Self { - UpgradableStream { - inner: UpgradableStreamInner::Base(base, config), - } - } - - pub async fn secure_upgrade(&mut self) -> Result<(), ConnectionError> - where - (B, C): StreamWithUpgrade, - { - match std::mem::replace(&mut self.inner, UpgradableStreamInner::Upgrading) { - UpgradableStreamInner::Base(base, config) => { - self.inner = - UpgradableStreamInner::Upgraded((base, config).secure_upgrade().await?); - Ok(()) - } - UpgradableStreamInner::Upgraded(..) => Err(invalid_state!( - "Attempted to upgrade an already upgraded stream" - )), - UpgradableStreamInner::Upgrading => Err(invalid_state!( - "Attempted to upgrade a stream that is already in the process of upgrading" - )), - } - } - - /// Convert the inner stream into a choice between the base and the upgraded stream. - /// - /// If the inner stream is in the process of upgrading, return an error containing `self`. - pub fn into_choice(self) -> Result, Self> { - match self.inner { - UpgradableStreamInner::Base(base, _) => Ok(UpgradableStreamChoice::Base(base)), - UpgradableStreamInner::Upgraded(upgraded) => { - Ok(UpgradableStreamChoice::Upgrade(upgraded)) - } - UpgradableStreamInner::Upgrading => Err(self), - } - } -} - -impl tokio::io::AsyncRead for UpgradableStream -where - (B, C): StreamWithUpgrade, -{ - #[inline(always)] - fn poll_read( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> std::task::Poll> { - let inner = &mut self.get_mut().inner; - match inner { - UpgradableStreamInner::Base(base, _) => Pin::new(base).poll_read(cx, buf), - UpgradableStreamInner::Upgraded(upgraded) => Pin::new(upgraded).poll_read(cx, buf), - UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Cannot read while upgrading", - ))), - } - } -} - -impl tokio::io::AsyncWrite for UpgradableStream -where - (B, C): StreamWithUpgrade, -{ - #[inline(always)] - fn poll_write( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - buf: &[u8], - ) -> std::task::Poll> { - let inner = &mut self.get_mut().inner; - match inner { - UpgradableStreamInner::Base(base, _) => Pin::new(base).poll_write(cx, buf), - UpgradableStreamInner::Upgraded(upgraded) => Pin::new(upgraded).poll_write(cx, buf), - UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Cannot write while upgrading", - ))), - } - } - - #[inline(always)] - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - let inner = &mut self.get_mut().inner; - match inner { - UpgradableStreamInner::Base(base, _) => Pin::new(base).poll_flush(cx), - UpgradableStreamInner::Upgraded(upgraded) => Pin::new(upgraded).poll_flush(cx), - UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Cannot flush while upgrading", - ))), - } - } - - #[inline(always)] - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - let inner = &mut self.get_mut().inner; - match inner { - UpgradableStreamInner::Base(base, _) => Pin::new(base).poll_shutdown(cx), - UpgradableStreamInner::Upgraded(upgraded) => Pin::new(upgraded).poll_shutdown(cx), - UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Cannot shutdown while upgrading", - ))), - } - } - - #[inline(always)] - fn is_write_vectored(&self) -> bool { - match &self.inner { - UpgradableStreamInner::Base(base, _) => base.is_write_vectored(), - UpgradableStreamInner::Upgraded(upgraded) => upgraded.is_write_vectored(), - UpgradableStreamInner::Upgrading => false, - } - } - - #[inline(always)] - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> std::task::Poll> { - let inner = &mut self.get_mut().inner; - match inner { - UpgradableStreamInner::Base(base, _) => Pin::new(base).poll_write_vectored(cx, bufs), - UpgradableStreamInner::Upgraded(upgraded) => { - Pin::new(upgraded).poll_write_vectored(cx, bufs) - } - UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Cannot write vectored while upgrading", - ))), - } - } -} - -#[derive(derive_more::Debug)] -enum UpgradableStreamInner -where - (B, C): StreamWithUpgrade, -{ - #[debug("Base(..)")] - Base(B, C), - #[debug("Upgraded(..)")] - Upgraded(<(B, C) as StreamWithUpgrade>::Upgrade), - #[debug("Upgrading")] - Upgrading, -} - -#[derive(derive_more::Debug)] -pub enum UpgradableStreamChoice -where - (B, C): StreamWithUpgrade, -{ - #[debug("Base(..)")] - Base(B), - #[debug("Upgrade(..)")] - Upgrade(<(B, C) as StreamWithUpgrade>::Upgrade), -} - -impl UpgradableStreamChoice -where - (B, C): StreamWithUpgrade, - B: 'static, - <(B, C) as StreamWithUpgrade>::Base: 'static, - <(B, C) as StreamWithUpgrade>::Upgrade: 'static, -{ - /// Take the inner stream as a boxed `Stream` - pub fn into_boxed(self) -> Box { - match self { - UpgradableStreamChoice::Base(base) => Box::new(base), - UpgradableStreamChoice::Upgrade(upgrade) => Box::new(upgrade), - } - } -} diff --git a/rust/gel-stream/src/common/mod.rs b/rust/gel-stream/src/common/mod.rs new file mode 100644 index 00000000000..c2a6bed0368 --- /dev/null +++ b/rust/gel-stream/src/common/mod.rs @@ -0,0 +1,16 @@ +pub mod stream; +pub mod target; +pub mod tls; + +#[cfg(feature = "openssl")] +pub mod openssl; +#[cfg(feature = "rustls")] +pub mod rustls; +#[cfg(feature = "tokio")] +pub mod tokio_stream; + +#[cfg(feature = "tokio")] +pub type BaseStream = tokio_stream::TokioStream; + +#[cfg(not(feature = "tokio"))] +pub type BaseStream = (); diff --git a/rust/gel-stream/src/common/openssl.rs b/rust/gel-stream/src/common/openssl.rs new file mode 100644 index 00000000000..40d39587fc6 --- /dev/null +++ b/rust/gel-stream/src/common/openssl.rs @@ -0,0 +1,521 @@ +use openssl::{ + ssl::{ + AlpnError, NameType, SniError, Ssl, SslAcceptor, SslContextBuilder, SslMethod, SslRef, + SslVerifyMode, + }, + x509::{verify::X509VerifyFlags, X509VerifyResult}, +}; +use rustls_pki_types::ServerName; +use std::{ + borrow::Cow, + io::IoSlice, + pin::Pin, + sync::{Arc, Mutex, MutexGuard, OnceLock}, + task::{ready, Poll}, +}; +use tokio::{ + io::{AsyncRead, AsyncWrite, ReadBuf}, + net::TcpStream, +}; + +use crate::{ + RewindStream, SslError, SslVersion, Stream, TlsCert, TlsClientCertVerify, TlsDriver, + TlsHandshake, TlsParameters, TlsServerCertVerify, TlsServerParameterProvider, + TlsServerParameters, +}; + +use super::tokio_stream::TokioStream; + +#[derive(Debug, Clone, Default)] +struct HandshakeData { + server_alpn: Option>, + handshake: TlsHandshake, +} + +impl HandshakeData { + fn from_ssl(ssl: &SslRef) -> Option> { + let Some(handshake) = ssl.ex_data(get_ssl_ex_data_index()) else { + return None; + }; + let Ok(handshake) = handshake.lock() else { + return None; + }; + Some(handshake) + } +} + +static SSL_EX_DATA_INDEX: OnceLock>>> = + OnceLock::new(); + +fn get_ssl_ex_data_index() -> openssl::ex_data::Index>> { + *SSL_EX_DATA_INDEX + .get_or_init(|| Ssl::new_ex_index().expect("Failed to create SSL ex_data index")) +} + +#[derive(Default)] + +pub struct OpensslDriver; + +pub struct TlsStream(tokio_openssl::SslStream); + +impl AsyncRead for TlsStream { + #[inline(always)] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } +} + +impl AsyncWrite for TlsStream { + #[inline(always)] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + #[inline(always)] + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[IoSlice<'_>], + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + } + + #[inline(always)] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } + + #[inline(always)] + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + #[inline(always)] + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let res = ready!(Pin::new(&mut self.0).poll_shutdown(cx)); + if let Err(e) = &res { + // Swallow NotConnected errors here + if e.kind() == std::io::ErrorKind::NotConnected { + return Poll::Ready(Ok(())); + } + } + Poll::Ready(res) + } +} + +impl TlsDriver for OpensslDriver { + type Stream = TlsStream; + type ClientParams = openssl::ssl::Ssl; + type ServerParams = openssl::ssl::SslContext; + + fn init_client( + params: &TlsParameters, + name: Option, + ) -> Result { + let TlsParameters { + server_cert_verify, + root_cert, + cert, + key, + crl, + min_protocol_version, + max_protocol_version, + alpn, + sni_override, + enable_keylog, + } = params; + + // let mut ssl = SslConnector::builder(SslMethod::tls_client())?; + let mut ssl = SslContextBuilder::new(SslMethod::tls_client())?; + + // Load root cert + if let TlsCert::Custom(root) = root_cert { + let root = openssl::x509::X509::from_der(root.as_ref())?; + ssl.cert_store_mut().add_cert(root)?; + ssl.set_verify(SslVerifyMode::PEER); + } else if *server_cert_verify == TlsServerCertVerify::Insecure { + ssl.set_verify(SslVerifyMode::NONE); + } + + // Load CRL + if !crl.is_empty() { + // The openssl crate doesn't yet have add_crl, so we need to use the raw FFI + use foreign_types::ForeignTypeRef; + let ptr = ssl.cert_store_mut().as_ptr(); + + extern "C" { + pub fn X509_STORE_add_crl( + store: *mut openssl_sys::X509_STORE, + x: *mut openssl_sys::X509_CRL, + ) -> openssl_sys::c_int; + } + + for crl in crl { + let crl = openssl::x509::X509Crl::from_der(crl.as_ref())?; + let crl_ptr = crl.as_ptr(); + let res = unsafe { X509_STORE_add_crl(ptr, crl_ptr) }; + if res != 1 { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to add CRL to store", + ) + .into()); + } + } + + ssl.verify_param_mut() + .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; + ssl.cert_store_mut() + .set_flags(X509VerifyFlags::CRL_CHECK | X509VerifyFlags::CRL_CHECK_ALL)?; + } + + // Load certificate chain and private key + if let (Some(cert), Some(key)) = (cert.as_ref(), key.as_ref()) { + let builder = openssl::x509::X509::from_der(cert.as_ref())?; + ssl.set_certificate(&builder)?; + let builder = openssl::pkey::PKey::private_key_from_der(&key.secret_der())?; + ssl.set_private_key(&builder)?; + } + + // Configure hostname verification + if *server_cert_verify == TlsServerCertVerify::VerifyFull { + ssl.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + } + + ssl.set_min_proto_version(min_protocol_version.map(|s| s.into()))?; + ssl.set_max_proto_version(max_protocol_version.map(|s| s.into()))?; + + // Configure key log filename + if *enable_keylog { + if let Ok(path) = std::env::var("SSLKEYLOGFILE") { + ssl.set_keylog_callback(move |_ssl, msg| { + let Ok(mut file) = std::fs::OpenOptions::new().append(true).open(&path) else { + return; + }; + let _ = std::io::Write::write_all(&mut file, msg.as_bytes()); + }); + } + } + + if *server_cert_verify == TlsServerCertVerify::VerifyFull { + if let Some(hostname) = sni_override { + ssl.verify_param_mut().set_host(hostname)?; + } else if let Some(ServerName::DnsName(hostname)) = &name { + ssl.verify_param_mut().set_host(hostname.as_ref())?; + } else if let Some(ServerName::IpAddress(ip)) = &name { + ssl.verify_param_mut().set_ip((*ip).into())?; + } + } + + let mut ssl = openssl::ssl::Ssl::new(&ssl.build())?; + ssl.set_connect_state(); + + // Set hostname if it's not an IP address + if let Some(hostname) = sni_override { + ssl.set_hostname(hostname)?; + } else if let Some(ServerName::DnsName(hostname)) = &name { + ssl.set_hostname(hostname.as_ref())?; + } + + if !alpn.is_empty() { + ssl.set_alpn_protos(&alpn.as_bytes())?; + } + + Ok(ssl) + } + + fn init_server(params: &TlsServerParameters) -> Result { + let TlsServerParameters { + client_cert_verify, + min_protocol_version, + max_protocol_version, + server_certificate, + // Handled elsewhere + alpn: _alpn, + } = params; + + let mut ssl = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls_server())?; + let cert = openssl::x509::X509::from_der(&server_certificate.cert.as_ref())?; + let key = openssl::pkey::PKey::private_key_from_der(&server_certificate.key.secret_der())?; + ssl.set_certificate(&cert)?; + ssl.set_private_key(&key)?; + ssl.set_min_proto_version(min_protocol_version.map(|s| s.into()))?; + ssl.set_max_proto_version(max_protocol_version.map(|s| s.into()))?; + match client_cert_verify { + TlsClientCertVerify::Ignore => ssl.set_verify(SslVerifyMode::NONE), + TlsClientCertVerify::Optional(root) => { + ssl.set_verify(SslVerifyMode::PEER); + for root in root { + let root = openssl::x509::X509::from_der(root.as_ref())?; + ssl.cert_store_mut().add_cert(root)?; + } + } + TlsClientCertVerify::Validate(root) => { + ssl.set_verify(SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT); + for root in root { + let root = openssl::x509::X509::from_der(root.as_ref())?; + ssl.cert_store_mut().add_cert(root)?; + } + } + } + create_alpn_callback(&mut ssl); + + Ok(ssl.build().into_context()) + } + + async fn upgrade_client( + params: Self::ClientParams, + stream: S, + ) -> Result<(Self::Stream, TlsHandshake), SslError> { + let stream = stream + .downcast::() + .map_err(|_| crate::SslError::SslUnsupportedByClient)?; + let TokioStream::Tcp(stream) = stream else { + return Err(crate::SslError::SslUnsupportedByClient); + }; + + let mut stream = tokio_openssl::SslStream::new(params, stream)?; + let res = Pin::new(&mut stream).do_handshake().await; + if res.is_err() { + if stream.ssl().verify_result() != X509VerifyResult::OK { + return Err(SslError::OpenSslErrorVerify(stream.ssl().verify_result())); + } + } + + let alpn = stream + .ssl() + .selected_alpn_protocol() + .map(|p| Cow::Owned(p.to_vec())); + + res.map_err(SslError::OpenSslError)?; + Ok(( + TlsStream(stream), + TlsHandshake { + alpn, + sni: None, + cert: None, + }, + )) + } + + async fn upgrade_server( + params: TlsServerParameterProvider, + stream: S, + ) -> Result<(Self::Stream, TlsHandshake), SslError> { + let stream = stream + .downcast::>() + .map_err(|_| crate::SslError::SslUnsupportedByClient)?; + let (stream, buffer) = stream.into_inner(); + if !buffer.is_empty() { + // TODO: We should also be able to support rewinding + return Err(crate::SslError::SslUnsupportedByClient); + } + let TokioStream::Tcp(stream) = stream else { + return Err(crate::SslError::SslUnsupportedByClient); + }; + + let handshake = Arc::new(Mutex::new(HandshakeData::default())); + + let mut ssl = SslContextBuilder::new(SslMethod::tls_server())?; + create_alpn_callback(&mut ssl); + create_sni_callback(&mut ssl, params); + + let mut ssl = Ssl::new(&ssl.build())?; + ssl.set_accept_state(); + ssl.set_ex_data(get_ssl_ex_data_index(), handshake.clone()); + + let mut stream = tokio_openssl::SslStream::new(ssl, stream)?; + let res = Pin::new(&mut stream).do_handshake().await; + res.map_err(SslError::OpenSslError)?; + + let handshake = handshake.lock().unwrap().handshake.clone(); + + Ok((TlsStream(stream), handshake)) + } +} + +fn ssl_select_next_proto<'a, 'b>(server: &'a [u8], client: &'b [u8]) -> Option<&'b [u8]> { + let mut server_packet = server; + while !server_packet.is_empty() { + let server_proto_len = *server_packet.get(0)? as usize; + let server_proto = server_packet.get(1..1 + server_proto_len)?; + let mut client_packet = client; + while !client_packet.is_empty() { + let client_proto_len = *client_packet.get(0)? as usize; + let client_proto = client_packet.get(1..1 + client_proto_len)?; + if client_proto == server_proto { + return Some(client_proto); + } + client_packet = client_packet.get(1 + client_proto_len..)?; + } + server_packet = server_packet.get(1 + server_proto_len..)?; + } + None +} + +/// Create an ALPN callback for the [`SslContextBuilder`]. +fn create_alpn_callback(ssl: &mut SslContextBuilder) { + ssl.set_alpn_select_callback(|ssl_ref, alpn| { + let Some(mut handshake) = HandshakeData::from_ssl(ssl_ref) else { + return Err(AlpnError::ALERT_FATAL); + }; + + if let Some(server) = handshake.server_alpn.take() { + eprintln!("server: {:?} alpn: {:?}", server, alpn); + let Some(selected) = ssl_select_next_proto(&server, alpn) else { + return Err(AlpnError::NOACK); + }; + handshake.handshake.alpn = Some(Cow::Owned(selected.to_vec())); + + Ok(selected) + } else { + Err(AlpnError::NOACK) + } + }) +} + +/// Create an SNI callback for the [`SslContextBuilder`]. +fn create_sni_callback(ssl: &mut SslContextBuilder, params: TlsServerParameterProvider) { + ssl.set_servername_callback(move |ssl_ref, _alert| { + let Some(mut handshake) = HandshakeData::from_ssl(ssl_ref) else { + return Ok(()); + }; + + if let Some(servername) = ssl_ref.servername_raw(NameType::HOST_NAME) { + handshake.handshake.sni = + Some(Cow::Owned(String::from_utf8_lossy(servername).to_string())); + } + + let params = params.lookup(None); + if !params.alpn.is_empty() { + handshake.server_alpn = Some(params.alpn.as_bytes().to_vec()); + } + + drop(handshake); + + let Ok(ssl) = OpensslDriver::init_server(¶ms) else { + return Err(SniError::ALERT_FATAL); + }; + let Ok(_) = ssl_ref.set_ssl_context(&ssl) else { + return Err(SniError::ALERT_FATAL); + }; + Ok(()) + }); +} + +impl From for openssl::ssl::SslVersion { + fn from(val: SslVersion) -> Self { + match val { + SslVersion::Tls1 => openssl::ssl::SslVersion::TLS1, + SslVersion::Tls1_1 => openssl::ssl::SslVersion::TLS1_1, + SslVersion::Tls1_2 => openssl::ssl::SslVersion::TLS1_2, + SslVersion::Tls1_3 => openssl::ssl::SslVersion::TLS1_3, + } + } +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ssl_select_next_proto() { + let server = b"\x02h2\x08http/1.1"; + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, Some(b"http/1.1".as_slice())); + } + + #[test] + fn test_ssl_select_next_proto_empty() { + let server = b""; + let client = b""; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, None); + } + + #[test] + fn test_ssl_select_next_proto_invalid_length() { + let server = b"\x08h2"; // Claims 8 bytes but only has 2 + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, None); + } + + #[test] + fn test_ssl_select_next_proto_zero_length() { + let server = b"\x00h2"; // Zero length but has data + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, None); + } + + #[test] + fn test_ssl_select_next_proto_truncated() { + let server = b"\x02h2\x08http/1"; // Second protocol truncated + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, None); + } + + #[test] + fn test_ssl_select_next_proto_overflow() { + let server = b"\xFFh2"; // Length that would overflow buffer + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, None); + } + + #[test] + fn test_ssl_select_next_proto_no_match() { + let server = b"\x02h2"; + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, None); + } + + #[test] + fn test_ssl_select_next_proto_multiple_server() { + let server = b"\x02h2\x06spdy/2\x08http/1.1"; + let client = b"\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, Some(b"http/1.1".as_slice())); + } + + #[test] + fn test_ssl_select_next_proto_multiple_client() { + let server = b"\x08http/1.1"; + let client = b"\x02h2\x06spdy/2\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, Some(b"http/1.1".as_slice())); + } + + #[test] + fn test_ssl_select_next_proto_first_match() { + let server = b"\x02h2\x06spdy/2\x08http/1.1"; + let client = b"\x06spdy/2\x02h2\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, Some(b"h2".as_slice())); + } + + #[test] + fn test_ssl_select_next_proto_first_match_2() { + let server = b"\x06spdy/2\x02h2\x08http/1.1"; + let client = b"\x02h2\x06spdy/2\x08http/1.1"; + let selected = ssl_select_next_proto(server, client); + assert_eq!(selected, Some(b"spdy/2".as_slice())); + } +} diff --git a/rust/gel-stream/src/client/rustls.rs b/rust/gel-stream/src/common/rustls.rs similarity index 53% rename from rust/gel-stream/src/client/rustls.rs rename to rust/gel-stream/src/common/rustls.rs index 456dc4818ec..8ec4368a317 100644 --- a/rust/gel-stream/src/client/rustls.rs +++ b/rust/gel-stream/src/common/rustls.rs @@ -1,109 +1,39 @@ +use futures::FutureExt; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::client::WebPkiServerVerifier; +use rustls::server::{Acceptor, ClientHello, WebPkiClientVerifier}; use rustls::{ - ClientConfig, ClientConnection, DigitallySignedStruct, RootCertStore, SignatureScheme, + ClientConfig, ClientConnection, DigitallySignedStruct, RootCertStore, ServerConfig, + SignatureScheme, }; use rustls_pki_types::{ CertificateDer, CertificateRevocationListDer, DnsName, ServerName, UnixTime, }; use rustls_platform_verifier::Verifier; +use rustls_tokio_stream::TlsStream; -use super::stream::{Stream, StreamWithUpgrade}; use super::tokio_stream::TokioStream; -use super::{TlsCert, TlsInit, TlsParameters, TlsServerCertVerify}; -use std::any::Any; +use crate::{ + RewindStream, SslError, Stream, TlsClientCertVerify, TlsDriver, TlsHandshake, + TlsServerParameterProvider, TlsServerParameters, +}; +use crate::{TlsCert, TlsParameters, TlsServerCertVerify}; +use std::borrow::Cow; use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; -impl StreamWithUpgrade for (S, Option) { - type Base = S; - type Config = ClientConnection; - type Upgrade = rustls_tokio_stream::TlsStream; - - async fn secure_upgrade(self) -> Result - where - Self: Sized, - { - let Some(tls) = self.1 else { - return Err(super::SslError::SslUnsupportedByClient); - }; - - // Note that we only support Tokio TcpStream for rustls. - let stream = &mut Some(self.0) as &mut dyn Any; - let Some(stream) = stream.downcast_mut::>() else { - return Err(super::SslError::SslUnsupportedByClient); - }; - - let stream = stream.take().unwrap(); - let TokioStream::Tcp(stream) = stream else { - return Err(super::SslError::SslUnsupportedByClient); - }; - - let mut stream = rustls_tokio_stream::TlsStream::new_client_side(stream, tls, None); - let res = stream.handshake().await; - - // Potentially unwrap the error to get the underlying error. - if let Err(e) = res { - let kind = e.kind(); - if let Some(e2) = e.into_inner() { - match e2.downcast::<::rustls::Error>() { - Ok(e) => return Err(super::SslError::RustlsError(*e)), - Err(e) => return Err(std::io::Error::new(kind, e).into()), - } - } else { - return Err(std::io::Error::from(kind).into()); - } - } - - Ok(stream) - } -} +#[derive(Default)] +pub struct RustlsDriver; -fn make_verifier( - server_cert_verify: &TlsServerCertVerify, - root_cert: &TlsCert, - crls: Vec>, -) -> Result, super::SslError> { - if *server_cert_verify == TlsServerCertVerify::Insecure { - return Ok(Arc::new(NullVerifier)); - } +impl TlsDriver for RustlsDriver { + type Stream = TlsStream; + type ClientParams = ClientConnection; + type ServerParams = Arc; - if let TlsCert::Custom(root) = root_cert { - let mut roots = RootCertStore::empty(); - let (loaded, ignored) = roots.add_parsable_certificates([root.clone()]); - if loaded == 0 || ignored > 0 { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Invalid certificate", - ) - .into()); - } - - let verifier = WebPkiServerVerifier::builder(Arc::new(roots)) - .with_crls(crls) - .build()?; - if *server_cert_verify == TlsServerCertVerify::IgnoreHostname { - return Ok(Arc::new(IgnoreHostnameVerifier::new(verifier))); - } - return Ok(verifier); - } - - if *server_cert_verify == TlsServerCertVerify::IgnoreHostname { - return Ok(Arc::new(IgnoreHostnameVerifier::new(Arc::new( - Verifier::new(), - )))); - } - - Ok(Arc::new(Verifier::new())) -} - -impl TlsInit for ClientConnection { - type Tls = ClientConnection; - - fn init( - parameters: &TlsParameters, + fn init_client( + params: &TlsParameters, name: Option, - ) -> Result { + ) -> Result { let _ = ::rustls::crypto::ring::default_provider().install_default(); let TlsParameters { @@ -117,7 +47,7 @@ impl TlsInit for ClientConnection { alpn, enable_keylog, sni_override, - } = parameters; + } = params; let verifier = make_verifier(server_cert_verify, root_cert, crl.clone())?; @@ -140,12 +70,7 @@ impl TlsInit for ClientConnection { }; // Configure ALPN if provided - if let Some(alpn_protocols) = alpn { - config.alpn_protocols = alpn_protocols - .iter() - .map(|p| p.as_bytes().to_vec()) - .collect(); - } + config.alpn_protocols = alpn.as_vec_vec(); // Configure keylog if provided if *enable_keylog { @@ -163,6 +88,172 @@ impl TlsInit for ClientConnection { Ok(ClientConnection::new(Arc::new(config), name)?) } + + fn init_server(params: &TlsServerParameters) -> Result { + let builder = match ¶ms.client_cert_verify { + TlsClientCertVerify::Ignore => ServerConfig::builder().with_no_client_auth(), + TlsClientCertVerify::Optional(certs) => { + let mut roots = RootCertStore::empty(); + roots.add_parsable_certificates( + certs.iter().map(|c| CertificateDer::from_slice(c.as_ref())), + ); + ServerConfig::builder().with_client_cert_verifier( + WebPkiClientVerifier::builder(roots.into()) + .allow_unauthenticated() + .build()?, + ) + } + TlsClientCertVerify::Validate(certs) => { + let mut roots = RootCertStore::empty(); + roots.add_parsable_certificates( + certs.iter().map(|c| CertificateDer::from_slice(c.as_ref())), + ); + ServerConfig::builder() + .with_client_cert_verifier(WebPkiClientVerifier::builder(roots.into()).build()?) + } + }; + + let mut config = builder.with_single_cert( + vec![params.server_certificate.cert.clone()], + params.server_certificate.key.clone_key(), + )?; + + config.alpn_protocols = params.alpn.as_vec_vec(); + + Ok(Arc::new(config)) + } + + async fn upgrade_client( + params: Self::ClientParams, + stream: S, + ) -> Result<(Self::Stream, TlsHandshake), SslError> { + // Note that we only support Tokio TcpStream for rustls. + let stream = stream + .downcast::() + .map_err(|_| crate::SslError::SslUnsupportedByClient)?; + let TokioStream::Tcp(stream) = stream else { + return Err(crate::SslError::SslUnsupportedByClient); + }; + + let mut stream = TlsStream::new_client_side(stream, params, None); + + match stream.handshake().await { + Ok(handshake) => Ok(( + stream, + TlsHandshake { + alpn: handshake.alpn.map(|alpn| Cow::Owned(alpn.to_vec())), + sni: handshake.sni.map(|sni| Cow::Owned(sni.to_string())), + cert: None, + }, + )), + Err(e) => { + let kind = e.kind(); + if let Some(e2) = e.into_inner() { + match e2.downcast::<::rustls::Error>() { + Ok(e) => Err(crate::SslError::RustlsError(*e)), + Err(e) => Err(std::io::Error::new(kind, e).into()), + } + } else { + Err(std::io::Error::from(kind).into()) + } + } + } + } + + async fn upgrade_server( + params: TlsServerParameterProvider, + stream: S, + ) -> Result<(Self::Stream, TlsHandshake), SslError> { + let stream = stream + .downcast::>() + .map_err(|_| crate::SslError::SslUnsupportedByClient)?; + let (stream, buffer) = stream.into_inner(); + let TokioStream::Tcp(stream) = stream else { + return Err(crate::SslError::SslUnsupportedByClient); + }; + + let mut acceptor = Acceptor::default(); + acceptor.read_tls(&mut buffer.as_slice())?; + let server_config_provider = Arc::new(move |client_hello: ClientHello| { + let params = params.clone(); + let server_name = client_hello + .server_name() + .map(|name| ServerName::DnsName(DnsName::try_from(name.to_string()).unwrap())); + async move { + let params = params.lookup(server_name); + let config = RustlsDriver::init_server(¶ms) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + Ok::<_, std::io::Error>(config) + } + .boxed() + }); + let mut stream = TlsStream::new_server_side_from_acceptor( + acceptor, + stream, + server_config_provider, + None, + ); + + match stream.handshake().await { + Ok(handshake) => Ok(( + stream, + TlsHandshake { + alpn: handshake.alpn.map(|alpn| Cow::Owned(alpn.to_vec())), + sni: handshake.sni.map(|sni| Cow::Owned(sni.to_string())), + cert: None, + }, + )), + Err(e) => { + let kind = e.kind(); + if let Some(e2) = e.into_inner() { + match e2.downcast::<::rustls::Error>() { + Ok(e) => Err(crate::SslError::RustlsError(*e)), + Err(e) => Err(std::io::Error::new(kind, e).into()), + } + } else { + Err(std::io::Error::from(kind).into()) + } + } + } + } +} + +fn make_verifier( + server_cert_verify: &TlsServerCertVerify, + root_cert: &TlsCert, + crls: Vec>, +) -> Result, crate::SslError> { + if *server_cert_verify == TlsServerCertVerify::Insecure { + return Ok(Arc::new(NullVerifier)); + } + + if let TlsCert::Custom(root) = root_cert { + let mut roots = RootCertStore::empty(); + let (loaded, ignored) = roots.add_parsable_certificates([root.clone()]); + if loaded == 0 || ignored > 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid certificate", + ) + .into()); + } + + let verifier = WebPkiServerVerifier::builder(Arc::new(roots)) + .with_crls(crls) + .build()?; + if *server_cert_verify == TlsServerCertVerify::IgnoreHostname { + return Ok(Arc::new(IgnoreHostnameVerifier::new(verifier))); + } + return Ok(verifier); + } + + if *server_cert_verify == TlsServerCertVerify::IgnoreHostname { + return Ok(Arc::new(IgnoreHostnameVerifier::new(Arc::new( + Verifier::new(), + )))); + } + + Ok(Arc::new(Verifier::new())) } #[derive(Debug)] diff --git a/rust/gel-stream/src/common/stream.rs b/rust/gel-stream/src/common/stream.rs new file mode 100644 index 00000000000..9b56a25e68a --- /dev/null +++ b/rust/gel-stream/src/common/stream.rs @@ -0,0 +1,343 @@ +#[cfg(feature = "tokio")] +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + +use std::future::Future; +#[cfg(feature = "tokio")] +use std::{ + any::Any, + io::IoSlice, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{Ssl, SslError, TlsDriver, TlsHandshake, TlsServerParameterProvider}; + +#[cfg(feature = "tokio")] +pub trait Stream: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static { + fn downcast(self) -> Result + where + Self: Sized + 'static, + { + // Note that we only support Tokio TcpStream for rustls. + let mut holder = Some(self); + let stream = &mut holder as &mut dyn Any; + let Some(stream) = stream.downcast_mut::>() else { + return Err(holder.take().unwrap()); + }; + let stream = stream.take().unwrap(); + Ok(stream) + } +} + +#[cfg(feature = "tokio")] +impl Stream for T where T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static {} + +#[cfg(not(feature = "tokio"))] +pub trait Stream: 'static {} +#[cfg(not(feature = "tokio"))] +impl Stream for UpgradableStream {} +#[cfg(not(feature = "tokio"))] +impl Stream for () {} + +pub trait StreamUpgrade: Stream { + fn secure_upgrade(&mut self) -> impl Future> + Send; + fn handshake(&self) -> Option<&TlsHandshake>; +} + +#[allow(private_bounds)] +#[derive(derive_more::Debug)] +pub struct UpgradableStream { + inner: UpgradableStreamInner, +} + +#[allow(private_bounds)] +impl UpgradableStream { + #[inline(always)] + pub(crate) fn new_client(base: S, config: Option) -> Self { + UpgradableStream { + inner: UpgradableStreamInner::BaseClient(base, config), + } + } + + #[inline(always)] + pub(crate) fn new_server(base: S, config: Option) -> Self { + UpgradableStream { + inner: UpgradableStreamInner::BaseServer(base, config), + } + } + + /// Consume the `UpgradableStream` and return the underlying stream as a [`Box`]. + pub fn into_boxed(self) -> Result, Self> { + match self.inner { + UpgradableStreamInner::BaseClient(base, _) => Ok(Box::new(base)), + UpgradableStreamInner::BaseServer(base, _) => Ok(Box::new(base)), + UpgradableStreamInner::Upgraded(upgraded, _) => Ok(Box::new(upgraded)), + UpgradableStreamInner::Upgrading => Err(self), + } + } +} + +impl StreamUpgrade for UpgradableStream { + async fn secure_upgrade(&mut self) -> Result<(), SslError> { + match std::mem::replace(&mut self.inner, UpgradableStreamInner::Upgrading) { + UpgradableStreamInner::BaseClient(base, config) => { + let Some(config) = config else { + return Err(SslError::SslUnsupportedByClient); + }; + let (upgraded, handshake) = D::upgrade_client(config, base).await?; + self.inner = UpgradableStreamInner::Upgraded(upgraded, handshake); + Ok(()) + } + UpgradableStreamInner::BaseServer(base, config) => { + let Some(config) = config else { + return Err(SslError::SslUnsupportedByClient); + }; + let (upgraded, handshake) = D::upgrade_server(config, base).await?; + self.inner = UpgradableStreamInner::Upgraded(upgraded, handshake); + Ok(()) + } + UpgradableStreamInner::Upgraded(..) => Err(SslError::SslAlreadyUpgraded), + UpgradableStreamInner::Upgrading => Err(SslError::SslAlreadyUpgraded), + } + } + + fn handshake(&self) -> Option<&TlsHandshake> { + match &self.inner { + UpgradableStreamInner::Upgraded(_, handshake) => Some(handshake), + _ => None, + } + } +} + +#[cfg(feature = "tokio")] +impl tokio::io::AsyncRead for UpgradableStream { + #[inline(always)] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + let inner = &mut self.get_mut().inner; + match inner { + UpgradableStreamInner::BaseClient(base, _) => Pin::new(base).poll_read(cx, buf), + UpgradableStreamInner::BaseServer(base, _) => Pin::new(base).poll_read(cx, buf), + UpgradableStreamInner::Upgraded(upgraded, _) => Pin::new(upgraded).poll_read(cx, buf), + UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot read while upgrading", + ))), + } + } +} + +#[cfg(feature = "tokio")] +impl tokio::io::AsyncWrite for UpgradableStream { + #[inline(always)] + fn poll_write( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + let inner = &mut self.get_mut().inner; + match inner { + UpgradableStreamInner::BaseClient(base, _) => Pin::new(base).poll_write(cx, buf), + UpgradableStreamInner::BaseServer(base, _) => Pin::new(base).poll_write(cx, buf), + UpgradableStreamInner::Upgraded(upgraded, _) => Pin::new(upgraded).poll_write(cx, buf), + UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot write while upgrading", + ))), + } + } + + #[inline(always)] + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let inner = &mut self.get_mut().inner; + match inner { + UpgradableStreamInner::BaseClient(base, _) => Pin::new(base).poll_flush(cx), + UpgradableStreamInner::BaseServer(base, _) => Pin::new(base).poll_flush(cx), + UpgradableStreamInner::Upgraded(upgraded, _) => Pin::new(upgraded).poll_flush(cx), + UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot flush while upgrading", + ))), + } + } + + #[inline(always)] + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let inner = &mut self.get_mut().inner; + match inner { + UpgradableStreamInner::BaseClient(base, _) => Pin::new(base).poll_shutdown(cx), + UpgradableStreamInner::BaseServer(base, _) => Pin::new(base).poll_shutdown(cx), + UpgradableStreamInner::Upgraded(upgraded, _) => Pin::new(upgraded).poll_shutdown(cx), + UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot shutdown while upgrading", + ))), + } + } + + #[inline(always)] + fn is_write_vectored(&self) -> bool { + match &self.inner { + UpgradableStreamInner::BaseClient(base, _) => base.is_write_vectored(), + UpgradableStreamInner::BaseServer(base, _) => base.is_write_vectored(), + UpgradableStreamInner::Upgraded(upgraded, _) => upgraded.is_write_vectored(), + UpgradableStreamInner::Upgrading => false, + } + } + + #[inline(always)] + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> std::task::Poll> { + let inner = &mut self.get_mut().inner; + match inner { + UpgradableStreamInner::BaseClient(base, _) => { + Pin::new(base).poll_write_vectored(cx, bufs) + } + UpgradableStreamInner::BaseServer(base, _) => { + Pin::new(base).poll_write_vectored(cx, bufs) + } + UpgradableStreamInner::Upgraded(upgraded, _) => { + Pin::new(upgraded).poll_write_vectored(cx, bufs) + } + UpgradableStreamInner::Upgrading => std::task::Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Cannot write vectored while upgrading", + ))), + } + } +} + +#[derive(derive_more::Debug)] +enum UpgradableStreamInner { + #[debug("BaseClient(..)")] + BaseClient(S, Option), + #[debug("BaseServer(..)")] + BaseServer(S, Option), + #[debug("Upgraded(..)")] + Upgraded(D::Stream, TlsHandshake), + #[debug("Upgrading")] + Upgrading, +} + +pub trait Rewindable { + fn rewind(&mut self, bytes: &[u8]) -> std::io::Result<()>; +} + +pub struct RewindStream { + buffer: Vec, + inner: S, +} + +impl RewindStream { + pub fn new(inner: S) -> Self { + RewindStream { + buffer: Vec::new(), + inner, + } + } + + pub fn rewind(&mut self, data: &[u8]) { + self.buffer.extend_from_slice(data); + } + + pub fn into_inner(self) -> (S, Vec) { + (self.inner, self.buffer) + } +} + +#[cfg(feature = "tokio")] +impl AsyncRead for RewindStream { + #[inline(always)] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if !self.buffer.is_empty() { + let to_read = std::cmp::min(buf.remaining(), self.buffer.len()); + let data = self.buffer.drain(..to_read).collect::>(); + buf.put_slice(&data); + Poll::Ready(Ok(())) + } else { + Pin::new(&mut self.inner).poll_read(cx, buf) + } + } +} + +#[cfg(feature = "tokio")] +impl AsyncWrite for RewindStream { + #[inline(always)] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) + } + + #[inline(always)] + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } + + #[inline(always)] + fn poll_shutdown( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) + } + + #[inline(always)] + fn is_write_vectored(&self) -> bool { + self.inner.is_write_vectored() + } + + #[inline(always)] + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write_vectored(cx, bufs) + } +} + +impl Rewindable for RewindStream { + fn rewind(&mut self, bytes: &[u8]) -> std::io::Result<()> { + self.rewind(bytes); + Ok(()) + } +} + +impl Rewindable for UpgradableStream +where + D::Stream: Rewindable, +{ + fn rewind(&mut self, bytes: &[u8]) -> std::io::Result<()> { + match &mut self.inner { + UpgradableStreamInner::BaseClient(stm, _) => stm.rewind(bytes), + UpgradableStreamInner::BaseServer(stm, _) => stm.rewind(bytes), + UpgradableStreamInner::Upgraded(stm, _) => stm.rewind(bytes), + UpgradableStreamInner::Upgrading => Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "Cannot rewind a stream that is upgrading", + )), + } + } +} diff --git a/rust/gel-stream/src/client/target.rs b/rust/gel-stream/src/common/target.rs similarity index 97% rename from rust/gel-stream/src/client/target.rs rename to rust/gel-stream/src/common/target.rs index 233b03865c5..1eccbdb1cff 100644 --- a/rust/gel-stream/src/client/target.rs +++ b/rust/gel-stream/src/common/target.rs @@ -7,7 +7,7 @@ use std::{ use rustls_pki_types::ServerName; -use super::TlsParameters; +use crate::TlsParameters; /// A target name describes the TCP or Unix socket that a client will connect to. pub struct TargetName { @@ -249,6 +249,19 @@ pub enum ResolvedTarget { UnixSocketAddr(std::os::unix::net::SocketAddr), } +impl ResolvedTarget { + pub fn tcp(&self) -> Option { + match self { + ResolvedTarget::SocketAddr(addr) => Some(*addr), + _ => None, + } + } +} + +pub trait LocalAddress { + fn local_address(&self) -> std::io::Result; +} + trait TcpResolve { fn into(self) -> MaybeResolvedTarget; } diff --git a/rust/gel-stream/src/common/tls.rs b/rust/gel-stream/src/common/tls.rs new file mode 100644 index 00000000000..07c2ead1608 --- /dev/null +++ b/rust/gel-stream/src/common/tls.rs @@ -0,0 +1,259 @@ +use crate::{SslError, Stream}; +use rustls_pki_types::{CertificateDer, CertificateRevocationListDer, PrivateKeyDer, ServerName}; +use std::{borrow::Cow, future::Future, sync::Arc}; + +use super::BaseStream; + +// Note that we choose rustls when both openssl and rustls are enabled. + +#[cfg(all(feature = "openssl", not(feature = "rustls")))] +pub type Ssl = crate::common::openssl::OpensslDriver; +#[cfg(feature = "rustls")] +pub type Ssl = crate::common::rustls::RustlsDriver; +#[cfg(not(any(feature = "openssl", feature = "rustls")))] +pub type Ssl = NullTlsDriver; + +pub trait TlsDriver: Default + Send + Sync + Unpin + 'static { + type Stream: Stream + Send; + type ClientParams: Unpin + Send; + type ServerParams: Unpin + Send; + + #[allow(unused)] + fn init_client( + params: &TlsParameters, + name: Option, + ) -> Result; + #[allow(unused)] + fn init_server(params: &TlsServerParameters) -> Result; + + fn upgrade_client( + params: Self::ClientParams, + stream: S, + ) -> impl Future> + Send; + fn upgrade_server( + params: TlsServerParameterProvider, + stream: S, + ) -> impl Future> + Send; +} + +#[derive(Default)] +pub struct NullTlsDriver; + +#[allow(unused)] +impl TlsDriver for NullTlsDriver { + type Stream = BaseStream; + type ClientParams = (); + type ServerParams = (); + + fn init_client( + params: &TlsParameters, + name: Option, + ) -> Result { + Err(SslError::SslUnsupportedByClient) + } + + fn init_server(params: &TlsServerParameters) -> Result { + Err(SslError::SslUnsupportedByClient) + } + + async fn upgrade_client( + params: Self::ClientParams, + stream: S, + ) -> Result<(Self::Stream, TlsHandshake), SslError> { + Err(SslError::SslUnsupportedByClient) + } + + async fn upgrade_server( + params: TlsServerParameterProvider, + stream: S, + ) -> Result<(Self::Stream, TlsHandshake), SslError> { + Err(SslError::SslUnsupportedByClient) + } +} + +/// Verification modes for TLS that are a superset of both PostgreSQL and EdgeDB/Gel. +/// +/// Postgres offers six levels: `disable`, `allow`, `prefer`, `require`, `verify-ca` and `verify-full`. +/// +/// EdgeDB/Gel offers three levels: `insecure`, `no_host_verification' and 'strict'. +/// +/// This table maps the various levels: +/// +/// | Postgres | EdgeDB/Gel | `TlsServerCertVerify` enum | +/// | -------- | ----------- | ----------------- | +/// | require | insecure | `Insecure` | +/// | verify-ca | no_host_verification | `IgnoreHostname` | +/// | verify-full | strict | `VerifyFull` | +/// +/// Note that both EdgeDB/Gel and Postgres may alter certificate validation levels +/// when custom root certificates are provided. This must be done in the +/// `TlsParameters` struct by the caller. +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +pub enum TlsServerCertVerify { + /// Do not verify the server's certificate. Only confirm that the server is + /// using TLS. + Insecure, + /// Verify the server's certificate using the CA (ignore hostname). + IgnoreHostname, + /// Verify the server's certificate using the CA and hostname. + #[default] + VerifyFull, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum TlsCert { + /// Use the system's default certificate. + #[default] + System, + /// Use a custom root certificate only. + Custom(CertificateDer<'static>), +} + +#[derive(Default, Debug, PartialEq, Eq)] +pub struct TlsParameters { + pub server_cert_verify: TlsServerCertVerify, + pub cert: Option>, + pub key: Option>, + pub root_cert: TlsCert, + pub crl: Vec>, + pub min_protocol_version: Option, + pub max_protocol_version: Option, + pub enable_keylog: bool, + pub sni_override: Option>, + pub alpn: TlsAlpn, +} + +impl TlsParameters { + pub fn insecure() -> Self { + Self { + server_cert_verify: TlsServerCertVerify::Insecure, + ..Default::default() + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum SslVersion { + Tls1, + Tls1_1, + Tls1_2, + Tls1_3, +} + +#[derive(Default, Debug, PartialEq, Eq)] +pub enum TlsClientCertVerify { + /// Do not verify the client's certificate, just ignore it. + #[default] + Ignore, + /// If a client certificate is provided, validate it. + Optional(Vec>), + /// Validate that a client certificate exists and is valid. This configuration + /// may not be ideal, because it does not fail the client-side handshake. + Validate(Vec>), +} + +#[derive(derive_more::Debug, derive_more::Constructor)] +pub struct TlsKey { + #[debug("key(...)")] + pub(crate) key: PrivateKeyDer<'static>, + #[debug("cert(...)")] + pub(crate) cert: CertificateDer<'static>, +} + +#[derive(Debug, Clone)] +pub struct TlsServerParameterProvider { + inner: TlsServerParameterProviderInner, +} + +impl TlsServerParameterProvider { + pub fn new(params: TlsServerParameters) -> Self { + Self { + inner: TlsServerParameterProviderInner::Static(Arc::new(params)), + } + } + + pub fn with_lookup( + lookup: impl Fn(Option) -> Arc + Send + Sync + 'static, + ) -> Self { + Self { + inner: TlsServerParameterProviderInner::Lookup(Arc::new(lookup)), + } + } + + pub fn lookup(&self, name: Option) -> Arc { + match &self.inner { + TlsServerParameterProviderInner::Static(params) => params.clone(), + TlsServerParameterProviderInner::Lookup(lookup) => lookup(name), + } + } +} + +#[derive(derive_more::Debug, Clone)] +enum TlsServerParameterProviderInner { + Static(Arc), + #[debug("Lookup(...)")] + #[allow(clippy::type_complexity)] + Lookup(Arc) -> Arc + Send + Sync + 'static>), +} + +#[derive(Debug)] +pub struct TlsServerParameters { + pub client_cert_verify: TlsClientCertVerify, + pub min_protocol_version: Option, + pub max_protocol_version: Option, + pub server_certificate: TlsKey, + pub alpn: TlsAlpn, +} + +#[derive(Debug, Default, Eq, PartialEq)] +pub struct TlsAlpn { + /// The split form (ie: ["AB", "ABCD"]) + alpn_parts: Cow<'static, [Cow<'static, [u8]>]>, +} + +impl TlsAlpn { + pub fn new(alpn: &'static [&'static [u8]]) -> Self { + let alpn = alpn.iter().map(|s| Cow::Borrowed(*s)).collect::>(); + Self { + alpn_parts: Cow::Owned(alpn), + } + } + + pub fn new_str(alpn: &'static [&'static str]) -> Self { + let alpn = alpn + .iter() + .map(|s| Cow::Borrowed(s.as_bytes())) + .collect::>(); + Self { + alpn_parts: Cow::Owned(alpn), + } + } + + pub fn is_empty(&self) -> bool { + self.alpn_parts.is_empty() + } + + pub fn as_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(self.alpn_parts.len() * 2); + for part in self.alpn_parts.iter() { + bytes.push(part.len() as u8); + bytes.extend_from_slice(part.as_ref()); + } + bytes + } + + pub fn as_vec_vec(&self) -> Vec> { + let mut vec = Vec::with_capacity(self.alpn_parts.len()); + for part in self.alpn_parts.iter() { + vec.push(part.to_vec()); + } + vec + } +} + +#[derive(Debug, Clone, Default)] +pub struct TlsHandshake { + pub alpn: Option>, + pub sni: Option>, + pub cert: Option>, +} diff --git a/rust/gel-stream/src/client/tokio_stream.rs b/rust/gel-stream/src/common/tokio_stream.rs similarity index 61% rename from rust/gel-stream/src/client/tokio_stream.rs rename to rust/gel-stream/src/common/tokio_stream.rs index 3b938e86d28..45b07310708 100644 --- a/rust/gel-stream/src/client/tokio_stream.rs +++ b/rust/gel-stream/src/common/tokio_stream.rs @@ -2,13 +2,13 @@ use std::net::{IpAddr, ToSocketAddrs}; use std::pin::Pin; -use std::task::{Context, Poll}; +use std::task::{ready, Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite}; -use tokio::net::TcpStream; #[cfg(unix)] use tokio::net::UnixStream; +use tokio::net::{TcpListener, TcpStream, UnixListener}; -use super::target::ResolvedTarget; +use super::target::{LocalAddress, ResolvedTarget}; pub(crate) struct Resolver { #[cfg(feature = "hickory")] @@ -51,7 +51,8 @@ impl Resolver { } impl ResolvedTarget { - /// Connects to the socket address and returns a TokioStream + #[cfg(feature = "client")] + /// Connects to the socket address and returns a [`TokioStream`]. pub async fn connect(&self) -> std::io::Result { match self { ResolvedTarget::SocketAddr(addr) => { @@ -66,6 +67,76 @@ impl ResolvedTarget { } } } + + #[cfg(feature = "server")] + pub async fn listen( + &self, + ) -> std::io::Result< + impl futures::Stream> + LocalAddress, + > { + self.listen_raw().await + } + + /// Listens for incoming connections on the socket address and returns a + /// [`futures::Stream`] of [`TokioStream`]s and the incoming address. + #[cfg(feature = "server")] + pub(crate) async fn listen_raw(&self) -> std::io::Result { + match self { + ResolvedTarget::SocketAddr(addr) => { + let listener = TcpListener::bind(addr).await?; + Ok(TokioListenerStream::Tcp(listener)) + } + #[cfg(unix)] + ResolvedTarget::UnixSocketAddr(path) => { + let listener = std::os::unix::net::UnixListener::bind_addr(path)?; + listener.set_nonblocking(true)?; + let listener = tokio::net::UnixListener::from_std(listener)?; + Ok(TokioListenerStream::Unix(listener)) + } + } + } +} + +pub(crate) enum TokioListenerStream { + Tcp(TcpListener), + #[cfg(unix)] + Unix(UnixListener), +} + +impl LocalAddress for TokioListenerStream { + fn local_address(&self) -> std::io::Result { + match self { + TokioListenerStream::Tcp(listener) => { + listener.local_addr().map(ResolvedTarget::SocketAddr) + } + #[cfg(unix)] + TokioListenerStream::Unix(listener) => listener + .local_addr() + .map(|addr| ResolvedTarget::UnixSocketAddr(addr.into())), + } + } +} + +impl futures::Stream for TokioListenerStream { + type Item = std::io::Result<(TokioStream, ResolvedTarget)>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + TokioListenerStream::Tcp(listener) => { + let (stream, addr) = ready!(listener.poll_accept(cx))?; + let stream = TokioStream::Tcp(stream); + let target = ResolvedTarget::SocketAddr(addr); + Poll::Ready(Some(Ok((stream, target)))) + } + #[cfg(unix)] + TokioListenerStream::Unix(listener) => { + let (stream, addr) = ready!(listener.poll_accept(cx))?; + let stream = TokioStream::Unix(stream); + let target = ResolvedTarget::UnixSocketAddr(addr.into()); + Poll::Ready(Some(Ok((stream, target)))) + } + } + } } /// Represents a connected Tokio stream, either TCP or Unix diff --git a/rust/gel-stream/src/lib.rs b/rust/gel-stream/src/lib.rs index b9babe5bc1d..a8feb5678b8 100644 --- a/rust/gel-stream/src/lib.rs +++ b/rust/gel-stream/src/lib.rs @@ -1 +1,127 @@ -pub mod client; +// We don't want to warn about unused code when 1) either client or server is not +// enabled, or 2) no crypto backend is enabled. +#![cfg_attr( + not(all( + all(feature = "client", feature = "server"), + any(feature = "rustls", feature = "openssl") + )), + allow(unused) +)] + +#[cfg(feature = "client")] +mod client; +#[cfg(feature = "server")] +mod server; + +#[cfg(feature = "client")] +pub use client::Connector; + +#[cfg(feature = "server")] +pub use server::Acceptor; + +mod common; +#[cfg(feature = "openssl")] +pub use common::openssl::OpensslDriver; +#[cfg(feature = "rustls")] +pub use common::rustls::RustlsDriver; +pub use common::{stream::*, target::*, tls::*, BaseStream}; + +#[derive(Debug, thiserror::Error)] +pub enum ConnectionError { + /// I/O error encountered during connection operations. + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + + /// UTF-8 decoding error. + #[error("UTF8 error: {0}")] + Utf8Error(#[from] std::str::Utf8Error), + + /// SSL-related error. + #[error("SSL error: {0}")] + SslError(#[from] SslError), +} + +#[derive(Debug, thiserror::Error)] +pub enum SslError { + #[error("SSL is not supported by this client transport")] + SslUnsupportedByClient, + #[error("SSL is already upgraded or is in the process of upgrading")] + SslAlreadyUpgraded, + + #[cfg(feature = "openssl")] + #[error("OpenSSL error: {0}")] + OpenSslError(#[from] ::openssl::ssl::Error), + #[cfg(feature = "openssl")] + #[error("OpenSSL error: {0}")] + OpenSslErrorStack(#[from] ::openssl::error::ErrorStack), + #[cfg(feature = "openssl")] + #[error("OpenSSL certificate verification error: {0}")] + OpenSslErrorVerify(#[from] ::openssl::x509::X509VerifyResult), + + #[cfg(feature = "rustls")] + #[error("Rustls error: {0}")] + RustlsError(#[from] ::rustls::Error), + + #[cfg(feature = "rustls")] + #[error("Webpki error: {0}")] + WebpkiError(::webpki::Error), + + #[cfg(feature = "rustls")] + #[error("Verifier builder error: {0}")] + VerifierBuilderError(#[from] ::rustls::server::VerifierBuilderError), + + #[error("Invalid DNS name: {0}")] + InvalidDnsNameError(#[from] ::rustls_pki_types::InvalidDnsNameError), + + #[error("SSL I/O error: {0}")] + SslIoError(#[from] std::io::Error), +} + +impl SslError { + /// Returns a common error for any time of crypto backend. + pub fn common_error(&self) -> Option { + match self { + #[cfg(feature = "rustls")] + SslError::RustlsError(::rustls::Error::InvalidCertificate(cert_err)) => { + match cert_err { + ::rustls::CertificateError::NotValidForName => { + Some(CommonError::InvalidCertificateForName) + } + ::rustls::CertificateError::Revoked => Some(CommonError::CertificateRevoked), + ::rustls::CertificateError::Expired => Some(CommonError::CertificateExpired), + ::rustls::CertificateError::UnknownIssuer => Some(CommonError::InvalidIssuer), + _ => None, + } + } + #[cfg(feature = "openssl")] + SslError::OpenSslErrorVerify(e) => match e.as_raw() { + openssl_sys::X509_V_ERR_HOSTNAME_MISMATCH => { + Some(CommonError::InvalidCertificateForName) + } + openssl_sys::X509_V_ERR_IP_ADDRESS_MISMATCH => { + Some(CommonError::InvalidCertificateForName) + } + openssl_sys::X509_V_ERR_CERT_REVOKED => Some(CommonError::CertificateRevoked), + openssl_sys::X509_V_ERR_CERT_HAS_EXPIRED => Some(CommonError::CertificateExpired), + openssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT + | openssl_sys::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY => { + Some(CommonError::InvalidIssuer) + } + _ => None, + }, + _ => None, + } + } +} + +#[derive(Debug, thiserror::Error, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +pub enum CommonError { + #[error("The certificate's subject name(s) do not match the name of the host")] + InvalidCertificateForName, + #[error("The certificate has been revoked")] + CertificateRevoked, + #[error("The certificate has expired")] + CertificateExpired, + #[error("The certificate was issued by an untrusted authority")] + InvalidIssuer, +} diff --git a/rust/gel-stream/src/server/acceptor.rs b/rust/gel-stream/src/server/acceptor.rs new file mode 100644 index 00000000000..df9c9307653 --- /dev/null +++ b/rust/gel-stream/src/server/acceptor.rs @@ -0,0 +1,178 @@ +use crate::{ + common::tokio_stream::TokioListenerStream, ConnectionError, LocalAddress, ResolvedTarget, + RewindStream, Ssl, SslError, StreamUpgrade, TlsDriver, TlsServerParameterProvider, + UpgradableStream, +}; +use futures::{FutureExt, StreamExt}; +use std::{ + future::Future, + pin::Pin, + task::{ready, Poll}, +}; +use std::{net::SocketAddr, path::Path}; + +use super::Connection; + +pub struct Acceptor { + resolved_target: ResolvedTarget, + tls_provider: Option, + should_upgrade: bool, +} + +impl Acceptor { + pub fn new_tcp(addr: SocketAddr) -> Self { + Self { + resolved_target: ResolvedTarget::SocketAddr(addr), + tls_provider: None, + should_upgrade: false, + } + } + + pub fn new_tcp_tls(addr: SocketAddr, provider: TlsServerParameterProvider) -> Self { + Self { + resolved_target: ResolvedTarget::SocketAddr(addr), + tls_provider: Some(provider), + should_upgrade: true, + } + } + + pub fn new_tcp_starttls(addr: SocketAddr, provider: TlsServerParameterProvider) -> Self { + Self { + resolved_target: ResolvedTarget::SocketAddr(addr), + tls_provider: Some(provider), + should_upgrade: false, + } + } + + #[cfg(unix)] + pub fn new_unix_path(path: impl AsRef) -> Result { + Ok(Self { + resolved_target: ResolvedTarget::from(std::os::unix::net::SocketAddr::from_pathname( + path, + )?), + tls_provider: None, + should_upgrade: false, + }) + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn new_unix_domain(domain: impl AsRef<[u8]>) -> Result { + use std::os::linux::net::SocketAddrExt; + Ok(Self { + resolved_target: ResolvedTarget::from( + std::os::unix::net::SocketAddr::from_abstract_name(domain)?, + ), + tls_provider: None, + should_upgrade: false, + }) + } + + pub async fn bind( + self, + ) -> Result< + impl ::futures::Stream> + LocalAddress, + ConnectionError, + > { + let stream = self.resolved_target.listen_raw().await?; + Ok(AcceptedStream { + stream, + should_upgrade: self.should_upgrade, + upgrade_future: None, + tls_provider: self.tls_provider, + _phantom: None, + }) + } + + #[allow(private_bounds)] + pub async fn bind_explicit( + self, + ) -> Result< + impl ::futures::Stream, ConnectionError>> + LocalAddress, + ConnectionError, + > { + let stream = self.resolved_target.listen_raw().await?; + Ok(AcceptedStream { + stream, + should_upgrade: self.should_upgrade, + upgrade_future: None, + tls_provider: self.tls_provider, + _phantom: None, + }) + } + + pub async fn accept_one(self) -> Result { + let mut stream = self.resolved_target.listen().await?; + let (stream, _target) = stream.next().await.unwrap()?; + Ok(UpgradableStream::new_server( + RewindStream::new(stream), + None::, + )) + } +} + +struct AcceptedStream { + stream: TokioListenerStream, + should_upgrade: bool, + tls_provider: Option, + #[allow(clippy::type_complexity)] + upgrade_future: + Option, SslError>> + Send + 'static>>>, + // Avoid using PhantomData because it fails to implement certain auto-traits + _phantom: Option<&'static D>, +} + +impl LocalAddress for AcceptedStream { + fn local_address(&self) -> std::io::Result { + self.stream.local_address() + } +} + +impl futures::Stream for AcceptedStream { + type Item = Result, ConnectionError>; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + if let Some(mut upgrade_future) = self.upgrade_future.take() { + match upgrade_future.poll_unpin(cx) { + Poll::Ready(Ok(conn)) => { + return Poll::Ready(Some(Ok(conn))); + } + Poll::Ready(Err(e)) => { + return Poll::Ready(Some(Err(e.into()))); + } + Poll::Pending => { + self.upgrade_future = Some(upgrade_future); + return Poll::Pending; + } + } + } + let r = ready!(self.stream.poll_next_unpin(cx)); + let Some(r) = r else { + return Poll::Ready(None); + }; + let (stream, _target) = r?; + let mut stream = + UpgradableStream::new_server(RewindStream::new(stream), self.tls_provider.clone()); + if self.should_upgrade { + let mut upgrade_future = Box::pin(async move { + stream.secure_upgrade().await?; + Ok::<_, SslError>(stream) + }); + match upgrade_future.poll_unpin(cx) { + Poll::Ready(Ok(stream)) => { + return Poll::Ready(Some(Ok(stream))); + } + Poll::Ready(Err(e)) => { + return Poll::Ready(Some(Err(e.into()))); + } + Poll::Pending => { + self.upgrade_future = Some(upgrade_future); + return Poll::Pending; + } + } + } + Poll::Ready(Some(Ok(stream))) + } +} diff --git a/rust/gel-stream/src/server/mod.rs b/rust/gel-stream/src/server/mod.rs new file mode 100644 index 00000000000..81d15f68943 --- /dev/null +++ b/rust/gel-stream/src/server/mod.rs @@ -0,0 +1,6 @@ +use crate::{RewindStream, Ssl, UpgradableStream}; + +mod acceptor; +pub use acceptor::Acceptor; + +type Connection = UpgradableStream, D>; diff --git a/rust/gel-stream/tests/socket.rs b/rust/gel-stream/tests/socket.rs new file mode 100644 index 00000000000..47f92e3de5d --- /dev/null +++ b/rust/gel-stream/tests/socket.rs @@ -0,0 +1,66 @@ +use futures::StreamExt; +use gel_stream::*; +use std::net::Ipv4Addr; +use std::net::SocketAddr; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[cfg(unix)] +#[tokio::test] +#[ntest::timeout(30_000)] +async fn test_target_unix() -> Result<(), ConnectionError> { + let tempdir = tempfile::tempdir().unwrap(); + let path = tempdir.path().join("gel-stream-test"); + + // Create a unix socket and connect to it + let mut acceptor = Acceptor::new_unix_path(&path)?.bind().await?; + let addr = acceptor.local_address(); + eprintln!("addr: {:?}", addr); + + let accept_task = tokio::spawn(async move { + let mut connection = acceptor.next().await.unwrap().unwrap(); + let mut buf = String::new(); + connection.read_to_string(&mut buf).await.unwrap(); + assert_eq!(buf, "Hello, world!"); + }); + + let connect_task = tokio::spawn(async { + let target = Target::new_unix_path(path)?; + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) +} + +#[tokio::test] +#[ntest::timeout(30_000)] +async fn test_target_tcp() -> Result<(), ConnectionError> { + // Create a TCP listener on a random port + let mut acceptor = Acceptor::new_tcp(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0)) + .bind() + .await?; + let addr = acceptor.local_address()?; + + let accept_task = tokio::spawn(async move { + let mut connection = acceptor.next().await.unwrap().unwrap(); + let mut buf = String::new(); + connection.read_to_string(&mut buf).await.unwrap(); + assert_eq!(buf, "Hello, world!"); + }); + + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved(addr); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) +} diff --git a/rust/gel-stream/tests/tls.rs b/rust/gel-stream/tests/tls.rs new file mode 100644 index 00000000000..b71b2a15386 --- /dev/null +++ b/rust/gel-stream/tests/tls.rs @@ -0,0 +1,451 @@ +use futures::StreamExt; +use gel_stream::*; +use std::borrow::Cow; +use std::net::Ipv4Addr; +use std::net::SocketAddr; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +fn load_client_test_cert() -> rustls_pki_types::CertificateDer<'static> { + rustls_pemfile::certs(&mut include_str!("../../../tests/certs/client.cert.pem").as_bytes()) + .next() + .expect("no cert") + .expect("cert is bad") +} + +fn load_client_test_key() -> rustls_pki_types::PrivateKeyDer<'static> { + rustls_pemfile::private_key(&mut include_str!("../../../tests/certs/client.key.pem").as_bytes()) + .expect("no client key") + .expect("client key is bad") +} + +fn load_client_test_ca() -> rustls_pki_types::CertificateDer<'static> { + rustls_pemfile::certs(&mut include_str!("../../../tests/certs/client_ca.cert.pem").as_bytes()) + .next() + .expect("no ca cert") + .expect("ca cert is bad") +} + +fn load_test_cert() -> rustls_pki_types::CertificateDer<'static> { + rustls_pemfile::certs(&mut include_str!("../../../tests/certs/server.cert.pem").as_bytes()) + .next() + .expect("no cert") + .expect("cert is bad") +} + +fn load_test_ca() -> rustls_pki_types::CertificateDer<'static> { + rustls_pemfile::certs(&mut include_str!("../../../tests/certs/ca.cert.pem").as_bytes()) + .next() + .expect("no ca cert") + .expect("ca cert is bad") +} + +fn load_test_key() -> rustls_pki_types::PrivateKeyDer<'static> { + rustls_pemfile::private_key(&mut include_str!("../../../tests/certs/server.key.pem").as_bytes()) + .expect("no server key") + .expect("server key is bad") +} + +fn load_test_crls() -> Vec> { + rustls_pemfile::crls(&mut include_str!("../../../tests/certs/ca.crl.pem").as_bytes()) + .collect::, _>>() + .unwrap() +} + +fn tls_server_parameters( + alpn: TlsAlpn, + client_cert_verify: TlsClientCertVerify, +) -> TlsServerParameterProvider { + TlsServerParameterProvider::new(TlsServerParameters { + server_certificate: TlsKey::new(load_test_key(), load_test_cert()), + client_cert_verify, + min_protocol_version: None, + max_protocol_version: None, + alpn, + }) +} + +async fn spawn_tls_server( + expected_hostname: Option<&str>, + server_alpn: TlsAlpn, + expected_alpn: Option<&str>, + client_cert_verify: TlsClientCertVerify, +) -> Result< + ( + ResolvedTarget, + tokio::task::JoinHandle>, + ), + ConnectionError, +> { + let mut acceptor = Acceptor::new_tcp_tls( + SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0), + tls_server_parameters(server_alpn, client_cert_verify), + ) + .bind_explicit::() + .await?; + let addr = acceptor.local_address()?; + + let expected_alpn = expected_alpn.map(|alpn| alpn.as_bytes().to_vec()); + let expected_hostname = expected_hostname.map(|sni| sni.to_string()); + let accept_task = tokio::spawn(async move { + let mut connection = acceptor.next().await.unwrap()?; + let handshake = connection + .handshake() + .unwrap_or_else(|| panic!("handshake was not available on {connection:?}")); + assert_eq!( + handshake.alpn.as_ref().map(|b| b.as_ref().to_vec()), + expected_alpn + ); + assert_eq!(handshake.sni.as_deref(), expected_hostname.as_deref()); + let mut buf = String::new(); + connection.read_to_string(&mut buf).await.unwrap(); + assert_eq!(buf, "Hello, world!"); + connection.shutdown().await?; + Ok::<_, ConnectionError>(()) + }); + Ok((addr, accept_task)) +} + +macro_rules! tls_test ( + ( + $( + $(#[ $attr:meta ])* + async fn $name:ident() -> Result<(), ConnectionError> $body:block + )* + ) => { + mod rustls { + use super::*; + $( + $(#[ $attr ])* + async fn $name() -> Result<(), ConnectionError> { + async fn test_inner() -> Result<(), ConnectionError> { + $body + } + test_inner::().await + } + )* + } + + mod rustls_server { + use super::*; + $( + $(#[ $attr ])* + async fn $name() -> Result<(), ConnectionError> { + async fn test_inner() -> Result<(), ConnectionError> { + $body + } + test_inner::().await + } + )* + } + + mod openssl { + use super::*; + + $( + $(#[ $attr ])* + async fn $name() -> Result<(), ConnectionError> { + async fn test_inner() -> Result<(), ConnectionError> { + $body + } + test_inner::().await + } + )* + } + + mod openssl_server { + use super::*; + $( + $(#[ $attr ])* + async fn $name() -> Result<(), ConnectionError> { + async fn test_inner() -> Result<(), ConnectionError> { + $body + } + test_inner::().await + } + )* + } + + } +); + +tls_test! { + /// The certificate is not valid for 127.0.0.1, so the connection should fail. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_verify_full_fails() -> Result<(), ConnectionError> { + let (addr, accept_task) = + spawn_tls_server::(None, TlsAlpn::default(), None, TlsClientCertVerify::Ignore).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved_tls( + addr, // Raw IP + TlsParameters { + ..Default::default() + }, + ); + let stm = Connector::::new_explicit(target).unwrap().connect().await; + assert!( + matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::InvalidIssuer)), + "{stm:?}" + ); + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap_err(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// The certificate is not valid for 127.0.0.1, so the connection should fail. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_verify_full_fails_name() -> Result<(), ConnectionError> { + let (addr, accept_task) = + spawn_tls_server::(None, TlsAlpn::default(), None, TlsClientCertVerify::Ignore).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved_tls( + addr, // Raw IP + TlsParameters { + root_cert: TlsCert::Custom(load_test_ca()), + ..Default::default() + }, + ); + let stm = Connector::::new_explicit(target).unwrap().connect().await; + assert!( + matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::InvalidCertificateForName)), + "{stm:?}" + ); + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap_err(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// The certificate is valid for "localhost", so the connection should succeed. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_verify_full_ok() -> Result<(), ConnectionError> { + let (addr, accept_task) = spawn_tls_server::( + Some("localhost"), + TlsAlpn::default(), + None, + TlsClientCertVerify::Ignore, + ) + .await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("localhost", addr.tcp().unwrap().port()), + TlsParameters { + root_cert: TlsCert::Custom(load_test_ca()), + ..Default::default() + }, + ); + let mut stm = Connector::::new_explicit(target).unwrap().connect().await?; + stm.write_all(b"Hello, world!").await?; + stm.shutdown().await?; + Ok::<_, ConnectionError>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_insecure() -> Result<(), ConnectionError> { + let (addr, accept_task) = + spawn_tls_server::(None, TlsAlpn::default(), None, TlsClientCertVerify::Ignore).await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved_tls( + addr, // Raw IP + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + ..Default::default() + }, + ); + let mut stm = Connector::::new_explicit(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await?; + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_crl() -> Result<(), ConnectionError> { + let (addr, accept_task) = spawn_tls_server::( + Some("localhost"), + TlsAlpn::default(), + None, + TlsClientCertVerify::Ignore, + ) + .await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_tcp_tls( + ("localhost", addr.tcp().unwrap().port()), + TlsParameters { + root_cert: TlsCert::Custom(load_test_ca()), + crl: load_test_crls(), + ..Default::default() + }, + ); + let stm = Connector::::new_explicit(target).unwrap().connect().await; + assert!( + matches!(&stm, Err(ConnectionError::SslError(ssl)) if ssl.common_error() == Some(CommonError::CertificateRevoked)), + "{stm:?}" + ); + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap_err(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// Test that we can override the SNI. + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_sni_override() -> Result<(), ConnectionError> { + let (addr, accept_task) = spawn_tls_server::( + Some("www.google.com"), + TlsAlpn::default(), + None, + TlsClientCertVerify::Ignore, + ) + .await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved_tls( + addr, + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + sni_override: Some(Cow::Borrowed("www.google.com")), + ..Default::default() + }, + ); + let mut stm = Connector::::new_explicit(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await.unwrap(); + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + /// Test that we can set the ALPN. + #[tokio::test] + async fn test_target_tcp_tls_alpn() -> Result<(), ConnectionError> { + let (addr, accept_task) = spawn_tls_server::( + None, + TlsAlpn::new_str(&["nope", "accepted"]), + Some("accepted"), + TlsClientCertVerify::Ignore, + ) + .await?; + + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved_tls( + addr, + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + alpn: TlsAlpn::new_str(&["accepted", "fake"]), + ..Default::default() + }, + ); + let mut stm = Connector::::new_explicit(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await.unwrap(); + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + + Ok(()) + } + + #[tokio::test] + #[ntest::timeout(30_000)] + async fn test_target_tcp_tls_client_verify_optional() -> Result<(), ConnectionError> { + let (addr, accept_task) = spawn_tls_server::( + None, + TlsAlpn::default(), + None, + TlsClientCertVerify::Optional(vec![load_client_test_ca()]), + ) + .await?; + let connect_task = tokio::spawn(async move { + let target = Target::new_resolved_tls( + addr, + TlsParameters { + server_cert_verify: TlsServerCertVerify::Insecure, + cert: Some(load_client_test_cert()), + key: Some(load_client_test_key()), + ..Default::default() + }, + ); + let mut stm = Connector::::new_explicit(target).unwrap().connect().await.unwrap(); + stm.write_all(b"Hello, world!").await.unwrap(); + stm.shutdown().await?; + Ok::<_, std::io::Error>(()) + }); + accept_task.await.unwrap().unwrap(); + connect_task.await.unwrap().unwrap(); + Ok(()) + } +} + +#[cfg(feature = "__manual_tests")] +#[tokio::test] +async fn test_live_server_manual_google_com() { + let target = Target::new_tcp_tls(("www.google.com", 443), TlsParameters::default()); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap(); + // HTTP/1. ..... + assert_eq!(stm.read_u8().await.unwrap(), b'H'); +} + +/// Normally connecting to Google's IP will send an invalid SNI and fail. +/// This test ensures that we can override the SNI to the correct hostname. +#[cfg(feature = "__manual_tests")] +#[tokio::test] +async fn test_live_server_google_com_override_sni() { + use std::net::ToSocketAddrs; + + let addr = "www.google.com:443" + .to_socket_addrs() + .unwrap() + .into_iter() + .next() + .unwrap(); + let target = Target::new_tcp_tls( + addr, + TlsParameters { + sni_override: Some(Cow::Borrowed("www.google.com")), + ..Default::default() + }, + ); + let mut stm = Connector::new(target).unwrap().connect().await.unwrap(); + stm.write_all(b"GET / HTTP/1.0\r\n\r\n").await.unwrap(); + // HTTP/1. ..... + assert_eq!(stm.read_u8().await.unwrap(), b'H'); +} diff --git a/rust/pgrust/Cargo.toml b/rust/pgrust/Cargo.toml index e23839d874e..e65b8413e27 100644 --- a/rust/pgrust/Cargo.toml +++ b/rust/pgrust/Cargo.toml @@ -18,7 +18,7 @@ pyo3.workspace = true tokio.workspace = true tracing.workspace = true db_proto.workspace = true -gel-stream.workspace = true +gel-stream = { workspace = true, features = ["client"] } futures = "0" thiserror = "1" @@ -41,6 +41,7 @@ features = ["full"] [dev-dependencies] tracing-subscriber.workspace = true captive_postgres.workspace = true +gel-stream = { workspace = true, features = ["rustls"] } scopeguard = "1" pretty_assertions = "1.2.0" diff --git a/rust/pgrust/examples/connect.rs b/rust/pgrust/examples/connect.rs index 1a6d05c6796..0b5e9fd881e 100644 --- a/rust/pgrust/examples/connect.rs +++ b/rust/pgrust/examples/connect.rs @@ -4,7 +4,7 @@ use captive_postgres::{ use clap::Parser; use clap_derive::Parser; use gel_auth::AuthType; -use gel_stream::client::{Connector, ResolvedTarget, Target}; +use gel_stream::{Connector, ResolvedTarget, Target}; use pgrust::{ connection::{ dsn::parse_postgres_dsn_env, Client, Credentials, ExecuteSink, Format, MaxRows, diff --git a/rust/pgrust/src/connection/conn.rs b/rust/pgrust/src/connection/conn.rs index 58c6443d175..0715d439642 100644 --- a/rust/pgrust/src/connection/conn.rs +++ b/rust/pgrust/src/connection/conn.rs @@ -14,7 +14,7 @@ use crate::{ }; use db_proto::StructBuffer; use futures::{future::Either, FutureExt}; -use gel_stream::client::{stream::Stream, Connector}; +use gel_stream::{Connector, Stream}; use std::{ cell::RefCell, future::ready, @@ -52,7 +52,7 @@ pub enum PGConnError { /// /// ``` /// # use pgrust::connection::*; -/// # use gel_stream::client::{Target, Connector}; +/// # use gel_stream::{Target, Connector}; /// # _ = async { /// # let credentials = Credentials::default(); /// # let connector = Connector::new(Target::new_tcp(("localhost", 1234))).unwrap(); diff --git a/rust/pgrust/src/connection/dsn/host.rs b/rust/pgrust/src/connection/dsn/host.rs index e08b28b8510..d290b4bfefe 100644 --- a/rust/pgrust/src/connection/dsn/host.rs +++ b/rust/pgrust/src/connection/dsn/host.rs @@ -1,5 +1,5 @@ use super::ParseError; -use gel_stream::client::{ResolvedTarget, TargetName}; +use gel_stream::{ResolvedTarget, TargetName}; use serde_derive::Serialize; use std::net::{IpAddr, Ipv6Addr}; diff --git a/rust/pgrust/src/connection/mod.rs b/rust/pgrust/src/connection/mod.rs index 8ceb3085dc8..28b4f366f91 100644 --- a/rust/pgrust/src/connection/mod.rs +++ b/rust/pgrust/src/connection/mod.rs @@ -13,7 +13,7 @@ pub use flow::{ CopyDataSink, DataSink, DoneHandling, ExecuteSink, FlowAccumulator, Format, MaxRows, Oid, Param, Pipeline, PipelineBuilder, Portal, QuerySink, Statement, }; -use gel_stream::client::ConnectionError; +use gel_stream::ConnectionError; pub use raw_conn::RawClient; macro_rules! __invalid_state { @@ -89,7 +89,6 @@ pub enum SslError { #[derive(Clone, Default, derive_more::Debug)] pub struct Credentials { pub username: String, - #[debug(skip)] pub password: String, pub database: String, pub server_settings: HashMap, diff --git a/rust/pgrust/src/connection/raw_conn.rs b/rust/pgrust/src/connection/raw_conn.rs index fece126c2b3..688f0dde67a 100644 --- a/rust/pgrust/src/connection/raw_conn.rs +++ b/rust/pgrust/src/connection/raw_conn.rs @@ -10,10 +10,7 @@ use crate::protocol::postgres::{FrontendBuilder, InitialBuilder}; use crate::protocol::{postgres::data::SSLResponse, postgres::meta}; use db_proto::StructBuffer; use gel_auth::AuthType; -use gel_stream::client::{ - stream::{Stream, StreamWithUpgrade, UpgradableStream}, - Connector, -}; +use gel_stream::{ConnectionError, Connector, Stream, StreamUpgrade}; use std::collections::HashMap; use std::pin::Pin; use tokio::io::AsyncWriteExt; @@ -74,16 +71,13 @@ impl ConnectionDriver { } } - async fn drive_bytes( + async fn drive_bytes( &mut self, state: &mut ConnectionState, drive: &[u8], message_buffer: &mut StructBuffer, - stream: &mut UpgradableStream, - ) -> Result<(), PGConnectionError> - where - (B, C): StreamWithUpgrade, - { + stream: &mut S, + ) -> Result<(), PGConnectionError> { message_buffer.push_fallible(drive, |msg| { state.drive(ConnectionDrive::Message(msg), self) })?; @@ -100,7 +94,10 @@ impl ConnectionDriver { } if self.upgrade { self.upgrade = false; - stream.secure_upgrade().await?; + stream + .secure_upgrade() + .await + .map_err(ConnectionError::from)?; state.drive(ConnectionDrive::SslReady, self)?; } else { break; @@ -109,15 +106,12 @@ impl ConnectionDriver { Ok(()) } - async fn drive( + async fn drive( &mut self, state: &mut ConnectionState, drive: ConnectionDrive<'_>, - stream: &mut UpgradableStream, - ) -> Result<(), PGConnectionError> - where - (B, C): StreamWithUpgrade, - { + stream: &mut S, + ) -> Result<(), PGConnectionError> { state.drive(drive, self)?; loop { if !self.send_buffer.is_empty() { @@ -132,7 +126,10 @@ impl ConnectionDriver { } if self.upgrade { self.upgrade = false; - stream.secure_upgrade().await?; + stream + .secure_upgrade() + .await + .map_err(ConnectionError::from)?; state.drive(ConnectionDrive::SslReady, self)?; } else { break; @@ -221,11 +218,11 @@ impl RawClient { } // This should not be possible -- we've fully upgraded the stream by now - let Ok(stream) = stream.into_choice() else { + let Ok(stream) = stream.into_boxed() else { return Err(invalid_state!("Connection was not ready")); }; - Ok(RawClient::new_boxed(stream.into_boxed(), update.params)) + Ok(RawClient::new_boxed(stream, update.params)) } /// Consume the `RawClient` and return the underlying stream and connection parameters. diff --git a/rust/pgrust/src/handshake/edgedb_server.rs b/rust/pgrust/src/handshake/edgedb_server.rs index 6aa2106f8b2..00a4a54fbbe 100644 --- a/rust/pgrust/src/handshake/edgedb_server.rs +++ b/rust/pgrust/src/handshake/edgedb_server.rs @@ -140,8 +140,10 @@ enum ServerStateImpl { Error, } +#[derive(derive_more::Debug, Default)] pub struct ServerState { state: ServerStateImpl, + #[debug(skip)] buffer: StructBuffer, } diff --git a/rust/pgrust/src/handshake/mod.rs b/rust/pgrust/src/handshake/mod.rs index a253e3bbde6..bc3689b5c20 100644 --- a/rust/pgrust/src/handshake/mod.rs +++ b/rust/pgrust/src/handshake/mod.rs @@ -139,7 +139,7 @@ mod tests { username: "user".to_string(), password: "password".to_string(), database: "database".to_string(), - ..Default::default() + server_settings: Default::default(), }, ConnectionSslRequirement::Disable, ); diff --git a/rust/pgrust/src/python.rs b/rust/pgrust/src/python.rs index 0e14393cf78..a31c515fbd4 100644 --- a/rust/pgrust/src/python.rs +++ b/rust/pgrust/src/python.rs @@ -14,7 +14,7 @@ use crate::{ protocol::postgres::{data::SSLResponse, meta, FrontendBuilder, InitialBuilder}, }; use db_proto::StructBuffer; -use gel_stream::client::ResolvedTarget; +use gel_stream::ResolvedTarget; use pyo3::{ buffer::PyBuffer, exceptions::{PyException, PyRuntimeError}, diff --git a/rust/pgrust/tests/query_real_postgres.rs b/rust/pgrust/tests/query_real_postgres.rs index 4f2ba64b39f..22fc622e198 100644 --- a/rust/pgrust/tests/query_real_postgres.rs +++ b/rust/pgrust/tests/query_real_postgres.rs @@ -6,7 +6,7 @@ use std::rc::Rc; // Constants use db_proto::match_message; use gel_auth::AuthType; -use gel_stream::client::{Connector, ResolvedTarget, Target}; +use gel_stream::{Connector, ResolvedTarget, Target}; use pgrust::connection::{ Client, Credentials, FlowAccumulator, MaxRows, Oid, Param, PipelineBuilder, Portal, Statement, }; diff --git a/rust/pgrust/tests/real_postgres.rs b/rust/pgrust/tests/real_postgres.rs index d0cb92fcf8a..301d97ec390 100644 --- a/rust/pgrust/tests/real_postgres.rs +++ b/rust/pgrust/tests/real_postgres.rs @@ -1,6 +1,6 @@ // Constants use gel_auth::AuthType; -use gel_stream::client::{Connector, ResolvedTarget, Target, TlsParameters}; +use gel_stream::{Connector, ResolvedTarget, Target, TlsParameters}; use pgrust::connection::{Credentials, PGConnectionError, RawClient}; use pgrust::errors::PgServerError; use pgrust::handshake::ConnectionSslRequirement; diff --git a/tests/certs/ca.cert.pem b/tests/certs/ca.cert.pem index 63e21bcf524..95b20225e32 100644 --- a/tests/certs/ca.cert.pem +++ b/tests/certs/ca.cert.pem @@ -1,40 +1,40 @@ -----BEGIN CERTIFICATE----- -MIIG+zCCBOOgAwIBAgIUOqp4W75e/yUKZ3Qih+D1hlvHZjowDQYJKoZIhvcNAQEL +MIIG+zCCBOOgAwIBAgIURGhmXq3lrQDD650rd+ZqQ4PBajMwDQYJKoZIhvcNAQEL BQAwgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH DA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKDAtFZGdlREIgSW5jLjEVMBMGA1UECwwM RWRnZURCIHRlc3RzMRwwGgYDVQQDDBNFZGdlREIgdGVzdCByb290IGNhMR8wHQYJ -KoZIhvcNAQkBFhBoZWxsb0BlZGdlZGIuY29tMB4XDTI1MDEyNTE3MjEwNVoXDTQ1 -MDEyMDE3MjEwNVowgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +KoZIhvcNAQkBFhBoZWxsb0BlZGdlZGIuY29tMB4XDTI1MDEyOTIwNDAyN1oXDTQ1 +MDEyNDIwNDAyN1owgaYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKDAtFZGdlREIgSW5jLjEV MBMGA1UECwwMRWRnZURCIHRlc3RzMRwwGgYDVQQDDBNFZGdlREIgdGVzdCByb290 IGNhMR8wHQYJKoZIhvcNAQkBFhBoZWxsb0BlZGdlZGIuY29tMIICIjANBgkqhkiG -9w0BAQEFAAOCAg8AMIICCgKCAgEAoCCv8eo5PlW1XBpnFU98b/qcr0Yo6FE3SOU0 -td2k6xydgcDI7vLkCpGGl7pcbr2mLFvn0XKJJkrGUO7N4pjgEBxp+BK7+LGoUQrw -+lVwTYaASbpWRQ8UyTPREhZjxVAYvlaXNXvKFu2Ie8ij3sQZgE6WUao1fjG9TFae -rbvWDdfhtxz4ayqpVVVHIxRA7Scn81KHlGCLBzBt7B435S2DdmJVEBzeR8dZCIR5 -rkvr4QeO0fKiUpldJ3PcWLpLW8oxenFog+/wRZ18QlgUGf6iKWpaRVWkrCh5ZQqx -kCK1O+kw72Czc3J/J8+VIISgCaHh2tXFVevi7wZ4KKtWpEkI0Q9cycYDYtKb2WCN -1NRBsHDqLm6sv7phr6N+txZLXfzD7U9+RL+ELsRZpB5XzMcTuglwBK3svDbTpk9N -zAbb6cPRFmJd5ou9LYcp3l5WEjQXEtpCUnayfF2WLW2vkbyl6cXMVZHSDV9mphNo -LT+SMi3+faMx+CkX4tMSjj2hs/3kG3cCKjwNhJSVubtylFfOYMUyb6wSSTwA/xh7 -5Eey6KtCq2WBLgONtCKIW4o3IvZxmaWGCv02rUuS4IsqsZ/KXwNP1QsMqRLZvefT -y8CPwzxiEO3n3Zc2QT8zF3VJcDwPezK6yNm3XKIzt+CjVhIryH+zNbd4suS0GkP5 -Zvq9LFkCAwEAAaOCAR0wggEZMB0GA1UdDgQWBBQxHJa8W6NzDmAfQ4bNppT+QbJP -OjCB5gYDVR0jBIHeMIHbgBQxHJa8W6NzDmAfQ4bNppT+QbJPOqGBrKSBqTCBpjEL +9w0BAQEFAAOCAg8AMIICCgKCAgEAyrVFGhnSZL/EY0h450+4mWERRUD2I8v8sIjv +Zd5qTf5UEDk1Wg6HOIJHtNmgmVOv0b6jQ9duHuktT4BhU6MFVRel4f1rbX77i6r6 +M6O151uu/iDxxYJJ1MiPQVTfNPH1x/DG8zAHc8XZLKp7S9Lz45k3RQE6RC6cdV2S +CIrCKE/bTm6o3/0ceqrlcOgFuPncdcRBzc0qNl2ecjySzhgcXZZzjoVb7lJ0n4NS +6v8YdB26SceJZJfIhSIFwEkmpGbSYlZpL+2BLkxRg29w7uVoxWtjzQ0DdULrqkWu +WOwQ4w06SIvNQxyYiGWY3bMGELkWUpxQaBBPfTYpbP3quosKpa+LiFAmjIEpKj/R +RZTEQDAogadJoT5rvBYDffdtpK2s95wBfZbZhTumNJEOtD86CjAmBbyHvgM3HW2S +a2aEdq7K3k17Ogk1vB3LwMARgYCbdSBNovayCHdsBsn5tchwNvMalSrLMV+mmm2f +J/yZsDtVeXYcIfZA00Kz26jdBct1a7MBXbQdOqxoIC3aPDV5S3VLqk+lLQolbil9 +welUj/mgW9gfafyfD83fTiHCQwgFC0pLKojYGfWLt67yqs9wvXHNdbv/CMr3Ub5v +6G1NooEP5BtRImOwtMIhDnrBRprsi5xHa1QJtLp91P8cXU7wzwUnA+594qwiNHeH +U0cqHLUCAwEAAaOCAR0wggEZMB0GA1UdDgQWBBQUcTQtXJpgHoIH5Zuy5WaiCGUk +yjCB5gYDVR0jBIHeMIHbgBQUcTQtXJpgHoIH5Zuy5WaiCGUkyqGBrKSBqTCBpjEL MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBG cmFuY2lzY28xFDASBgNVBAoMC0VkZ2VEQiBJbmMuMRUwEwYDVQQLDAxFZGdlREIg dGVzdHMxHDAaBgNVBAMME0VkZ2VEQiB0ZXN0IHJvb3QgY2ExHzAdBgkqhkiG9w0B -CQEWEGhlbGxvQGVkZ2VkYi5jb22CFDqqeFu+Xv8lCmd0Iofg9YZbx2Y6MA8GA1Ud -EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAEWaN71TvIpCLIqBIiBRvdB7 -71kY04L5ZvIFqScZjJ6Ki4QCdjxO2Z52QVjuSeeRJkEMWAppg1oUrIzE5fBGPNwf -zgFsib6MenHlJcjiuOYfE1JvrIDDijoqq7k+/muJATXS5Xa5Wa0Y7BIeNSEoTiCh -EYwnZ5sMBgjlueq9SbFLJ0P2sJVMEdZLMOy25LzVk9T/joguYkLiDp7R1Q6Q9du9 -oLXGDpb5DUci+Ui1JWdn/0cJLSm4PIMlIg9vGLJuhKSgqk9lGFSHAWOJ79kdqih4 -bmC42Ows/69PGVzbpPcNW/d7BPY7CsBCxUdREEIYeOvayRNxU+fizdZH+HNpAWZ2 -ShAU+/8ww3w7rCukIvrAWgv174gl+w0emrEyoQ5lXRbozYlZkwN9wplcjThRTs4D -99EEbkp5CoSlZGQFiYtHbAj58SK7W6cNs14oCrFSV5FPO8sQ3oc12xz+VdKoZtKn -AKz27W8h91QqVOlatMz6fiH+n8V/h4H1rLphrbmFu5mgeX86cphYhLqt4s6b8HYg -vjyL/Or29rInOfg0zur7Es+a68paHIli9FOLtT46XC7NHiwOef5U04pCvUjcTfmn -77PWbYWWfp06X/M20LdYEGs20T4uBV8yEx7BYc7CALg4e6Oo+jsewHffDrf+EO9/ -wuuleg3Qo92tkeoX9SqR +CQEWEGhlbGxvQGVkZ2VkYi5jb22CFERoZl6t5a0Aw+udK3fmakODwWozMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAEFPR/3ga+tJenOAlNwykYma +4rz1ryxizrww2TgFKmA7VdPK+NqyoTuK+OhikJ0r2GCk6jEQvru4e7pBxsUoBpTP +d6wv/xNkMnDgS/J9VGziQF3ZarbayyQdLlt+J/kmvxiF9/RUyB0x3f3hfx/yqrRD +b07/0qRgW0pIk/D5Hs+D3FnPpr3i0pg1iauLwnWptagobFEM27eHAuqOmmc8EMUP +hHmwhEI7vQ1NZ7X/+gdVx/CyIiDYBw88/x4aEH/6rVPZU3Ifg5GINGyMyndJ+WtV +21CSFz8SqG+K2pU3k9wjorJcSZr2ChLEi9gyHH4Rk9ADHYxiKg+qb11Mysyx4Qoo +pyfGeJq1qhb2ScJrdTuj2NctQhUVTQoeIWh36zdf6sxRLXcj8M6gQz5joed1/7Y7 +YgOXq3gmI4e9you/F4Vqhdl3ojhstkpW8fWQiXYvWIvOctgjGtZoLP4gZujgGs4h ++fdX5pMmOoSPrriUINo+0W55hhIFIDisUNkxC1PKkjdU4XjckFC9iJeT0VsKLPzK +HMN0LA7BxEjLnV0TG/vXaoaM+rlz11nMTwt7BNSWqhH+6lxwgOSG3Ft7u8svt7x/ +6b706AHItpYRegu+NWzVfrbntZsJSdGSYi8mZwvIjSrA8o/zFpWepINlWph14mxU +bmAtF79u44d/tAoBn/gH -----END CERTIFICATE----- diff --git a/tests/certs/ca.crl.pem b/tests/certs/ca.crl.pem index 5f3b1bac25f..8c956b333ab 100644 --- a/tests/certs/ca.crl.pem +++ b/tests/certs/ca.crl.pem @@ -3,22 +3,22 @@ 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 -----END X509 CRL----- diff --git a/tests/certs/ca.key.pem b/tests/certs/ca.key.pem index 718e4c4fdb4..f49f6fa219f 100644 --- a/tests/certs/ca.key.pem +++ b/tests/certs/ca.key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -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 -9SXjVRx6nxQSlO4HYDaa2aw6W0+wdw== +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 +HEQwFKprD1VR7j6l2kChJIMHc5RJxQ== -----END PRIVATE KEY----- diff --git a/tests/certs/client.cert.pem b/tests/certs/client.cert.pem index 14510751712..ee18ce07ccf 100644 --- a/tests/certs/client.cert.pem +++ b/tests/certs/client.cert.pem @@ -1,33 +1,36 @@ -----BEGIN CERTIFICATE----- -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 +vR6+GZsfuWGM8iVlCZPSFrZCngjEZKg= -----END CERTIFICATE----- diff --git a/tests/certs/client.csr.pem b/tests/certs/client.csr.pem index ed38fc1246e..d0ccdd809d4 100644 --- a/tests/certs/client.csr.pem +++ b/tests/certs/client.csr.pem @@ -3,27 +3,27 @@ MIIE4TCCAskCAQAwgZsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh MRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKDAtFZGdlREIgSW5jLjEV MBMGA1UECwwMRWRnZURCIHRlc3RzMREwDwYDVQQDDAhzc2xfdXNlcjEfMB0GCSqG SIb3DQEJARYQaGVsbG9AZWRnZWRiLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAKI4Gq3AsHQ8sWyVk+bKgeYuoN0NdfXaALQNZiSZRYlzeiKadugm -3B5SVPU3ivIM+DzKxWIuZRq082xxflBGQIS3AARfjgUD4oSlq8kqnqZG8Gm6PgsA -pG64Dmr/jeU33U2KCtCHSS7ciCLMz3fBWsilaztdLFH0psDjFVGQTiWm0WDzgZNN -It7mXcvUoyb3/rFXgeE0mg35PqVhSlWyFrT4Vb7j1bK4T+yW4ulfx3TgzkGaRp4+ -NnoHWE1arDfT4XZxiav5t/S110W/UR1xMY0+wdCIu5Gl+JKUpazikEz+kJAC5OGJ -kW2nzMMYJ/MlnZWK84q+hM8QZshCHQdW5z87BFwe2382zOs5Zzj/w7XD2cI7pw+3 -Z3FAL79sx+quZZRJomaJTj/capBxK426oewcA4Ey2wmZa7zLVX8Xs0+vmWhm2/xC -/p+E5reLI/v0y2YIW0KmyBKU5X1Hew9b27jZgoao7dPIb36NNDIh9MhQ/pBU5PE1 -XGz679OnQJDZVNUNhZosZoBeuMTrp+g3n7Cuim0btsf3+SQBRlZftfBHJwTspc6H -GPmPKiLCOMSYzx+fZ1e4bw4X7wfkBf+C8uxt55mD1vtd5bvU41++97VtwkPt/9aA -ySd4cb7V6YJPA250+AvUUD5qkq54Si0+ZVJ4RdhBQdDma/skHuu+xS6tAgMBAAGg -ADANBgkqhkiG9w0BAQsFAAOCAgEAdXiLGvPIm4u2XF8OyCi/2wbwT3Hswg8Z7AG+ -F4z6L3B+mDZNVtk+NQr3eAr9Eg5HsjC81VxSwXQtwjnzinAgpoNqRr70qhKeCjXr -szVHdN40L5J2RA+PeB9qFsUBXymt7qr9Cqb6a3s4sHTy8wMaYOmmkNqfdak674Tl -C7+Z/E9saASlgDDrlLLvzvPgC/y5gIhNYK7LIHI8yOtgd96R5Gs9/hyJ9y05+B9i -rnwok1QBkpy2cGnYoX+4Hn2gRIu6wMWfv5mj/wfn/gGihe8Fi2/89u+XRSZaZivZ -9CQzoLu1bajof8lSDPjORrL/ceiz7nNtKlkBCGtsscGUvuFXSMg7ENIukzGhCFI9 -Yo2ZVNnEjzaaBngVSdufVWkUhS/AfxxWKUDq/tS1zzX76PlZ1Cc847x6s+8+28uu -f9IOxwxVMJqEwtvIpG0ECsBBr+tgYIBaaSyUIHRlagOfeZ23HgJjYxa9Zvuxs+gb -GVheIgz1oZRtYL/MUa9UwptQ666gzcBXLQf5oCKzp73WIPVdp99r3y8P0b2X63Xz -Dhtp5dMfSlitOM7a3/Ddqt/iiSsq2caV5hbSElQsorxypbDv+roL851TRzqfHYSA -dZIgI0aWgVfsXDtByf8R7lfZWgwISsIKAomsSXIFihERKkzxWG1swCIs8WcqLcAM -6wHuhd0= +ADCCAgoCggIBAMUzOWyNYcpd7fCMtrBXA2YI4Uop1A0xfZtAg1Gopcg46WziPtrV +RlVtqyCzUome87CIJ6ggqLblS6SnrpWnw6YsNv3Hc9Zt3OonF6SrMF/ygglxMzXF +6cuoSevQVyWlgQobWd9N0gHzQGj95iF7p3f4+CdCK7R6tThG6rPg/QUlTSOW+ZBO +BjJsbGUMIbNDXm2UBUDqEGqNX+A4WiG3TSlos7uBJnBmDLRRqsuC1MF4FRXnE/Xo +rKsc3CE3k9DVijHQXlkhmAux33I8GtSrQj8yUmDPRwuUMFNlT6lQRj55pObTIz3p +3XvJlG3GB8VMwlW5P/y5GnjDdHKNjWBKKhGvYKUtbtkP3KlRLlbOOTqclwO6oytI +e7voZqKH6bN14vCmlNkUd3kcQGkV2rswtlL/Dmx4c/ZemURHNI8tOMiXtky/aBFt +tAk8EEb4qLGRvY5RK1GA+M3oRuIdwSJOYEkq7D345HJ3xoznCC/JXdfCtULHB08b +36UtDeIdx7WeHM5/xqW3aLavxM3QpsRswfONXeqDeBmXF/JB5br7Ls3TQpPFsELh +BKLBeibuWGBPPVln8RnVZw6I17a9Y9WAXLAYJh1n+fwnMR0qxnmQlptoILeNriCR +syLOtSP+o+5v0S2kxfMys4s3sBMntQawgkFABHn0iymMPwm7p91O85t1AgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAgEAXK7EjCpnqgDWCumXkE5fkymVgQ40gtj4OvMz +OVjW+HqNIU9+v12OqENxtPZJLxzU7hsGbUjNY6zuplFnaK86Z8GTMXbvLz2tXtN+ ++BIi6ZGzPrvFub5CLBpxzVBQdDCSDkR8t5D0LRl+N1ep+Ls3FyCOWRe5LF58tbCl +SLImwOFaOLRe3dALB8vvMKi/BP9zZUuxRgNIy4mdMUasr8ma9xkb19/FvOy9BMIJ +ei9g28+xgOJ14uI1zqrKIDpdLcgObYy1DpPatpzgs65JLnVxZH8H3r2DFd7m+XuI +mt0iZr7Cry7aYgdemOCf8jehq+dJkqvefQFUPbelbOZV7yLQS74CQXbLkhmTytP+ +zYqDIU6Gwo65rE59g53Aonqa+gOgGdu9cKfz54Sa4KqtsGZPtsASd4jgp8bcQ5sE +8Reg4fjWETyNVIDf2FHPEL4S3IbLRaRATcPzTvBYxP/CotqnoexXT2Vp6PSwYnVk +Lm1bd41oEhitlrzU7Zx/fOvJuL079Qy+2ixQiusEfRaYNICVHyaC7JieNk6P2G5Y +qyUx4beBOYm+Ux1R4kuyRGI7befJa1QyE0nxFQmJrHzOkrwJ722MMZSkhAyB5wKV +NKVMxMMSFpevo7wDRjlZve5V8R0l2lDc8TGrmRFDCSchvO0yLlnEg7TrdIRXAAuq +MDjdO8g= -----END CERTIFICATE REQUEST----- diff --git a/tests/certs/client.key.pem b/tests/certs/client.key.pem index aa63c1f5237..d6ad1d98b8f 100644 --- a/tests/certs/client.key.pem +++ b/tests/certs/client.key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -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 -g1WA14/4g+NcaVuglAM0na4pouro +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 +TRkcU50kVlx/0XcGTR0F9Cp2fhO8NHo= -----END PRIVATE KEY----- diff --git a/tests/certs/client.key.protected.pem b/tests/certs/client.key.protected.pem index e2b882cc99b..4fffb1fd88a 100644 --- a/tests/certs/client.key.protected.pem +++ b/tests/certs/client.key.protected.pem @@ -1,54 +1,54 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -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 -----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/certs/client_ca.cert.pem b/tests/certs/client_ca.cert.pem index becca16bea7..1278c0ec1b0 100644 --- a/tests/certs/client_ca.cert.pem +++ b/tests/certs/client_ca.cert.pem @@ -1,36 +1,36 @@ -----BEGIN CERTIFICATE----- -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 -z1r76p+mCg== +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKybpkWhPpMncwyrbUmPWyjYh0v42XS+ ++Mr+G45OAzt6H+FCnOKLTaHV3dOAAztmYcc/XjBJ51Tv/bRqn8ou2xhCAX5eky2i +8xBq04dYLz1iatT2Gvla0JpxozNxKEQhGN/AaC6BsGomSf05sjgrTI/b5tEZxzv3 +iqdyewPq9bYPiEjU7r7lQBOGUfmItpUQq1NQXhBO6ezovFAZ52iPxfyIdatl9CWY +bZWc/VgEIc3ojjsfspeXvkbDawQlIkJgBFD+O5BPDltF5bZG0MLcSU1fnWpwFlf5 +Cqf+H7F7dk001zD90Uq1evBFmYbQMD2IIeDP05kK5aeWcRX9ibbEydUaRKh6L1Ag +JrfmBoneoMcjxbdiK43b8lsPnkEeuvCwu5VN0upHLGJW7Fy60assL0hCvSMbmKTJ +qIHQd+sINifXq2vAjVZdUJompHrnypJxIrxdl+sTSYP/veZwvxuLdTUXQceiofEc +aR/y7OcLOIgywQkbqkrO/Cx1fcoPopr++dAEWQ2VhoSPuoC9f54dxgeMXj1/Kmxy +u4z89Xz6HfWqNDYbAEwSaWFOOWewUVfnsJ7gcptE2Arn+NlllGQN10DKdwHKUiKG +YfBhd3tdMlMkkNtowUGHhny32eoEnQv3Xqrd4rqe8cE58bKacM+OhhPkfWA2UT9w +bYA2mUVcg+izAgMBAAGjUzBRMB0GA1UdDgQWBBQyJOoeR8Y5TkgQ9vmRjskM3yS1 +qTAfBgNVHSMEGDAWgBQyJOoeR8Y5TkgQ9vmRjskM3yS1qTAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAmq6ca42dFmuRrIzMTKQgAAD70QC1dgGAh +N8miJlbTfzsqXKtiMezZCQmeBJHVb3ljYtf3HkddqxQG8YGHOYTbxjvpoMhUbQe7 +/r2iBtYtMcempJPpflT3GqNPZcESMQKJP7Cq8uZjSbHAE3kgXxH6/89J9DP2V0mF +fOs3pnenvsEQPnPub/QZ4XGRtUJH1+0ALlBfvULiG8Sm3pMS0wvOeNB7GkXwUl1V +ZvEhCTJUX1lrgYVmw/wnypAffABIXl1PIeXP2GVxANPjFhFgoSKJI1iAvMoCeCAQ +JLuH7Vxk1nA9BaQavwvWJdDlfdtDMTrMegM9U2dSHeqw1Ai8lyxsINnCRtmLSRui +7DztPojiUanBJSPNJPzcAjJvFJRJ/xafHL3uhK31cMo15yUskxPPJvSfVzDvRGBX +GQjN7BIcFVx9CZYwjlVkXjGXCbUs+LAo5ecN6MmQ/BU4AKJ8GHcH2VBQ34W7H1TU +ZlnQxlzus+eMlhAjkoEKQn7SLjHdZT0SqYBPKXDymj2/VtvTqJgwcrf2E0pKBDh3 +TlmL+OuSLjwmhoQo2YASw0wb1NKJuALvYUfULl7en+WQPpfYvcHdShqWe8cid3T9 +BSksp7qKiKm67n4okeOqhUJu68yJ1ffdAnZCoy8DQmTouIWSAf2/5Z4En9rfT8+a +nuI9Lyjq7Q== -----END CERTIFICATE----- diff --git a/tests/certs/client_ca.cert.srl b/tests/certs/client_ca.cert.srl index 5e2fab31bcb..58dc4271789 100644 --- a/tests/certs/client_ca.cert.srl +++ b/tests/certs/client_ca.cert.srl @@ -1 +1 @@ -3DF7A3F0843FE5B0AB8A1A96226AEADAF28F3ABD +3DF7A3F0843FE5B0AB8A1A96226AEADAF28F3ABE diff --git a/tests/certs/client_ca.key.pem b/tests/certs/client_ca.key.pem index ad3a15a3d2f..864e7f15cfb 100644 --- a/tests/certs/client_ca.key.pem +++ b/tests/certs/client_ca.key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -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 -JMzZVaMH/iiG4xZmm1vh3roPi8Sq +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 +grZhpKVB22PtQI9T68oHoj4yU7BC -----END PRIVATE KEY----- diff --git a/tests/certs/gen.sh b/tests/certs/gen.sh index ed53e24218a..1ee040f624c 100755 --- a/tests/certs/gen.sh +++ b/tests/certs/gen.sh @@ -35,7 +35,7 @@ openssl req -new -x509 -key client_ca.key.pem -out client_ca.cert.pem -days 7300 # Client cert openssl genrsa -out client.key.pem 4096 openssl req -new -key client.key.pem -out client.csr.pem -subj "/C=US/ST=California/L=San Francisco/O=EdgeDB Inc./OU=EdgeDB tests/CN=ssl_user/emailAddress=hello@edgedb.com" -batch -openssl x509 -req -in client.csr.pem -CA client_ca.cert.pem -CAkey client_ca.key.pem -CAcreateserial -out client.cert.pem -days 7300 +openssl x509 -req -in client.csr.pem -CA client_ca.cert.pem -CAkey client_ca.key.pem -CAcreateserial -out client.cert.pem -days 7300 -extensions v3_req -extfile ca.conf # Password protected client key openssl rsa -aes256 -in client.key.pem -out client.key.protected.pem -passout pass:secret1234 diff --git a/tests/certs/server.cert.pem b/tests/certs/server.cert.pem index 970908a810c..8f7361e4d50 100644 --- a/tests/certs/server.cert.pem +++ b/tests/certs/server.cert.pem @@ -1,36 +1,36 @@ -----BEGIN CERTIFICATE----- -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 -m+hGEYaP23LwDLBwv9//+jNASPYLfA== +c3QwHQYDVR0OBBYEFJfIRd55lBPNoJqNtVl+a7OOsIOvMB8GA1UdIwQYMBaAFBRx +NC1cmmAeggflm7LlZqIIZSTKMA0GCSqGSIb3DQEBCwUAA4ICAQBYe/i6zkxTwJXH +Zhv+0kAgdUIBiTKBImzwPwnwShbKbWNLloKtZ6aF+Spy1uO0w/NnCZ5S8M/E7HZr +ELnLJR+tU//sfmDb/y0UGNyJptj2z78R8k7yZCqA2MpA+t8nf9TT9cYu2wmCORt+ +jNZ0qYiRD4JichkVprkswF8gzZkhy8tM8TzO1HJu5TTZKQqcT4O1BCh9LkSgkhFD +OYqVqwdq/bo8tCnJzICl6ZdRObKUkQ3MrEyGde68suvG5mlfxylgknF9BUDvQKPA +eQISbIy+KuUSgD5q8f12qL6O1gPbnydYaFdS3aY8nTkfZCKUFcqBroyO57vK9rr3 +WdUZoT3eU011JrAXG7w8MBaoghTHor8mx3tjsNmnnJztngvm2nE2f34q261SA/uT +jfp1avecmzRFudUtT4NaAHw2aa6KqTZ4y/nHfoFozJxMtYVyuFGsIeToAAtZIpdb +zp8cDL7kW6MTIwFy7d8RAI+msDB5dUgT45iJUseAGklvnn/kibLzsbgfkeZXLxTg +l7oPm8ybtrM0RU/s3Pkknq5zoYp4PTQukcWWKO6rKBnu4UHDaF7ATtj9/7mQ/7rK +Muqt+iFhb3RMD7lj0ihZokvGZSe1AQxyS48llbLZoXOts20giTZGwVkvR125jF70 +bLtP4MYoBxHJxYGo4r0S6pYJ/nnTEA== -----END CERTIFICATE----- diff --git a/tests/certs/server.key.pem b/tests/certs/server.key.pem index 65bf666b930..c9a8a6abef2 100644 --- a/tests/certs/server.key.pem +++ b/tests/certs/server.key.pem @@ -1,52 +1,52 @@ -----BEGIN PRIVATE KEY----- -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 -VOSY1MQIxTgVpg1s9emO9LzO+FedN5k= +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 +OVaGdduWlABHp+A2Q9+iJdYrF5iN -----END PRIVATE KEY----- From 0676b5e3b58ff9d9ae72bdcc6edb731122e95f74 Mon Sep 17 00:00:00 2001 From: Elvis Pranskevichus Date: Fri, 31 Jan 2025 12:58:43 -0800 Subject: [PATCH 052/154] Make sure config spec-dependent functions are regened when grafting testmode (#8283) When adding remapping machinery in #8275 I forgot that `_testmode` config gets grafted onto bootstrap cache shipped in production builds. Rectify that. --- edb/pgsql/metaschema.py | 20 ++++++++++++++++++++ edb/server/bootstrap.py | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/edb/pgsql/metaschema.py b/edb/pgsql/metaschema.py index 0a696b43072..3152f6a1da8 100644 --- a/edb/pgsql/metaschema.py +++ b/edb/pgsql/metaschema.py @@ -8239,6 +8239,26 @@ async def generate_support_functions( return trampoline_functions(cmds) +async def regenerate_config_support_functions( + conn: PGConnection, + config_spec: edbconfig.Spec, +) -> None: + # Regenerate functions dependent on config spec. + commands = dbops.CommandGroup() + + funcs = [ + ApplySessionConfigFunction(config_spec), + PostgresJsonConfigValueToFrontendConfigValueFunction(config_spec), + ] + + cmds = [dbops.CreateFunction(func, or_replace=True) for func in funcs] + commands.add_commands(cmds) + + block = dbops.PLTopBlock() + commands.generate(block) + await _execute_block(conn, block) + + async def generate_more_support_functions( conn: PGConnection, compiler: edbcompiler.Compiler, diff --git a/edb/server/bootstrap.py b/edb/server/bootstrap.py index e7a526de6f4..7e33d70c66d 100644 --- a/edb/server/bootstrap.py +++ b/edb/server/bootstrap.py @@ -1781,8 +1781,10 @@ async def _init_stdlib( await conn.sql_execute(testmode_sql.encode("utf-8")) trampolines.extend(new_trampolines) # _testmode includes extra config settings, so make sure - # those are picked up. + # those are picked up... config_spec = config.load_spec_from_schema(stdlib.stdschema) + # ...and that config functions dependent on it are regenerated + await metaschema.regenerate_config_support_functions(conn, config_spec) logger.info('Finalizing database setup...') From a8c5ae09d59d79e4fd6edd31e51715bb60375d05 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Sat, 1 Feb 2025 08:50:14 -0800 Subject: [PATCH 053/154] Don't try to use a fixed port in test_server_config_default (#8272) We had a flake regarding this. (At least on my machine?), linux never randomly assigns an even numbered port, so probably the flake collided with something external specifically looking for it? The find_available_port mechanism is fundamentally racy, of course; the port could be allocated by someone else immediately, and in testing I did manage to observe cycle lengths as short as 2 before a reuse. Possibly these tests should just have a big hammer `@retry_failed_test(3)` decorator around them or something, but I am reluctant to introduce this because I fear we will use it instead of writing proper tests. (Most tests don't have as good an excuse for flakiness as needing to allocate a shared resource in a 16-bit namespace). --- edb/testbase/server.py | 21 ++++----------------- tests/test_server_config.py | 4 ++-- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/edb/testbase/server.py b/edb/testbase/server.py index c563684a284..354ca9abc99 100644 --- a/edb/testbase/server.py +++ b/edb/testbase/server.py @@ -3002,23 +3002,10 @@ def get_cases_by_shard(cases, selected_shard, total_shards, verbosity, stats): return _merge_results(cases) -def find_available_port(max_value=None) -> int: - if max_value is None: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.bind(("localhost", 0)) - return sock.getsockname()[1] - elif max_value > 1024: - port = max_value - while port > 1024: - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.bind(("localhost", port)) - return port - except IOError: - port -= 1 - raise RuntimeError("cannot find an available port") - else: - raise ValueError("max_value must be greater than 1024") +def find_available_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("localhost", 0)) + return sock.getsockname()[1] def _needs_factoring(weakly): diff --git a/tests/test_server_config.py b/tests/test_server_config.py index f59660f8fb9..96558d9016f 100644 --- a/tests/test_server_config.py +++ b/tests/test_server_config.py @@ -2366,7 +2366,7 @@ async def test_server_config_env_03(self): "cannot use CONFIGURE INSTANCE in multi-tenant mode", ) async def test_server_config_default(self): - p1 = tb.find_available_port(max_value=50000) + p1 = tb.find_available_port() async with tb.start_edgedb_server( extra_args=["--port", str(p1)] ) as sd: @@ -2378,7 +2378,7 @@ async def test_server_config_default(self): """), p1, ) - p2 = tb.find_available_port(p1 - 1) + p2 = tb.find_available_port() await conn.execute(f"""\ configure instance set listen_port := {p2} """) From 3bb14485d7e4d60b6911461882f379118b50e23a Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 4 Feb 2025 13:48:27 +0000 Subject: [PATCH 054/154] Add `edb::env-switcher`, `edb::split-section` and `edb::split-point` sphinx directives + add go domain (#8218) --- edb/tools/docs/__init__.py | 2 + edb/tools/docs/edb.py | 83 +++++++++++++++++++- edb/tools/docs/go.py | 155 +++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 edb/tools/docs/go.py diff --git a/edb/tools/docs/__init__.py b/edb/tools/docs/__init__.py index 332d4c85513..513ce9654e4 100644 --- a/edb/tools/docs/__init__.py +++ b/edb/tools/docs/__init__.py @@ -30,6 +30,7 @@ from . import js from . import sdl from . import graphql +from . import go from . import shared @@ -119,6 +120,7 @@ def setup(app): js.setup_domain(app) sdl.setup_domain(app) graphql.setup_domain(app) + go.setup_domain(app) app.add_directive('versionadded', VersionAdded, True) app.add_directive('versionchanged', VersionChanged, True) diff --git a/edb/tools/docs/edb.py b/edb/tools/docs/edb.py index ea42aa2e58a..0e84e34a267 100644 --- a/edb/tools/docs/edb.py +++ b/edb/tools/docs/edb.py @@ -24,6 +24,8 @@ from docutils.parsers import rst as d_rst from docutils.parsers.rst import directives as d_directives # type: ignore +from sphinx_code_tabs import TabsNode + class EDBYoutubeEmbed(d_rst.Directive): @@ -56,13 +58,92 @@ def run(self): return [node] +class EDBEnvironmentSwitcher(d_rst.Directive): + + has_content = False + optional_arguments = 0 + required_arguments = 0 + + def run(self): + node = d_nodes.container() + node['env-switcher'] = True + return [node] + + +class EDBSplitSection(d_rst.Directive): + + has_content = True + optional_arguments = 0 + required_arguments = 0 + + def run(self): + node = d_nodes.container() + node['split-section'] = True + self.state.nested_parse(self.content, self.content_offset, node) + + split_indexes = [ + index for index, child in enumerate(node.children) + if isinstance(child, d_nodes.container) and child.get('split-point') + ] + if len(split_indexes) > 1: + raise Exception( + f'cannot have multiple edb:split-point\'s in edb:split-section' + ) + blocks = ( + node.children[:split_indexes[0]] if + node.children[split_indexes[0]].get('code-above') + else node.children[split_indexes[0] + 1:] + ) if len(split_indexes) == 1 else [node.children[-1]] + if len(blocks) < 1: + raise Exception( + f'no content found at end of edb:split-section block, ' + f'or before/after the edb:split-point in the edb:split-section' + ) + for block in blocks: + if ( + not isinstance(block, d_nodes.literal_block) + and not isinstance(block, TabsNode) + and not isinstance(block, d_nodes.image) + and not isinstance(block, d_nodes.figure) + ): + raise Exception( + f'expected all content before/after the edb:split-point or ' + f'at the end of the edb:split-section to be either a ' + f'code block, code tabs, or image/figure' + ) + return [node] + + +class EDBSplitPoint(d_rst.Directive): + + has_content = False + optional_arguments = 1 + required_arguments = 0 + + def run(self): + node = d_nodes.container() + node['split-point'] = True + if len(self.arguments) > 0: + if self.arguments[0] not in ['above', 'below']: + raise Exception( + f"expected edb:split-point arg to be 'above', 'below' " + f"or empty (defaults to 'below')" + ) + if self.arguments[0] == 'above': + node['code-above'] = True + return [node] + + class GelDomain(s_domains.Domain): name = "edb" label = "Gel" directives = { 'collapsed': EDBCollapsed, - 'youtube-embed': EDBYoutubeEmbed + 'youtube-embed': EDBYoutubeEmbed, + 'env-switcher': EDBEnvironmentSwitcher, + 'split-section': EDBSplitSection, + 'split-point': EDBSplitPoint } diff --git a/edb/tools/docs/go.py b/edb/tools/docs/go.py new file mode 100644 index 00000000000..e080fe3ce7c --- /dev/null +++ b/edb/tools/docs/go.py @@ -0,0 +1,155 @@ +# +# This source file is part of the EdgeDB open source project. +# +# Copyright 2018-present MagicStack Inc. and the EdgeDB authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +from __future__ import annotations + +import re + +from typing import Any, Dict + +from sphinx import addnodes as s_nodes +from sphinx import directives as s_directives +from sphinx import domains as s_domains + + +class BaseGoDirective(s_directives.ObjectDescription): + + def get_signatures(self): + return [re.compile(r'\\\s*\n').sub('\n', self.arguments[0])] + + def add_target_and_index(self, name, sig, signode): + target = name.replace(' ', '-') + + if target in self.state.document.ids: + raise self.error( + f'duplicate {self.objtype} {name} description') + + signode['names'].append(target) + signode['ids'].append(target) + self.state.document.note_explicit_target(signode) + + objects = self.env.domaindata['go']['objects'] + + if target in objects: + raise self.error( + f'duplicate {self.objtype} {name} description') + objects[target] = (self.env.docname, self.objtype) + + +class GoTypeDirective(BaseGoDirective): + + def handle_signature(self, sig, signode): + name = re.split(r'\s+', sig)[1].strip() + + signode['name'] = name + signode['fullname'] = name + + signode['is_multiline'] = True + signode += [ + s_nodes.desc_signature_line(sig, line) + for line in sig.split('\n') + ] + + return name + + def add_target_and_index(self, name, sig, signode): + return super().add_target_and_index(name, sig, signode) + + +goFuncRegex = re.compile( + r"func\s+(?:\(.+?\s+\*?(?P.+?)\)\s+)?(?P.+?)\s*\(") + + +class GoFunctionDirective(BaseGoDirective): + + def handle_signature(self, sig, signode): + match = goFuncRegex.match(sig) + if match is None: + raise self.error(f'could not parse go func signature: {sig!r}') + + signode['fullname'] = fullname = ( + f"{match.group('receiver')}.{match.group('name')}" + if match.group('receiver') + else match.group('name') + ) + signode['name'] = match.group('name') + + signode['is_multiline'] = True + signode += [ + s_nodes.desc_signature_line(sig, line) + for line in sig.split('\n') + ] + + return fullname + + def add_target_and_index(self, name, sig, signode): + return super().add_target_and_index(name, sig, signode) + + +class GoMethodDirective(GoFunctionDirective): + pass + + +class GolangDomain(s_domains.Domain): + + name = "go" + label = "Golang" + + object_types = { + 'function': s_domains.ObjType('function'), + 'type': s_domains.ObjType('type'), + } + + directives = { + 'function': GoFunctionDirective, + 'type': GoTypeDirective, + 'method': GoMethodDirective, + } + + initial_data: Dict[str, Dict[str, Any]] = { + 'objects': {} # fullname -> docname, objtype + } + + def clear_doc(self, docname): + for fullname, (fn, _l) in list(self.data['objects'].items()): + if fn == docname: + del self.data['objects'][fullname] + + def merge_domaindata(self, docnames, otherdata): + for fullname, (fn, objtype) in otherdata['objects'].items(): + if fn in docnames: + self.data['objects'][fullname] = (fn, objtype) + + def get_objects(self): + for refname, (docname, type) in self.data['objects'].items(): + yield (refname, refname, type, docname, refname, 1) + + def get_full_qualified_name(self, node): + fn = node.get('fullname') + if not fn: + raise self.error('no fullname attribute') + return fn + + +def setup_domain(app): + app.add_domain(GolangDomain) + + +def setup(app): + setup_domain(app) From fe6e1983bb4ce443ed626f5808b11f5852342f9e Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 30 Jan 2025 21:51:10 +0000 Subject: [PATCH 055/154] Add index directives for keywords, symbols, sql equivalents, etc. in docs --- docs/datamodel/access_policies.rst | 10 ++++++++++ docs/datamodel/aliases.rst | 2 ++ docs/datamodel/annotations.rst | 4 ++++ docs/datamodel/computeds.rst | 2 ++ docs/datamodel/constraints.rst | 12 ++++++++++++ docs/datamodel/extensions.rst | 7 +++++++ docs/datamodel/functions.rst | 2 ++ docs/datamodel/future.rst | 2 ++ docs/datamodel/globals.rst | 4 ++++ docs/datamodel/indexes.rst | 10 ++++++++++ docs/datamodel/inheritance.rst | 7 +++++++ docs/datamodel/introspection/index.rst | 2 ++ docs/datamodel/links.rst | 22 +++++++++++++++++++++- docs/datamodel/mutation_rewrites.rst | 7 +++++++ docs/datamodel/objects.rst | 9 ++++++++- docs/datamodel/properties.rst | 18 +++++++++++++++++- docs/datamodel/triggers.rst | 5 +++++ docs/edgeql/analyze.rst | 2 +- docs/edgeql/delete.rst | 8 ++++++++ docs/edgeql/for.rst | 6 ++++++ docs/edgeql/group.rst | 3 +++ docs/edgeql/insert.rst | 16 ++++++++++++++++ docs/edgeql/literals.rst | 21 +++++++++++++++++++++ docs/edgeql/parameters.rst | 10 ++++++++++ docs/edgeql/path_resolution.rst | 2 ++ docs/edgeql/paths.rst | 5 +++++ docs/edgeql/select.rst | 26 +++++++++++++++++++++++++- docs/edgeql/sets.rst | 26 ++++++++++++++++++++++++++ docs/edgeql/transactions.rst | 3 +++ docs/edgeql/types.rst | 10 ++++++++++ docs/edgeql/update.rst | 10 ++++++++++ docs/edgeql/with.rst | 7 +++++++ docs/stdlib/array.rst | 6 ++++++ docs/stdlib/bool.rst | 6 ++++++ docs/stdlib/bytes.rst | 8 +++++++- docs/stdlib/cfg.rst | 14 ++++++++++++++ docs/stdlib/datetime.rst | 4 ++++ docs/stdlib/generic.rst | 16 ++++++++++++++++ docs/stdlib/json.rst | 8 ++++++++ docs/stdlib/numbers.rst | 16 ++++++++-------- docs/stdlib/range.rst | 14 +++++++++++--- docs/stdlib/set.rst | 22 +++++++++++++++++++--- docs/stdlib/string.rst | 10 ++++++++++ docs/stdlib/type.rst | 10 +++++++--- 44 files changed, 391 insertions(+), 23 deletions(-) diff --git a/docs/datamodel/access_policies.rst b/docs/datamodel/access_policies.rst index 2e155ca645d..8ba5e584146 100644 --- a/docs/datamodel/access_policies.rst +++ b/docs/datamodel/access_policies.rst @@ -6,6 +6,9 @@ Access Policies =============== +.. index:: access policy, object-level security, row-level security, RLS, + allow, deny, using + Object types can contain security policies that restrict the set of objects that can be selected, inserted, updated, or deleted by a particular query. This is known as *object-level security* and it is similar in function to SQL's @@ -378,6 +381,9 @@ is visible to a particular query. Policy types ^^^^^^^^^^^^ +.. index:: accesss policy, select, insert, delete, update, update read, + update write, all + For the most part, the policy types correspond to EdgeQL's *statement types*: - ``select``: Applies to all queries; objects without a ``select`` permission @@ -523,6 +529,8 @@ Custom error messages .. versionadded:: 3.0 +.. index:: access policy, errmessage, using + When you run a query that attempts a write and is restricted by an access policy, you will get a generic error message. @@ -583,6 +591,8 @@ will receive this error: Disabling policies ^^^^^^^^^^^^^^^^^^ +.. index:: apply_access_policies + You may disable all access policies by setting the ``apply_access_policies`` :ref:`configuration parameter ` to ``false``. diff --git a/docs/datamodel/aliases.rst b/docs/datamodel/aliases.rst index d48c4f309d1..dc577b5ade3 100644 --- a/docs/datamodel/aliases.rst +++ b/docs/datamodel/aliases.rst @@ -4,6 +4,8 @@ Aliases ======= +.. index:: alias, virtual type + .. important:: This section assumes a basic understanding of EdgeQL. If you aren't familiar diff --git a/docs/datamodel/annotations.rst b/docs/datamodel/annotations.rst index 558fcb6c3f4..dd67a3f2284 100644 --- a/docs/datamodel/annotations.rst +++ b/docs/datamodel/annotations.rst @@ -4,6 +4,8 @@ Annotations =========== +.. index:: annotation, title, description, deprecated + *Annotations* are named values associated with schema items and are designed to hold arbitrary schema-level metadata represented as a :eql:type:`str`. @@ -52,6 +54,8 @@ should be used instead. User-defined annotations ------------------------ +.. index:: abstract annotation + To declare a custom annotation type beyond the three built-ins, add an abstract annotation type to your schema. A custom annotation could be used to attach arbitrary JSON-encoded data to your schema—potentially useful for introspection diff --git a/docs/datamodel/computeds.rst b/docs/datamodel/computeds.rst index bf1c77b2368..ae547da2dd0 100644 --- a/docs/datamodel/computeds.rst +++ b/docs/datamodel/computeds.rst @@ -6,6 +6,8 @@ Computeds :edb-alt-title: Computed properties and links +.. index:: computeds, :=, __source__, backlinks + .. important:: This section assumes a basic understanding of EdgeQL. If you aren't familiar diff --git a/docs/datamodel/constraints.rst b/docs/datamodel/constraints.rst index ea4917707f0..edda31b3bb6 100644 --- a/docs/datamodel/constraints.rst +++ b/docs/datamodel/constraints.rst @@ -4,6 +4,10 @@ Constraints =========== +.. index:: constraint, validation, exclusive, expression on, one_of, max_value, + max_ex_value, min_value, min_ex_value, max_len_value, min_len_value, + regexp, __subject__ + .. important:: This section assumes a basic understanding of EdgeQL. @@ -202,6 +206,8 @@ Constraints can be defined on computed properties. Composite constraints ^^^^^^^^^^^^^^^^^^^^^ +.. index:: tuple + To define a composite constraint, create an ``exclusive`` constraint on a tuple of properties or links. @@ -239,6 +245,8 @@ Partial constraints .. versionadded:: 2.0 +.. index:: constraint exclusive on, except + Constraints on object types can be made partial, so that they don't apply when some condition holds. @@ -333,6 +341,8 @@ Link ``@source`` and ``@target`` constraints .. versionadded:: 4.0 +.. index:: constraint exclusive on, @source, @target + .. note:: ``@source`` and ``@target`` are available starting with version 4.3. @@ -412,6 +422,8 @@ using arbitrary EdgeQL expressions. The example below uses the built-in Constraints and type inheritence -------------------------------- +.. index:: delegated constraint + If you define a constraint on a type and then extend that type, the constraint will *not* be applied individually to each extending type. Instead, it will apply globally across all the types that inherited the constraint. diff --git a/docs/datamodel/extensions.rst b/docs/datamodel/extensions.rst index d76174c4961..b2b96124017 100644 --- a/docs/datamodel/extensions.rst +++ b/docs/datamodel/extensions.rst @@ -4,6 +4,8 @@ Extensions ========== +.. index:: using extension + Extensions are the way EdgeDB adds more functionality. In principle, extensions could add new types, scalars, functions, etc., but, more importantly, they can add new ways of interacting with the database. @@ -11,6 +13,9 @@ importantly, they can add new ways of interacting with the database. Built-in extensions ------------------- +.. index:: edgeql_http, graphql, auth, ai, pg_trgm, pg_unaccent, pgcrypto, + pgvector + There are a few built-in extensions available: - ``edgeql_http``: enables :ref:`EdgeQL over HTTP `, @@ -43,6 +48,8 @@ To enable these extensions, add a ``using`` statement at the top level of your s Standalone extensions --------------------- +.. index:: postgis + Additionally, standalone extension packages can be installed via the CLI. List installed extensions: diff --git a/docs/datamodel/functions.rst b/docs/datamodel/functions.rst index 472641e0f49..bba6de34d37 100644 --- a/docs/datamodel/functions.rst +++ b/docs/datamodel/functions.rst @@ -4,6 +4,8 @@ Functions ========= +.. index:: function, using + .. note:: This page documents how to define custom functions, however EdgeDB provides a diff --git a/docs/datamodel/future.rst b/docs/datamodel/future.rst index 63af1d91561..81089f8d945 100644 --- a/docs/datamodel/future.rst +++ b/docs/datamodel/future.rst @@ -4,6 +4,8 @@ Future Behavior =============== +.. index:: future, nonrecursive_access_policies + Any time that we add new functionality to EdgeDB we strive to do it in the least disruptive way possible. Deprecation warnings, documentation and guides can help make these transitions smoother, but sometimes the changes are just diff --git a/docs/datamodel/globals.rst b/docs/datamodel/globals.rst index a6bec677daf..7159595eef6 100644 --- a/docs/datamodel/globals.rst +++ b/docs/datamodel/globals.rst @@ -6,6 +6,8 @@ Globals ======= +.. index:: global, required global + Schemas can contain scalar-typed *global variables*. .. code-block:: sdl @@ -151,6 +153,8 @@ default value. Computed globals ---------------- +.. index:: global, := + Global variables can also be computed. The value of computed globals are dynamically computed when they are referenced in queries. diff --git a/docs/datamodel/indexes.rst b/docs/datamodel/indexes.rst index afdb9a378ff..96516026116 100644 --- a/docs/datamodel/indexes.rst +++ b/docs/datamodel/indexes.rst @@ -4,6 +4,8 @@ Indexes ======= +.. index:: index on, performance, postgres query planner + An index is a data structure used internally to speed up filtering, ordering, and grouping operations. Indexes help accomplish this in two key ways: @@ -100,6 +102,8 @@ references multiple properties of the enclosing object type. Index on multiple properties ---------------------------- +.. index:: tuple + A *composite index* is an index that references multiple properties. This can speed up queries that filter, order, or group on both properties. @@ -137,6 +141,8 @@ In EdgeDB, this index is created by indexing on a ``tuple`` of properties. Index on a link property ------------------------ +.. index:: __subject__, linkprops + Link properties can also be indexed. .. code-block:: sdl @@ -168,6 +174,8 @@ Link properties can also be indexed. Specify a Postgres index type ----------------------------- +.. index:: pg::hash, pg::btree, pg::gin, pg::gist, pg::spgist, pg::brin + .. versionadded:: 3.0 EdgeDB exposes Postgres indexes that you can use in your schemas. These are @@ -201,6 +209,8 @@ You can use them like this: Annotate an index ----------------- +.. index:: annotation + Indexes can be augmented with annotations. .. code-block:: sdl diff --git a/docs/datamodel/inheritance.rst b/docs/datamodel/inheritance.rst index 821a77a2a8e..d30b1d48c4f 100644 --- a/docs/datamodel/inheritance.rst +++ b/docs/datamodel/inheritance.rst @@ -4,6 +4,9 @@ Inheritance =========== +.. index:: abstract, extending, extends, subtype, supertype, parent type, + child type + Inheritance is a crucial aspect of schema modeling in EdgeDB. Schema items can *extend* one or more parent types. When extending, the child (subclass) inherits the definition of its parents (superclass). @@ -94,6 +97,8 @@ Polymorphic queries `. Multiple Inheritance ^^^^^^^^^^^^^^^^^^^^ +.. index:: Multiple Inheritance + Object types can :ref:`extend more than one type ` — that's called *multiple inheritance*. This mechanism allows building complex object @@ -136,6 +141,8 @@ types out of combinations of more basic types. Overloading ^^^^^^^^^^^ +.. index:: overloaded + An object type can overload an inherited property or link. All overloaded declarations must be prefixed with the ``overloaded`` prefix to avoid unintentional overloads. diff --git a/docs/datamodel/introspection/index.rst b/docs/datamodel/introspection/index.rst index e0ef0c69b8e..bec1d7405e8 100644 --- a/docs/datamodel/introspection/index.rst +++ b/docs/datamodel/introspection/index.rst @@ -3,6 +3,8 @@ Introspection ============= +.. index:: describe, introspect, typeof, schema module + All of the schema information in EdgeDB is stored in the ``schema`` :ref:`module ` and is accessible via *introspection queries*. diff --git a/docs/datamodel/links.rst b/docs/datamodel/links.rst index 81cffa6c96a..48f52b1f68f 100644 --- a/docs/datamodel/links.rst +++ b/docs/datamodel/links.rst @@ -4,7 +4,7 @@ Links ===== -:index: link one-to-one one-to-many many-to-one many-to-many +.. index:: link, relation, source, target, backlinks, foreign key Links define a specific relationship between two :ref:`object types `. @@ -31,6 +31,8 @@ declared) and a *target* (the type they point to). Link cardinality ---------------- +.. index:: single, multi + All links have a cardinality: either ``single`` or ``multi``. The default is ``single`` (a "to-one" link). Use the ``multi`` keyword to declare a "to-many" link. @@ -76,6 +78,8 @@ declare a "to-one" backlink. Required links -------------- +.. index:: required, optional, not null + All links are either ``optional`` or ``required``; the default is ``optional``. Use the ``required`` keyword to declare a required link. A required link must point to *at least one* target instance, and if the cardinality of the required @@ -125,6 +129,8 @@ Attempting to create a ``GroupChat`` with no members would fail. Exclusive constraints --------------------- +.. index:: constraint exclusive + You can add an ``exclusive`` constraint to a link to guarantee that no other instances can link to the same target(s). @@ -163,6 +169,9 @@ objects. Modeling relations ------------------ +.. index:: cardinality, one-to-one, one-to-many, many-to-one, many-to-many, + link table, association table + By combinining *link cardinality* and *exclusivity constraints*, we can model every kind of relationship: one-to-one, one-to-many, many-to-one, and many-to-many. @@ -534,6 +543,8 @@ the link's shape: Default values -------------- +.. index:: default + Like properties, links can declare a default value in the form of an EdgeQL expression, which will be executed upon insertion. In the example below, new people are automatically assigned three random friends. @@ -562,6 +573,8 @@ people are automatically assigned three random friends. Link properties --------------- +.. index:: linkprops, metadata, link table + Like object types, links in EdgeDB can contain **properties**. Link properties can be used to store metadata about links, such as *when* they were created or the *nature/strength* of the relationship. @@ -786,6 +799,9 @@ a shape on the link. Deletion policies ----------------- +.. index:: on target delete, on source delete, restrict, delete source, allow, + deferred restrict, delete target, if orphan + Links can declare their own **deletion policy**. There are two kinds of events that might trigger these policies: *target deletion* and *source deletion*. @@ -985,6 +1001,8 @@ deletion policy. Polymorphic links ----------------- +.. index:: abstract, subtypes + Links can have ``abstract`` targets, in which case the link is considered **polymorphic**. Consider the following schema: @@ -1045,6 +1063,8 @@ docs ` Abstract links -------------- +.. index:: abstract + It's possible to define ``abstract`` links that aren't tied to a particular *source* or *target*. If you're declaring several links with the same set of properties, annotations, constraints, or indexes, abstract links can be used diff --git a/docs/datamodel/mutation_rewrites.rst b/docs/datamodel/mutation_rewrites.rst index b9a6ff6229a..7cdd815d0bf 100644 --- a/docs/datamodel/mutation_rewrites.rst +++ b/docs/datamodel/mutation_rewrites.rst @@ -6,6 +6,9 @@ Mutation rewrites ================= +.. index:: rewrite, insert, update, using, __subject__, __specified__, __old__, + modify, modification + .. edb:youtube-embed:: ImgMfb_jCJQ?end=41 Mutation rewrites allow you to intercept database mutations (i.e., @@ -113,6 +116,8 @@ while updates will set the ``modified`` property: Available variables =================== +.. index:: rewrite, __subject__, __specified__, __old__ + Inside the rewrite rule's expression, you have access to a few special values: * ``__subject__`` refers to the object type with the new property and link @@ -286,6 +291,8 @@ containing the new author value. Mutation rewrite as cached computed =================================== +..index:: cached computeds, caching computeds + Mutation rewrites can be used to effectively create a cached computed value as demonstrated with the ``byline`` property in this schema: diff --git a/docs/datamodel/objects.rst b/docs/datamodel/objects.rst index c017733b220..d84d17b03db 100644 --- a/docs/datamodel/objects.rst +++ b/docs/datamodel/objects.rst @@ -4,6 +4,8 @@ Object Types ============ +.. index:: type, tables, models + *Object types* are the primary components of an EdgeDB schema. They are analogous to SQL *tables* or ORM *models*, and consist of :ref:`properties ` and :ref:`links `. @@ -45,15 +47,18 @@ documentation on links, see :ref:`Links `. IDs --- +.. index:: uuid, primary key + There's no need to manually declare a primary key on your object types. All object types automatically contain a property ``id`` of type ``UUID`` that's *required*, *globally unique*, and *readonly*. This ``id`` is assigned upon creation and never changes. - Abstract types -------------- +.. index:: abstract, inheritance + Object types can either be *abstract* or *non-abstract*. By default all object types are non-abstract. You can't create or store instances of abstract types, but they're a useful way to share functionality and structure among @@ -81,6 +86,8 @@ Abstract types are commonly used in tandem with inheritance. Inheritance ----------- +.. index:: extending, extends, subtypes, supertypes + Object types can *extend* other object types. The extending type (AKA the *subtype*) inherits all links, properties, indexes, constraints, etc. from its *supertypes*. diff --git a/docs/datamodel/properties.rst b/docs/datamodel/properties.rst index 4f1a6cc6a97..c3935542b1b 100644 --- a/docs/datamodel/properties.rst +++ b/docs/datamodel/properties.rst @@ -4,7 +4,7 @@ Properties ========== -:index: property +.. index:: property, primitive types, fields, columns Properties are used to associate primitive data with an :ref:`object type ` or :ref:`link `. @@ -37,6 +37,8 @@ encompasses :ref:`scalar types ` like ``str`` and Required properties ------------------- +.. index:: required, optional, not null + Properties can be either ``optional`` (the default) or ``required``. .. code-block:: sdl @@ -57,6 +59,8 @@ Properties can be either ``optional`` (the default) or ``required``. Property cardinality -------------------- +.. index:: cardinality, single, multi + Properties have a **cardinality**, either ``single`` (the default) or ``multi``. A ``multi`` property of type ``str`` points to an *unordered set* of strings. @@ -105,6 +109,8 @@ more involved discussion, see :ref:`EdgeQL > Sets Default values -------------- +.. index:: default + Properties can have a default value. This default can be a static value or an arbitrary EdgeQL expression, which will be evaluated upon insertion. @@ -136,6 +142,8 @@ arbitrary EdgeQL expression, which will be evaluated upon insertion. Readonly properties ------------------- +.. index:: readonly, immutable + Properties can be marked as ``readonly``. In the example below, the ``User.external_id`` property can be set at the time of creation but not modified thereafter. @@ -160,6 +168,8 @@ modified thereafter. Constraints ----------- +.. index:: constraint + Properties can be augmented wth constraints. The example below showcases a subset of EdgeDB's built-in constraints. @@ -240,6 +250,8 @@ reference `. Annotations ----------- +.. index:: annotation, metadata, title, description, deprecated + Properties can contain annotations, small human-readable notes. The built-in annotations are ``title``, ``description``, and ``deprecated``. You may also declare :ref:`custom annotation types `. @@ -269,6 +281,8 @@ declare :ref:`custom annotation types `. Abstract properties ------------------- +.. index:: abstract property + Properties can be *concrete* (the default) or *abstract*. Abstract properties are declared independent of a source or target, can contain :ref:`annotations `, and can be marked as ``readonly``. @@ -304,6 +318,8 @@ are declared independent of a source or target, can contain :ref:`annotations Link properties --------------- +.. index:: linkprops, relations, link table + Properties can also be defined on **links**. For a full guide, refer to :ref:`Guides > Using link properties `. diff --git a/docs/datamodel/triggers.rst b/docs/datamodel/triggers.rst index 9e8116cb9bf..c2186643e83 100644 --- a/docs/datamodel/triggers.rst +++ b/docs/datamodel/triggers.rst @@ -6,6 +6,9 @@ Triggers ======== +.. index:: trigger, after insert, after update, after delete, for each, for all, + when, do, __new__, __old__ + .. edb:youtube-embed:: ImgMfb_jCJQ?start=41 Triggers allow you to define an expression to be executed whenever a given @@ -297,6 +300,8 @@ object instead of one ``Log`` object per row: Validation using triggers ========================= +.. index:: trigger, validate, assert + Triggers may also be used for validation by calling :eql:func:`assert` inside the trigger. In this example, the ``Person`` type has two multi links to other ``Person`` objects named ``friends`` and ``enemies``. These two links should be diff --git a/docs/edgeql/analyze.rst b/docs/edgeql/analyze.rst index 09311e4b016..c41472cb5e6 100644 --- a/docs/edgeql/analyze.rst +++ b/docs/edgeql/analyze.rst @@ -5,7 +5,7 @@ Analyze ======= -:index: performance +.. index:: analyze, explain, performance, postgres query planner Prefix an EdgeQL query with ``analyze`` to run a performance analysis of that query. diff --git a/docs/edgeql/delete.rst b/docs/edgeql/delete.rst index 97c28190ea4..79f7b80e684 100644 --- a/docs/edgeql/delete.rst +++ b/docs/edgeql/delete.rst @@ -3,6 +3,8 @@ Delete ====== +.. index:: delete + The ``delete`` command is used to delete objects from the database. .. code-block:: edgeql @@ -28,6 +30,8 @@ on these clauses. Link deletion ------------- +.. index:: ConstraintViolationError + Every link is associated with a *link deletion policy*. By default, it isn't possible to delete an object linked to by another. @@ -83,6 +87,8 @@ the ``allow`` deletion policy. Cascading deletes ^^^^^^^^^^^^^^^^^ +.. index:: delete cascade, delete source, delete target, deletion policy + If a link uses the ``delete source`` policy, then deleting a *target* of the link will also delete the object that links to it (the *source*). This behavior can be used to implement cascading deletes; be careful with this power! @@ -93,6 +99,8 @@ The full list of deletion policies is documented at :ref:`Schema > Links Return value ------------ +.. index:: delete, returning + A ``delete`` statement returns the set of deleted objects. You can pass this set into ``select`` to fetch properties and links of the (now-deleted) objects. This is the last moment this data will be available before being diff --git a/docs/edgeql/for.rst b/docs/edgeql/for.rst index dcbf542b978..62167e6bbbf 100644 --- a/docs/edgeql/for.rst +++ b/docs/edgeql/for.rst @@ -3,6 +3,8 @@ For === +.. index:: for in, union + EdgeQL supports a top-level ``for`` statement. These "for loops" iterate over each element of some input set, execute some expression with it, and merge the results into a single output set. @@ -42,6 +44,8 @@ are merged into a single output set. Bulk inserts ------------ +.. index:: bulk inserts + The ``for`` statement is commonly used for bulk inserts. .. code-block:: edgeql-repl @@ -87,6 +91,8 @@ A similar approach can be used for bulk updates. Conditional DML --------------- +.. index:: for, if else, unless conflict + .. versionadded:: 4.0 DML is now supported in ``if..else``. The method of achieving conditional diff --git a/docs/edgeql/group.rst b/docs/edgeql/group.rst index aa53c885c5e..97a327aa824 100644 --- a/docs/edgeql/group.rst +++ b/docs/edgeql/group.rst @@ -5,6 +5,9 @@ Group ===== +.. index:: group by, group using by, key, grouping, elements, analytics, + aggregate, rollup, cube, partition + EdgeQL supports a top-level ``group`` statement. This is used to partition sets into subsets based on some parameters. These subsets then can be additionally aggregated to provide some analytics. diff --git a/docs/edgeql/insert.rst b/docs/edgeql/insert.rst index 70f7f01bfc8..72103287934 100644 --- a/docs/edgeql/insert.rst +++ b/docs/edgeql/insert.rst @@ -3,6 +3,8 @@ Insert ====== +.. index:: insert, returning + The ``insert`` command is used to create instances of object types. The code samples on this page assume the following schema: @@ -160,6 +162,8 @@ You can use :ref:`ref_eql_with` to tidy this up if you prefer: Inserting links --------------- +.. index:: inserting links + EdgeQL's composable syntax makes link insertion painless. Below, we insert "Spider-Man: No Way Home" and include all known heroes and villains as ``characters`` (which is basically true). @@ -218,6 +222,8 @@ returned, this query will fail at runtime. Nested inserts -------------- +.. index:: nested inserts + Just as we used subqueries to populate links with existing objects, we can also execute *nested inserts*. @@ -274,6 +280,8 @@ actually exist in the database. With block ---------- +.. index:: with insert + In the previous query, we selected Black Widow twice: once in the ``characters`` set and again as the ``nemesis`` of Dreykov. In circumstances like this, pulling a subquery into a ``with`` block lets you avoid @@ -319,6 +327,8 @@ can reference earlier ones. Conflicts --------- +.. index:: unless conflict on, else + EdgeDB provides a general-purpose mechanism for gracefully handling possible exclusivity constraint violations. Consider a scenario where we are trying to ``insert`` Eternals (the ``Movie``), but we can't remember if it already exists @@ -356,6 +366,8 @@ conflicts and provide a fallback expression. Upserts ^^^^^^^ +.. index:: upserts, unless conflict on, else update + There are no limitations on what the ``else`` clause can contain; it can be any EdgeQL expression, including an :ref:`update ` statement. This lets you express *upsert* logic in a single EdgeQL query. @@ -458,6 +470,8 @@ tutorial `_. Suppressing failures ^^^^^^^^^^^^^^^^^^^^ +.. index:: unless conflict + The ``else`` clause is optional; when omitted, the ``insert`` statement will return an *empty set* if a conflict occurs. This is a common way to prevent ``insert`` queries from failing on constraint violations. @@ -476,6 +490,8 @@ return an *empty set* if a conflict occurs. This is a common way to prevent Bulk inserts ------------ +.. index:: bulk inserts + Bulk inserts are performed by passing in a JSON array as a :ref:`query parameter `, :eql:func:`unpacking ` it, and using a :ref:`for loop ` to insert the objects. diff --git a/docs/edgeql/literals.rst b/docs/edgeql/literals.rst index 3d8c50075ad..9163d5f9ced 100644 --- a/docs/edgeql/literals.rst +++ b/docs/edgeql/literals.rst @@ -3,6 +3,8 @@ Literals ======== +.. index:: primitive types + EdgeQL is *inextricably tied* to EdgeDB's rigorous type system. Below is an overview of how to declare a literal value of each *primitive type*. Click a link in the left column to jump to the associated section. @@ -55,6 +57,8 @@ link in the left column to jump to the associated section. Strings ------- +.. index:: str, unicode, quotes, raw strings, escape character + The :eql:type:`str` type is a variable-length string of Unicode characters. A string can be declared with either single or double quotes. @@ -146,6 +150,8 @@ For a complete reference on strings, see :ref:`Standard Library > String Booleans -------- +.. index:: bool + The :eql:type:`bool` type represents a true/false value. .. code-block:: edgeql-repl @@ -296,6 +302,8 @@ Generate a random UUID. Enums ----- +.. index:: enums + Enum types must be :ref:`declared in your schema `. .. code-block:: sdl @@ -318,6 +326,8 @@ casting an appropriate string literal: Dates and times --------------- +.. index:: temporal + EdgeDB's typesystem contains several temporal types. .. list-table:: @@ -483,6 +493,9 @@ Ranges .. versionadded:: 2.0 +.. index:: ranges, lower bound, upper bound, inclusive, inc_lower, inc_upper, + empty + Ranges represent a range of orderable scalar values. A range comprises a lower bound, upper bound, and two boolean flags indicating whether each bound is inclusive. @@ -533,6 +546,8 @@ ranges cannot be unpacked. Bytes ----- +.. index:: binary, raw byte strings + The ``bytes`` type represents raw binary data. .. code-block:: edgeql-repl @@ -557,6 +572,8 @@ character. Arrays ------ +.. index:: collection, lists, ordered + An array is an *ordered* collection of values of the *same type*. For example: .. code-block:: edgeql-repl @@ -597,6 +614,8 @@ reference on array data types. Tuples ------ +.. index:: fixed length ordered collection, named tuples + A tuple is *fixed-length*, *ordered* collection of values, each of which may have a *different type*. The elements of a tuple can be of any type, including scalars, arrays, other tuples, and object types. @@ -647,6 +666,8 @@ For a full reference on tuples, see :ref:`Standard Library > Tuple JSON ---- +.. index:: json + The :eql:type:`json` scalar type is a stringified representation of structured data. JSON literals are declared by explicitly casting other values or passing a properly formatted JSON string into :eql:func:`to_json`. Any type can be diff --git a/docs/edgeql/parameters.rst b/docs/edgeql/parameters.rst index f799056944f..9798356e5f9 100644 --- a/docs/edgeql/parameters.rst +++ b/docs/edgeql/parameters.rst @@ -3,6 +3,8 @@ Parameters ========== +.. index:: query params, query arguments, query args, $, < >$, input + :edb-alt-title: Query Parameters EdgeQL queries can reference parameters with ``$`` notation. The value of these @@ -96,6 +98,8 @@ language-native types. Parameter types and JSON ------------------------ +.. index:: complex parameters + Prior to EdgeDB 3.0, parameters can be only :ref:`scalars ` or arrays of scalars. In EdgeDB 3.0, parameters can also be tuples. If you need to pass complex structures as parameters, use @@ -128,6 +132,8 @@ properties. Optional parameters ------------------- +.. index:: $ + By default, query parameters are ``required``; the query will fail if the parameter value is an empty set. You can use an ``optional`` modifier inside the type cast if the parameter is optional. @@ -150,6 +156,8 @@ the type cast if the parameter is optional. Default parameter values ------------------------ +.. index:: ?? + When using optional parameters, you may want to provide a default value to use in case the parameter is not passed. You can do this by using the :eql:op:`?? (coalesce) ` operator. @@ -167,6 +175,8 @@ in case the parameter is not passed. You can do this by using the What can be parameterized? -------------------------- +.. index:: order by parameters + Any data manipulation language (DML) statement can be parameterized: ``select``, ``insert``, ``update``, and ``delete``. Since parameters can only be scalars, arrays of scalars, and, as of EdgeDB 3.0, diff --git a/docs/edgeql/path_resolution.rst b/docs/edgeql/path_resolution.rst index 5b447ffcfb4..8917d1f6a20 100644 --- a/docs/edgeql/path_resolution.rst +++ b/docs/edgeql/path_resolution.rst @@ -4,6 +4,8 @@ Path scoping ============ +.. index:: using future simple_scoping, using future warn_old_scoping + Beginning with EdgeDB 6.0, we are phasing out our historical (and somewhat notorious) :ref:`"path scoping" algorithm ` diff --git a/docs/edgeql/paths.rst b/docs/edgeql/paths.rst index b1e6fcb8bda..d73e05c4d9c 100644 --- a/docs/edgeql/paths.rst +++ b/docs/edgeql/paths.rst @@ -4,6 +4,7 @@ Paths ===== +.. index:: links, relations A *path expression* (or simply a *path*) represents a set of values that are reachable by traversing a given sequence of links or properties from some @@ -129,6 +130,8 @@ Paths can terminate with a property reference. Backlinks --------- +.. index:: .< + All examples thus far have traversed links in the *forward direction*, however it's also possible to traverse links *backwards* with ``.<`` notation. These are called **backlinks**. @@ -264,6 +267,8 @@ that the type name (in this case ``User``) doesn't need to be specified. Link properties --------------- +.. index:: linkprops, @ + Paths can also reference :ref:`link properties ` with ``@`` notation. To demonstrate this, let's add a property to the ``User. friends`` link: diff --git a/docs/edgeql/select.rst b/docs/edgeql/select.rst index 8ebee596ac8..855a44a305e 100644 --- a/docs/edgeql/select.rst +++ b/docs/edgeql/select.rst @@ -3,6 +3,7 @@ Select ====== +.. index:: select The ``select`` command retrieves or computes a set of values. We've already seen simple queries that select primitive values. @@ -214,6 +215,8 @@ tutorial `_. Shapes ------ +.. index:: select, shapes, { } + To specify which properties to select, we attach a **shape** to ``Villain``. A shape can be attached to any object type expression in EdgeQL. @@ -234,6 +237,8 @@ tutorial `_. Nested shapes ^^^^^^^^^^^^^ +.. index:: select, nested shapes + Nested shapes can be used to fetch linked objects and their properties. Here we fetch all ``Villain`` objects and their nemeses. @@ -281,6 +286,8 @@ Splats .. versionadded:: 3.0 +.. index:: select, splats, *, **, select *, select all, [is ].*, [is ].** + Splats allow you to select all properties of a type using the asterisk (``*``) or all properties of the type and a single level of linked types with a double asterisk (``**``). @@ -530,6 +537,8 @@ we wouldn't get those with this query either. Filtering --------- +.. index:: select, filter, where + To filter the set of selected objects, use a ``filter `` clause. The ```` that follows the ``filter`` keyword can be *any boolean expression*. @@ -676,6 +685,8 @@ traversing a backlink would look like this: Ordering -------- +.. index:: order by, sorting, asc, desc, then, empty first, empty last + Order the result of a query with an ``order by`` clause. .. code-block:: edgeql-repl @@ -735,6 +746,8 @@ are handled, see :ref:`Reference > Commands > Select Pagination ---------- +.. index:: limit, offset + EdgeDB supports ``limit`` and ``offset`` clauses. These are typically used in conjunction with ``order by`` to maintain a consistent ordering across pagination queries. @@ -798,6 +811,8 @@ providing one or the other. Computed fields --------------- +.. index:: computeds, := + Shapes can contain *computed fields*. These are EdgeQL expressions that are computed on the fly during the execution of the query. As with other clauses, we can use :ref:`leading dot notation ` (e.g. ``.name``) to @@ -851,6 +866,8 @@ As with nested filters, the *current scope* changes inside nested shapes. Backlinks --------- +.. index:: .< + Fetching backlinks is a common use case for computed fields. To demonstrate this, let's fetch a list of all movies starring a particular Hero. @@ -931,6 +948,9 @@ shapes just like a non-computed link. Subqueries ---------- +.. index:: nested queries, composition, composing queries, composable, + embedded queries, embedding queries + There's no limit to the complexity of computed expressions. EdgeQL is designed to be fully composable; entire queries can be embedded inside each other. Below, we use a subquery to select all movies containing a villain's nemesis. @@ -961,7 +981,7 @@ Below, we use a subquery to select all movies containing a villain's nemesis. Polymorphic queries ------------------- -:index: poly polymorphism nested shapes +.. index:: polymorphism All queries thus far have referenced concrete object types: ``Hero`` and ``Villain``. However, both of these types extend the abstract type ``Person``, @@ -1016,6 +1036,8 @@ abstract type (such as ``Movie.characters``) or a :eql:op:`union type Polymorphic fields ^^^^^^^^^^^^^^^^^^ +.. index:: [is ]. + We can fetch different properties *conditional* on the subtype of each object by prefixing property/link references with ``[is ]``. This is known as a **polymorphic query**. @@ -1196,6 +1218,8 @@ internals of EdgeDB. Free objects ------------ +.. index:: ad hoc type + To select several values simultaneously, you can "bundle" them into a "free object". Free objects are a set of key-value pairs that can contain any expression. Here, the term "free" is used to indicate that the object in diff --git a/docs/edgeql/sets.rst b/docs/edgeql/sets.rst index 768c9efa8ba..ff5382f410d 100644 --- a/docs/edgeql/sets.rst +++ b/docs/edgeql/sets.rst @@ -8,6 +8,8 @@ Sets Everything is a set ------------------- +.. index:: set, multiset, cardinality, empty set, singleton + All values in EdgeQL are actually **sets**: a collection of values of a given **type**. All elements of a set must have the same type. The number of items in a set is known as its **cardinality**. A set with a cardinality of zero is @@ -19,6 +21,8 @@ referred to as an **empty set**. A set with a cardinality of one is known as a Constructing sets ----------------- +.. index:: constructor, { }, union + Set literals are declared with *set constructor* syntax: a comma-separated list of values inside a set of ``{curly braces}``. @@ -121,6 +125,8 @@ You can retrieve the cardinality of a set with the :eql:func:`count` function. Empty sets ---------- +.. index:: null, exists + The reason EdgeQL introduced the concept of *sets* is to eliminate the concept of ``null``. In SQL databases ``null`` is a special value denoting the absence of data; in EdgeDB the absence of data is just an empty set. @@ -173,6 +179,8 @@ operator. Set references -------------- +.. index:: pointer, alias, with + A set reference is a *pointer* to a set of values. Most commonly, this is the name of an :ref:`object type ` you've declared in your schema. @@ -207,6 +215,8 @@ schema. Multisets --------- +.. index:: multisets, distinct, duplicates + Technically sets in EdgeDB are actually *multisets*, because they can contain duplicates of the same element. To eliminate duplicates, use the :eql:op:`distinct` set operator. @@ -223,6 +233,8 @@ duplicates of the same element. To eliminate duplicates, use the Checking membership ------------------- +.. index:: in + Use the :eql:op:`in` operator to check whether a set contains a particular element. @@ -239,6 +251,8 @@ element. Merging sets ------------ +.. index:: union, merge + Use the :eql:op:`union` operator to merge two sets. .. code-block:: edgeql-repl @@ -253,6 +267,8 @@ Finding common members .. versionadded:: 3.0 +.. index:: intersect + Use the :eql:op:`intersect` operator to find common members between two sets. .. code-block:: edgeql-repl @@ -283,6 +299,8 @@ Removing common members .. versionadded:: 3.0 +.. index:: except + Use the :eql:op:`except` operator to leave only the members in the first set that do not appear in the second set. @@ -314,6 +332,8 @@ first set's ``3`` members are eliminated from the resulting set. Coalescing ---------- +.. index:: empty set, ??, default values, optional + Occasionally in queries, you need to handle the case where a set is empty. This can be achieved with a coalescing operator :eql:op:`?? `. This is commonly used to provide default values for optional :ref:`query parameters @@ -341,6 +361,8 @@ commonly used to provide default values for optional :ref:`query parameters Inheritance ----------- +.. index:: type intersection, backlinks, [is ] + EdgeDB schemas support :ref:`inheritance `; types (usually object types) can extend one or more other types. For instance you may declare an abstract object type ``Media`` that is extended by ``Movie`` @@ -410,6 +432,8 @@ Type filters are commonly used in conjunction with :ref:`backlinks Aggregate vs element-wise operations ------------------------------------ +.. index:: cartesian product + EdgeQL provides a large library of built-in functions and operators for handling data structures. It's useful to consider functions/operators as either *aggregate* or *element-wise*. @@ -472,6 +496,8 @@ Cardinality `. Conversion to/from arrays ------------------------- +.. index:: array_unpack, array_agg, converting sets + Both arrays and sets are collections of values that share a type. EdgeQL provides ways to convert one into the other. diff --git a/docs/edgeql/transactions.rst b/docs/edgeql/transactions.rst index df010ea18ce..2dd52e814dd 100644 --- a/docs/edgeql/transactions.rst +++ b/docs/edgeql/transactions.rst @@ -3,6 +3,9 @@ Transactions ============ +.. index:: start transaction, declare savepoint, release savepoint, + rollback to savepoint, rollback, commit + EdgeQL supports atomic transactions. The transaction API consists of several commands: diff --git a/docs/edgeql/types.rst b/docs/edgeql/types.rst index 249a2592b64..c33be16e33b 100644 --- a/docs/edgeql/types.rst +++ b/docs/edgeql/types.rst @@ -14,6 +14,8 @@ types. Type expressions ---------------- +.. index:: array< >, tuple< > + Type expressions are exactly what they sound like: EdgeQL expressions that refer to a type. Most commonly, these are simply the *names* of established types: ``str``, ``int64``, ``BlogPost``, etc. Arrays and tuples have a @@ -38,6 +40,8 @@ For additional details on type syntax, see :ref:`Schema > Primitive Types Type casting ------------ +.. index:: casts, < >, find object by id + Type casting is used to convert primitive values into another type. Casts are indicated with angle brackets containing a type expression. @@ -114,6 +118,8 @@ typed; you can't simply convert an object to an object of a different type. Type intersections ------------------ +.. index:: [is ] + All elements of a given set have the same type; however, in the context of *sets of objects*, this type might be ``abstract`` and contain elements of multiple concrete subtypes. For instance, a set of ``Media`` objects may @@ -160,6 +166,8 @@ a "filter" that removes all elements that aren't of type ``Movie``. Type checking ------------- +.. index:: is + The ``[is foo]`` "type intersection" syntax should not be confused with the *type checking* operator :eql:op:`is`. @@ -176,6 +184,8 @@ The ``[is foo]`` "type intersection" syntax should not be confused with the The ``typeof`` operator ----------------------- +.. index:: typeof + The type of any expression can be extracted with the :eql:op:`typeof` operator. This can be used in any expression that expects a type. diff --git a/docs/edgeql/update.rst b/docs/edgeql/update.rst index f79ec7bed92..8c881a0db8c 100644 --- a/docs/edgeql/update.rst +++ b/docs/edgeql/update.rst @@ -3,6 +3,8 @@ Update ====== +.. index:: update, filter, set + The ``update`` command is used to update existing objects. .. code-block:: edgeql-repl @@ -35,6 +37,8 @@ set is specified, then filters are applied, then the data is updated. Updating properties ------------------- +.. index:: unset + To explicitly unset a property that is not required, set it to an empty set. .. code-block:: edgeql @@ -44,6 +48,8 @@ To explicitly unset a property that is not required, set it to an empty set. Updating links -------------- +.. index:: :=, +=, -= + When updating links, the ``:=`` operator will *replace* the set of linked values. @@ -93,6 +99,8 @@ To remove items, use ``-=``. Returning data on update ------------------------ +.. index:: update, returning + By default, ``update`` returns only the inserted object's ``id`` as seen in the examples above. If you want to get additional data back, you may wrap your ``update`` with a ``select`` and apply a shape specifying any properties and @@ -114,6 +122,8 @@ links you want returned: With blocks ----------- +.. index:: with update + All top-level EdgeQL statements (``select``, ``insert``, ``update``, and ``delete``) can be prefixed with a ``with`` block. This is useful for updating the results of a complex query. diff --git a/docs/edgeql/with.rst b/docs/edgeql/with.rst index 9582ab20a93..451187957f8 100644 --- a/docs/edgeql/with.rst +++ b/docs/edgeql/with.rst @@ -3,6 +3,9 @@ With ==== +.. index:: composition, composing queries, composable, CTE, + common table expressions, subquery, subqueries + All top-level EdgeQL statements (``select``, ``insert``, ``update``, and ``delete``) can be prefixed by a ``with`` block. These blocks contain declarations of standalone expressions that can be used in your query. @@ -61,6 +64,8 @@ Avengers. Query parameters ^^^^^^^^^^^^^^^^ +.. index:: with + A common use case for ``with`` clauses is the initialization of :ref:`query parameters `. @@ -77,6 +82,7 @@ For a full reference on using query parameters, see :ref:`EdgeQL > Parameters Module alias ^^^^^^^^^^^^ +.. index:: with, as module Another use of ``with`` is to provide aliases for modules. This can be useful for long queries which reuse many objects or functions from the same module. @@ -100,6 +106,7 @@ part of the ``std`` module, that will be used automatically. Module selection ^^^^^^^^^^^^^^^^ +.. index:: with module, fully-qualified names By default, the *active module* is ``default``, so all schema objects inside this module can be referenced by their *short name*, e.g. ``User``, diff --git a/docs/stdlib/array.rst b/docs/stdlib/array.rst index acf313ac607..3b868303901 100644 --- a/docs/stdlib/array.rst +++ b/docs/stdlib/array.rst @@ -153,6 +153,8 @@ Reference .. eql:operator:: arrayidx: array [ int64 ] -> anytype + :index: [int], index access + Accesses the array element at a given index. Example: @@ -185,6 +187,8 @@ Reference .. eql:operator:: arrayslice: array [ int64 : int64 ] -> anytype + :index: [int:int] + Produces a sub-array from an existing array. Omitting the lower bound of an array slice will default to a lower bound @@ -228,6 +232,8 @@ Reference .. eql:operator:: arrayplus: array ++ array -> array + :index: ++, concatenate, join, add + Concatenates two arrays of the same type into one. .. code-block:: edgeql-repl diff --git a/docs/stdlib/bool.rst b/docs/stdlib/bool.rst index 5a106661600..113efa3abe2 100644 --- a/docs/stdlib/bool.rst +++ b/docs/stdlib/bool.rst @@ -134,6 +134,8 @@ Booleans .. eql:operator:: or: bool or bool -> bool + :index: or + Evaluates ``true`` if either boolean is ``true``. .. code-block:: edgeql-repl @@ -166,6 +168,8 @@ Booleans .. eql:operator:: and: bool and bool -> bool + :index: and + Evaluates ``true`` if both booleans are ``true``. .. code-block:: edgeql-repl @@ -198,6 +202,8 @@ Booleans .. eql:operator:: not: not bool -> bool + :index: not + Logically negates a given boolean value. .. code-block:: edgeql-repl diff --git a/docs/stdlib/bytes.rst b/docs/stdlib/bytes.rst index 18dc389c956..81b87461a7a 100644 --- a/docs/stdlib/bytes.rst +++ b/docs/stdlib/bytes.rst @@ -139,6 +139,8 @@ Bytes .. eql:operator:: bytesidx: bytes [ int64 ] -> bytes + :index: [int] + Accesses a byte at a given index. Examples: @@ -156,6 +158,8 @@ Bytes .. eql:operator:: bytesslice: bytes [ int64 : int64 ] -> bytes + :index: [int:int] + Produces a bytes sub-sequence from an existing bytes value. Examples: @@ -171,7 +175,9 @@ Bytes --------- -.. eql:operator:: bytesplus: bytes ++ bytes -> bytes +.. eql:operator:: bytesplus: bytes ++ bytes -> + + :index: ++, bytes, concatenate, join, add Concatenates two bytes values into one. diff --git a/docs/stdlib/cfg.rst b/docs/stdlib/cfg.rst index a0fc12e5133..0468fce80e8 100644 --- a/docs/stdlib/cfg.rst +++ b/docs/stdlib/cfg.rst @@ -64,6 +64,8 @@ Configuration Parameters Connection settings ------------------- +.. index:: listen_addresses, listen_port + :eql:synopsis:`listen_addresses -> multi str` Specifies the TCP/IP address(es) on which the server is to listen for connections from client applications. If the list is empty, the server @@ -76,6 +78,8 @@ Connection settings Resource usage -------------- +.. index:: effective_io_concurrency, query_work_mem, shared_buffers + :eql:synopsis:`effective_io_concurrency -> int64` Sets the number of concurrent disk I/O operations that can be executed simultaneously. Corresponds to the PostgreSQL @@ -95,6 +99,8 @@ Resource usage Query planning -------------- +.. index:: default_statistics_target, effective_cache_size + :eql:synopsis:`default_statistics_target -> int64` Sets the default data statistics target for the planner. Corresponds to the PostgreSQL configuration parameter of the same @@ -112,6 +118,8 @@ Query cache .. versionadded:: 5.0 +.. index:: auto_rebuild_query_cache, query_cache_mode, cfg::QueryCacheMode + :eql:synopsis:`auto_rebuild_query_cache -> bool` Determines whether to recompile the existing query cache to SQL any time DDL is executed. @@ -135,6 +143,9 @@ Query cache Query behavior -------------- +.. index:: allow_bare_ddl, cfg::AllowBareDDL, apply_access_policies, + apply_access_policies_pg, force_database_error + :eql:synopsis:`allow_bare_ddl -> cfg::AllowBareDDL` Allows for running bare DDL outside a migration. Possible values are ``cfg::AllowBareDDL.AlwaysAllow`` and ``cfg::AllowBareDDL.NeverAllow``. @@ -192,6 +203,9 @@ Query behavior Client connections ------------------ +.. index:: allow_user_specified_id, session_idle_timeout, + session_idle_transaction_timeout, query_execution_timeout + :eql:synopsis:`allow_user_specified_id -> bool` Makes it possible to set the ``.id`` property when inserting new objects. diff --git a/docs/stdlib/datetime.rst b/docs/stdlib/datetime.rst index 7ed6d12a436..ccfbd1b97e5 100644 --- a/docs/stdlib/datetime.rst +++ b/docs/stdlib/datetime.rst @@ -573,6 +573,8 @@ functionality. -> cal::local_datetime cal::local_date + duration -> cal::local_datetime + :index: +, duration, datetime, add + Adds a duration and any other datetime value. This operator is commutative. @@ -621,6 +623,8 @@ functionality. cal::relative_duration - duration\ -> cal::relative_duration + :index: -, duration, datetime, subtract + Subtracts two compatible datetime or duration values. .. code-block:: edgeql-repl diff --git a/docs/stdlib/generic.rst b/docs/stdlib/generic.rst index dadaea2012e..f3a9fa7802e 100644 --- a/docs/stdlib/generic.rst +++ b/docs/stdlib/generic.rst @@ -53,6 +53,8 @@ Generic .. eql:operator:: eq: anytype = anytype -> bool + :index: =, equal, comparison, compare + Compares two values for equality. .. code-block:: edgeql-repl @@ -89,6 +91,8 @@ Generic .. eql:operator:: neq: anytype != anytype -> bool + :index: !=, not equal, comparison, compare + Compares two values for inequality. .. code-block:: edgeql-repl @@ -127,6 +131,8 @@ Generic .. eql:operator:: coaleq: optional anytype ?= optional anytype -> bool + :index: ?=, coalesce equal, comparison, compare, empty set + Compares two (potentially empty) values for equality. This works the same as a regular :eql:op:`=` operator, but also allows @@ -147,6 +153,8 @@ Generic .. eql:operator:: coalneq: optional anytype ?!= optional anytype -> bool + :index: ?!=, coalesce not equal, comparison, compare + Compares two (potentially empty) values for inequality. This works the same as a regular :eql:op:`=` operator, but also allows @@ -167,6 +175,8 @@ Generic .. eql:operator:: lt: anytype < anytype -> bool + :index: <, less than, comparison, compare + Less than operator. The operator returns ``true`` if the value of the left expression is less @@ -208,6 +218,8 @@ Generic .. eql:operator:: gt: anytype > anytype -> bool + :index: >, greater than, comparison, compare + Greater than operator. The operator returns ``true`` if the value of the left expression is @@ -249,6 +261,8 @@ Generic .. eql:operator:: lteq: anytype <= anytype -> bool + :index: <=, less than or equal, comparison, compare + Less or equal operator. The operator returns ``true`` if the value of the left expression is less @@ -292,6 +306,8 @@ Generic .. eql:operator:: gteq: anytype >= anytype -> bool + :index: >=, greater than or equal, comparison, compare + Greater or equal operator. The operator returns ``true`` if the value of the left expression is diff --git a/docs/stdlib/json.rst b/docs/stdlib/json.rst index 578d1125e92..a051d199968 100644 --- a/docs/stdlib/json.rst +++ b/docs/stdlib/json.rst @@ -185,6 +185,8 @@ JSON array. .. eql:operator:: jsonidx: json [ int64 ] -> json + :index: [int], index access + Accesses the element of the JSON string or array at a given index. The contents of JSON *arrays* and *strings* can also be @@ -211,6 +213,8 @@ JSON array. .. eql:operator:: jsonslice: json [ int64 : int64 ] -> json + :index: [int:int] + Produces a JSON value comprising a portion of the existing JSON value. JSON *arrays* and *strings* can be sliced in the same way as @@ -236,6 +240,8 @@ JSON array. .. eql:operator:: jsonplus: json ++ json -> json + :index: ++, concatenate, join, add + Concatenates two JSON arrays, objects, or strings into one. JSON arrays, objects and strings can be concatenated with JSON values of @@ -261,6 +267,8 @@ JSON array. .. eql:operator:: jsonobjdest: json [ str ] -> json + :index: [str], json get key + Accesses an element of a JSON object given its key. The fields of JSON *objects* can also be accessed via ``[]``: diff --git a/docs/stdlib/numbers.rst b/docs/stdlib/numbers.rst index 2321881bc08..8912fb6ceb3 100644 --- a/docs/stdlib/numbers.rst +++ b/docs/stdlib/numbers.rst @@ -358,7 +358,7 @@ Definitions .. eql:operator:: plus: anyreal + anyreal -> anyreal - :index: plus add + :index: +, addition Arithmetic addition. @@ -373,7 +373,7 @@ Definitions .. eql:operator:: minus: anyreal - anyreal -> anyreal - :index: minus subtract + :index: -, subtraction Arithmetic subtraction. @@ -388,7 +388,7 @@ Definitions .. eql:operator:: uminus: - anyreal -> anyreal - :index: unary minus subtract + :index: -, unary minus, subtraction Arithmetic negation. @@ -403,7 +403,7 @@ Definitions .. eql:operator:: mult: anyreal * anyreal -> anyreal - :index: multiply multiplication + :index: \*, multiply, multiplication Arithmetic multiplication. @@ -418,7 +418,7 @@ Definitions .. eql:operator:: div: anyreal / anyreal -> anyreal - :index: divide division + :index: /, divide, division Arithmetic division. @@ -440,7 +440,7 @@ Definitions .. eql:operator:: floordiv: anyreal // anyreal -> anyreal - :index: floor divide division + :index: //, floor divide, division Floor division. @@ -479,7 +479,7 @@ Definitions .. eql:operator:: mod: anyreal % anyreal -> anyreal - :index: modulo mod division + :index: %, modulo division, remainder Remainder from division (modulo). @@ -522,7 +522,7 @@ Definitions .. eql:operator:: pow: anyreal ^ anyreal -> anyreal - :index: power pow + :index: ^, power, exponentiation Power operation. diff --git a/docs/stdlib/range.rst b/docs/stdlib/range.rst index ed6bfeee7e1..415305118fd 100644 --- a/docs/stdlib/range.rst +++ b/docs/stdlib/range.rst @@ -274,6 +274,8 @@ Reference .. eql:operator:: rangelt: range < range -> bool multirange < multirange -> bool + :index: <, multirange, less than, before, comparison, compare + One range or multirange is before the other. Returns ``true`` if the lower bound of the first range or multirange is @@ -320,6 +322,8 @@ Reference .. eql:operator:: rangegt: range > range -> bool multirange > multirange -> bool + :index: >, multirange, greater than, after, comparison, compare + One range or multirange is after the other. Returns ``true`` if the lower bound of the first range or multirange is @@ -366,6 +370,8 @@ Reference .. eql:operator:: rangelteq: range <= range -> bool multirange <= multirange -> bool + :index: <=, multirange, less than or equal, before, comparison, compare + One range or multirange is before or same as the other. Returns ``true`` if the ranges or multiranges are identical or if the @@ -419,6 +425,8 @@ Reference .. eql:operator:: rangegteq: range >= range -> bool multirange >= multirange -> bool + :index: >=, multirange, greater than or equal, after, comparison, compare + One range or multirange is after or same as the other. Returns ``true`` if the ranges or multiranges are identical or if the @@ -471,7 +479,7 @@ Reference multirange + multirange \ -> multirange - :index: plus add + :index: +, multirange, plus, addition, union Range or multirange union. @@ -507,7 +515,7 @@ Reference multirange - multirange \ -> multirange - :index: minus subtract + :index: -, multirange, minus, subtraction Range or multirange subtraction. @@ -547,7 +555,7 @@ Reference multirange * multirange \ -> multirange - :index: intersect intersection + :index: \*, multirange, intersection Range or multirnage intersection. diff --git a/docs/stdlib/set.rst b/docs/stdlib/set.rst index 9115c56d4ab..4c5af12ef10 100644 --- a/docs/stdlib/set.rst +++ b/docs/stdlib/set.rst @@ -95,6 +95,8 @@ Sets .. eql:operator:: distinct: distinct set of anytype -> set of anytype + :index: distinct, unique, set of + Produces a set of all unique elements in the given set. ``distinct`` is a set operator that returns a new set where @@ -112,7 +114,7 @@ Sets .. eql:operator:: in: anytype in set of anytype -> bool anytype not in set of anytype -> bool - :index: intersection + :index: in, not in, intersection, contains, set of Checks if a given element is a member of a given set. @@ -148,6 +150,8 @@ Sets .. eql:operator:: union: set of anytype union set of anytype -> set of anytype + :index: union, merge, join, set of + Merges two sets. Since EdgeDB sets are formally multisets, ``union`` is a *multiset sum*, @@ -166,6 +170,8 @@ Sets .. eql:operator:: intersect: set of anytype intersect set of anytype \ -> set of anytype + :index: intersect, common, set of + .. versionadded:: 3.0 Produces a set containing the common items between the given sets. @@ -184,6 +190,8 @@ Sets .. eql:operator:: except: set of anytype except set of anytype \ -> set of anytype + :index: except, set of + .. versionadded:: 3.0 Produces a set of all items in the first set which are not in the second. @@ -202,7 +210,7 @@ Sets .. eql:operator:: if..else: set of anytype if bool else set of anytype \ -> set of anytype - :index: if else ifelse elif ternary + :index: if else, ifelse, elif, ternary, conditional Produces one of two possible results based on a given condition. @@ -259,6 +267,8 @@ Sets .. eql:operator:: if..then..else: if bool then set of anytype else set of \ anytype -> set of anytype + :index: if then else, ifelse, elif, conditional + .. versionadded:: 4.0 Produces one of two possible results based on a given condition. @@ -315,6 +325,8 @@ Sets .. eql:operator:: coalesce: optional anytype ?? set of anytype \ -> set of anytype + :index: ??, empty set + Produces the first of its operands that is not an empty set. This evaluates to ``A`` for an non-empty ``A``, otherwise evaluates to @@ -347,6 +359,8 @@ Sets .. eql:operator:: detached: detached set of anytype -> set of anytype + :index: detached, set of + Detaches the input set reference from the current scope. A ``detached`` expression allows referring to some set as if it were @@ -400,6 +414,8 @@ Sets .. eql:operator:: exists: exists set of anytype -> bool + :index: exists, set of, is empty + Determines whether a set is empty or not. ``exists`` is an aggregate operator that returns a singleton set @@ -417,7 +433,7 @@ Sets .. eql:operator:: isintersect: anytype [is type] -> anytype - :index: is type intersection + :index: [is type], type intersection, filter Filters a set based on its type. Will return back the specified type. diff --git a/docs/stdlib/string.rst b/docs/stdlib/string.rst index c58a15221fb..da77b5efaa4 100644 --- a/docs/stdlib/string.rst +++ b/docs/stdlib/string.rst @@ -191,6 +191,8 @@ Strings .. eql:operator:: stridx: str [ int64 ] -> str + :index: [int], index access + String indexing. Indexing starts at 0. Negative indexes are also valid and count from @@ -229,6 +231,8 @@ Strings .. eql:operator:: strslice: str [ int64 : int64 ] -> str + :index: [int:int] + String slicing. Indexing starts at 0. Negative indexes are also valid and count from @@ -261,6 +265,8 @@ Strings .. eql:operator:: strplus: str ++ str -> str + :index: ++, string, concatenate, join, add + String concatenation. .. code-block:: edgeql-repl @@ -275,6 +281,8 @@ Strings .. eql:operator:: like: str like str -> bool str not like str -> bool + :index: like, not like, case sensitive, string matching, comparison, compare + Case-sensitive simple string matching. Returns ``true`` if the *value* (the ``str`` on the left) matches the @@ -325,6 +333,8 @@ Strings .. eql:operator:: ilike: str ilike str -> bool str not ilike str -> bool + :index: ilike, not ilike, case insensitive, string matching, comparison, compare + Case-insensitive simple string matching. The operators ``ilike`` and ``not ilike`` work diff --git a/docs/stdlib/type.rst b/docs/stdlib/type.rst index a2630957e08..d804310513c 100644 --- a/docs/stdlib/type.rst +++ b/docs/stdlib/type.rst @@ -63,6 +63,8 @@ the ``name`` property inside ``__type__``: .. eql:operator:: is: anytype is type -> bool anytype is not type -> bool + :index: is, type checking, compare, comparison + Type checking operator. Check if ``A`` is an instance of ``B`` or any of ``B``'s subtypes. @@ -93,7 +95,7 @@ the ``name`` property inside ``__type__``: .. eql:operator:: typeor: type | type -> type - :index: poly polymorphism polymorphic queries nested shapes + :index: \|, type union, polymorphism, polymorphic queries, nested shapes Type union operator. @@ -191,6 +193,8 @@ the ``name`` property inside ``__type__``: .. eql:operator:: cast: < type > anytype -> anytype + :index: , type conversion, convert type + Type cast operator. A type cast operator converts the specified value to another value of @@ -292,7 +296,7 @@ the ``name`` property inside ``__type__``: .. eql:operator:: typeof: typeof anytype -> type - :index: type introspect introspection + :index: typeof, introspection Static type inference operator. @@ -370,7 +374,7 @@ the ``name`` property inside ``__type__``: .. eql:operator:: introspect: introspect type -> schema::Type - :index: type typeof introspection + :index: introspect, type introspection, typeof Static type introspection operator. From 79d954999b757465e265e959918a17600f4246ca Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 4 Feb 2025 13:58:12 +0000 Subject: [PATCH 056/154] Add docs preview deploys (#8289) --- .github/scripts/docs/preview-deploy.js | 166 ++++++++++++++++++++++ .github/workflows/docs-preview-deploy.yml | 21 +++ 2 files changed, 187 insertions(+) create mode 100644 .github/scripts/docs/preview-deploy.js create mode 100644 .github/workflows/docs-preview-deploy.yml diff --git a/.github/scripts/docs/preview-deploy.js b/.github/scripts/docs/preview-deploy.js new file mode 100644 index 00000000000..da131be226e --- /dev/null +++ b/.github/scripts/docs/preview-deploy.js @@ -0,0 +1,166 @@ +module.exports = async ({ github, context }) => { + const { VERCEL_TOKEN, VERCEL_TEAM_ID } = process.env; + + if (!VERCEL_TOKEN || !VERCEL_TEAM_ID) { + throw new Error( + `cannot run docs preview deploy workflow, ` + + `VERCEL_TOKEN or VERCEL_TEAM_ID secrets are missing` + ); + } + + const prBranch = context.payload.pull_request.head.ref; + const commitSHA = context.payload.pull_request.head.sha; + const shortCommitSHA = commitSHA.slice(0, 8); + + const existingComments = ( + await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }) + ).data; + + const commentHeader = `### Docs preview deploy\n`; + let commentMessage = commentHeader; + + let updateComment = existingComments.find( + (c) => + c.performed_via_github_app?.slug === "github-actions" && + c.body?.startsWith(commentHeader) + ); + + let deployment; + try { + deployment = await vercelFetch("https://api.vercel.com/v13/deployments", { + name: "edgedb-docs", + gitSource: { + type: "github", + org: "edgedb", + repo: "edgedb.com", + ref: "docs-preview", + }, + projectSettings: { + buildCommand: `EDGEDB_REPO_BRANCH=${prBranch} EDGEDB_REPO_SHA=${commitSHA} yarn vercel-build`, + }, + }); + + commentMessage += `\n🔄 Deploying docs preview for commit ${shortCommitSHA}:\n\n`; + } catch (e) { + commentMessage += `\n❌ Failed to deploy docs preview for commit ${shortCommitSHA}:\n\n\`\`\`\n${e.message}\n\`\`\``; + } + + commentMessage += `\n\n(Last updated: ${formatDatetime(new Date())})`; + + if (updateComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: updateComment.id, + body: commentMessage, + }); + } else { + updateComment = ( + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentMessage, + }) + ).data; + } + + let i = 0; + while (i < 40) { + await sleep(15_000); + i++; + + const status = ( + await vercelFetch( + `https://api.vercel.com/v13/deployments/${deployment.id}` + ) + ).status; + + const latestComment = await github.rest.issues.getComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: updateComment.id, + }); + + if (!latestComment.data.body.includes(shortCommitSHA)) { + console.log("Skipping further updates, new deployment has started"); + return; + } + + if (status === "READY" || status === "ERROR" || status === "CANCELED") { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: updateComment.id, + body: `${commentHeader}${ + status === "READY" + ? `\n✅ Successfully deployed docs preview for commit ${shortCommitSHA}:` + : `\n❌ Docs preview deployment ${ + status === "CANCELED" ? "failed" : "was canceled" + } for commit ${shortCommitSHA}:` + }\n\n\n\n(Last updated: ${formatDatetime( + new Date() + )})`, + }); + return; + } + } + throw new Error("timed out waiting for deployment status to succeed or fail"); +}; + +async function vercelFetch(url, body) { + const { VERCEL_TOKEN, VERCEL_TEAM_ID } = process.env; + const _url = new URL(url); + url = `${_url.origin}${_url.pathname}?${new URLSearchParams({ + teamId: VERCEL_TEAM_ID, + })}`; + + let res; + try { + res = await fetch(url, { + body: body ? JSON.stringify(body) : undefined, + headers: { + Authorization: `Bearer ${VERCEL_TOKEN}`, + "Content-Type": body ? "application/json" : undefined, + }, + method: body ? "post" : "get", + }); + } catch (e) { + throw new Error(`vercel api request failed: ${e}`); + } + + if (res.ok) { + return await res.json(); + } else { + let body; + try { + body = await res.text(); + } catch (e) { + // ignore + } + throw new Error( + `vercel api request failed: ${res.status} ${res.statusText}, ${body}` + ); + } +} + +function formatDatetime(date) { + return date.toLocaleString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + hourCycle: "h24", + timeZoneName: "short", + }); +} + +function sleep(milliseconds) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} diff --git a/.github/workflows/docs-preview-deploy.yml b/.github/workflows/docs-preview-deploy.yml new file mode 100644 index 00000000000..3b16ed62385 --- /dev/null +++ b/.github/workflows/docs-preview-deploy.yml @@ -0,0 +1,21 @@ +name: Docs Preview Deploy + +on: + pull_request: + paths: + - "docs/**" + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: write-all + steps: + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }} + with: + script: | + const script = require('./.github/scripts/docs/preview-deploy.js'); + await script({github, context}); From d9f0631cbe104bb6ed9e4f216c14d72d0e4186ad Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 4 Feb 2025 14:06:16 +0000 Subject: [PATCH 057/154] Fix deploy failed message --- .github/scripts/docs/preview-deploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/docs/preview-deploy.js b/.github/scripts/docs/preview-deploy.js index da131be226e..4906c25f97b 100644 --- a/.github/scripts/docs/preview-deploy.js +++ b/.github/scripts/docs/preview-deploy.js @@ -100,7 +100,7 @@ module.exports = async ({ github, context }) => { status === "READY" ? `\n✅ Successfully deployed docs preview for commit ${shortCommitSHA}:` : `\n❌ Docs preview deployment ${ - status === "CANCELED" ? "failed" : "was canceled" + status === "CANCELED" ? "was canceled" : "failed" } for commit ${shortCommitSHA}:` }\n\n\n\n(Last updated: ${formatDatetime( new Date() From 40a0f0968c3b3bb5287bb6ae796f44ba70c5fd09 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 4 Feb 2025 14:37:45 +0000 Subject: [PATCH 058/154] Fix indentation in string docs --- docs/stdlib/string.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/stdlib/string.rst b/docs/stdlib/string.rst index da77b5efaa4..b6ab6ae3652 100644 --- a/docs/stdlib/string.rst +++ b/docs/stdlib/string.rst @@ -165,11 +165,11 @@ Strings .. versionadded:: 6.0 - Regular strings may use ``\(expr)`` to interpolate the value of - ``expr`` into the string. The value will be cast to ``str`` if it - is not already. For example: + Regular strings may use ``\(expr)`` to interpolate the value of + ``expr`` into the string. The value will be cast to ``str`` if it + is not already. For example: - .. code-block:: edgeql-repl + .. code-block:: edgeql-repl db> select '1 + 1 = \(1+1)'; {'1 + 1 = 2'} From b8acef258b6645ee31725e06af1efa91c57be0ea Mon Sep 17 00:00:00 2001 From: James Clarke Date: Wed, 5 Feb 2025 12:49:39 +0000 Subject: [PATCH 059/154] Allow any type of content on right side of split pages --- edb/tools/docs/edb.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/edb/tools/docs/edb.py b/edb/tools/docs/edb.py index 0e84e34a267..f78a0a958a5 100644 --- a/edb/tools/docs/edb.py +++ b/edb/tools/docs/edb.py @@ -99,18 +99,6 @@ def run(self): f'no content found at end of edb:split-section block, ' f'or before/after the edb:split-point in the edb:split-section' ) - for block in blocks: - if ( - not isinstance(block, d_nodes.literal_block) - and not isinstance(block, TabsNode) - and not isinstance(block, d_nodes.image) - and not isinstance(block, d_nodes.figure) - ): - raise Exception( - f'expected all content before/after the edb:split-point or ' - f'at the end of the edb:split-section to be either a ' - f'code block, code tabs, or image/figure' - ) return [node] From 0619bd9c947b05fbe52eabba2d3d9b9c44679b87 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 7 Feb 2025 14:49:46 -0800 Subject: [PATCH 060/154] Move JS/Python/Go docs into the core --- docs/clients/dart/index.rst | 9 - docs/clients/dotnet/index.rst | 9 - docs/clients/elixir/index.rst | 9 - docs/clients/go/api.rst | 211 + docs/clients/go/codegen.rst | 35 + docs/clients/go/index.rst | 270 +- docs/clients/go/types.rst | 3569 +++++++++++++++++ docs/clients/index.rst | 11 +- docs/clients/java/index.rst | 9 - docs/clients/js/delete.rst | 19 + docs/clients/js/driver.rst | 468 +++ docs/clients/js/for.rst | 342 ++ docs/clients/js/funcops.rst | 81 + docs/clients/js/generation.rst | 122 + docs/clients/js/group.rst | 269 ++ docs/clients/js/index.rst | 408 +- docs/clients/js/insert.rst | 162 + docs/clients/js/interfaces.rst | 189 + docs/clients/js/literals.rst | 417 ++ docs/clients/js/objects.rst | 124 + docs/clients/js/parameters.rst | 112 + docs/clients/js/queries.rst | 281 ++ docs/clients/js/querybuilder.rst | 625 +++ docs/clients/js/reference.rst | 1301 ++++++ docs/clients/js/select.rst | 679 ++++ docs/clients/js/types.rst | 131 + docs/clients/js/update.rst | 171 + docs/clients/js/with.rst | 100 + docs/clients/python/api/advanced.rst | 288 ++ docs/clients/python/api/asyncio_client.rst | 683 ++++ docs/clients/python/api/blocking_client.rst | 679 ++++ docs/clients/python/api/codegen.rst | 96 + docs/clients/python/api/types.rst | 296 ++ docs/clients/python/index.rst | 57 +- docs/clients/python/installation.rst | 56 + docs/clients/python/requirements.txt | 2 + docs/clients/python/usage.rst | 176 + docs/clients/rust/arguments.rst | 81 - docs/clients/rust/client.rst | 113 - docs/clients/rust/client_config.rst | 51 - docs/clients/rust/execute.rst | 22 - docs/clients/rust/getting_started.rst | 141 - docs/clients/rust/index.rst | 41 - docs/clients/rust/queryable.rst | 150 - docs/clients/rust/queryable_alternatives.rst | 101 - docs/clients/rust/transactions.rst | 71 - docs/edgeql/transactions.rst | 3 +- .../guides/tutorials/phoenix_github_oauth.rst | 4 +- docs/intro/clients.rst | 12 +- docs/intro/quickstart.rst | 26 +- 50 files changed, 12429 insertions(+), 853 deletions(-) delete mode 100644 docs/clients/dart/index.rst delete mode 100644 docs/clients/dotnet/index.rst delete mode 100644 docs/clients/elixir/index.rst create mode 100644 docs/clients/go/api.rst create mode 100644 docs/clients/go/codegen.rst create mode 100644 docs/clients/go/types.rst delete mode 100644 docs/clients/java/index.rst create mode 100644 docs/clients/js/delete.rst create mode 100644 docs/clients/js/driver.rst create mode 100644 docs/clients/js/for.rst create mode 100644 docs/clients/js/funcops.rst create mode 100644 docs/clients/js/generation.rst create mode 100644 docs/clients/js/group.rst create mode 100644 docs/clients/js/insert.rst create mode 100644 docs/clients/js/interfaces.rst create mode 100644 docs/clients/js/literals.rst create mode 100644 docs/clients/js/objects.rst create mode 100644 docs/clients/js/parameters.rst create mode 100644 docs/clients/js/queries.rst create mode 100644 docs/clients/js/querybuilder.rst create mode 100644 docs/clients/js/reference.rst create mode 100644 docs/clients/js/select.rst create mode 100644 docs/clients/js/types.rst create mode 100644 docs/clients/js/update.rst create mode 100644 docs/clients/js/with.rst create mode 100644 docs/clients/python/api/advanced.rst create mode 100644 docs/clients/python/api/asyncio_client.rst create mode 100644 docs/clients/python/api/blocking_client.rst create mode 100644 docs/clients/python/api/codegen.rst create mode 100644 docs/clients/python/api/types.rst create mode 100644 docs/clients/python/installation.rst create mode 100644 docs/clients/python/requirements.txt create mode 100644 docs/clients/python/usage.rst delete mode 100644 docs/clients/rust/arguments.rst delete mode 100644 docs/clients/rust/client.rst delete mode 100644 docs/clients/rust/client_config.rst delete mode 100644 docs/clients/rust/execute.rst delete mode 100644 docs/clients/rust/getting_started.rst delete mode 100644 docs/clients/rust/index.rst delete mode 100644 docs/clients/rust/queryable.rst delete mode 100644 docs/clients/rust/queryable_alternatives.rst delete mode 100644 docs/clients/rust/transactions.rst diff --git a/docs/clients/dart/index.rst b/docs/clients/dart/index.rst deleted file mode 100644 index 073632aca15..00000000000 --- a/docs/clients/dart/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _edgedb-dart-intro: - -==== -Dart -==== - -The documentation for the Dart client is automatically generated -from https://github.com/edgedb/edgedb-dart by the build -pipeline of the edgedb.com website. diff --git a/docs/clients/dotnet/index.rst b/docs/clients/dotnet/index.rst deleted file mode 100644 index d5818190afe..00000000000 --- a/docs/clients/dotnet/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _edgedb-dotnet-intro: - -==== -.NET -==== - -The documentation for the .NET client is automatically generated -from https://github.com/edgedb/edgedb-net/tree/dev/docs by the build -pipeline of the edgedb.com website. diff --git a/docs/clients/elixir/index.rst b/docs/clients/elixir/index.rst deleted file mode 100644 index 0d85a0f4778..00000000000 --- a/docs/clients/elixir/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _edgedb-elixir-intro: - -====== -Elixir -====== - -The documentation for the Elixir client is automatically pulled -from https://github.com/edgedb/edgedb-elixir by the -build pipeline of the edgedb.com website. diff --git a/docs/clients/go/api.rst b/docs/clients/go/api.rst new file mode 100644 index 00000000000..2f7a815eeb7 --- /dev/null +++ b/docs/clients/go/api.rst @@ -0,0 +1,211 @@ + +API +=== + + +*type* Client +------------- + +Client is a connection pool and is safe for concurrent use. + + +.. code-block:: go + + type Client = edgedb.Client + + +*type* Error +------------ + +Error is the error type returned from edgedb. + + +.. code-block:: go + + type Error = edgedb.Error + + +*type* ErrorCategory +-------------------- + +ErrorCategory values represent EdgeDB's error types. + + +.. code-block:: go + + type ErrorCategory = edgedb.ErrorCategory + + +*type* ErrorTag +--------------- + +ErrorTag is the argument type to Error.HasTag(). + + +.. code-block:: go + + type ErrorTag = edgedb.ErrorTag + + +*type* Executor +--------------- + +Executor is a common interface between \*Client and \*Tx, +that can run queries on an EdgeDB database. + + +.. code-block:: go + + type Executor = edgedb.Executor + + +*type* IsolationLevel +--------------------- + +IsolationLevel documentation can be found here +`docs/reference/edgeql/tx_start#parameters `_ + + +.. code-block:: go + + type IsolationLevel = edgedb.IsolationLevel + + +*type* ModuleAlias +------------------ + +ModuleAlias is an alias name and module name pair. + + +.. code-block:: go + + type ModuleAlias = edgedb.ModuleAlias + + +*type* Options +-------------- + +Options for connecting to an EdgeDB server + + +.. code-block:: go + + type Options = edgedb.Options + + +*type* RetryBackoff +------------------- + +RetryBackoff returns the duration to wait after the nth attempt +before making the next attempt when retrying a transaction. + + +.. code-block:: go + + type RetryBackoff = edgedb.RetryBackoff + + +*type* RetryCondition +--------------------- + +RetryCondition represents scenarios that can cause a transaction +run in Tx() methods to be retried. + + +.. code-block:: go + + type RetryCondition = edgedb.RetryCondition + + +*type* RetryOptions +------------------- + +RetryOptions configures how Tx() retries failed transactions. Use +NewRetryOptions to get a default RetryOptions value instead of creating one +yourself. + + +.. code-block:: go + + type RetryOptions = edgedb.RetryOptions + + +*type* RetryRule +---------------- + +RetryRule determines how transactions should be retried when run in Tx() +methods. See Client.Tx() for details. + + +.. code-block:: go + + type RetryRule = edgedb.RetryRule + + +*type* TLSOptions +----------------- + +TLSOptions contains the parameters needed to configure TLS on EdgeDB +server connections. + + +.. code-block:: go + + type TLSOptions = edgedb.TLSOptions + + +*type* TLSSecurityMode +---------------------- + +TLSSecurityMode specifies how strict TLS validation is. + + +.. code-block:: go + + type TLSSecurityMode = edgedb.TLSSecurityMode + + +*type* Tx +--------- + +Tx is a transaction. Use Client.Tx() to get a transaction. + + +.. code-block:: go + + type Tx = edgedb.Tx + + +*type* TxBlock +-------------- + +TxBlock is work to be done in a transaction. + + +.. code-block:: go + + type TxBlock = edgedb.TxBlock + + +*type* TxOptions +---------------- + +TxOptions configures how transactions behave. + + +.. code-block:: go + + type TxOptions = edgedb.TxOptions + + +*type* WarningHandler +--------------------- + +WarningHandler takes a slice of edgedb.Error that represent warnings and +optionally returns an error. This can be used to log warnings, increment +metrics, promote warnings to errors by returning them etc. + + +.. code-block:: go + + type WarningHandler = edgedb.WarningHandler \ No newline at end of file diff --git a/docs/clients/go/codegen.rst b/docs/clients/go/codegen.rst new file mode 100644 index 00000000000..636a6e17190 --- /dev/null +++ b/docs/clients/go/codegen.rst @@ -0,0 +1,35 @@ +Codegen +======= + +edgeql-go is a tool to generate go functions from edgeql queries. When run +in an EdgeDB project directory (or subdirectory) a \*_edgeql.go source file +will be generated for each \*.edgeql file. The generated go will have an +edgeqlFileName and edgeqlFileNameJSON function with typed arguments and +return value matching the query's arguments and result shape. + + +Install +------- + +.. code-block:: go + + go install github.com/edgedb/edgedb-go/cmd/edgeql-go@latest + +See also `pinning tool dependencies `_. + + +Usage +----- + +Typically this process would be run using `go generate `_ like this: + +.. code-block:: go + + //go:generate edgeql-go -pubfuncs -pubtypes -mixedcaps + +For a complete list of options: + +.. code-block:: go + + edgeql-go -help + diff --git a/docs/clients/go/index.rst b/docs/clients/go/index.rst index fbc70705e0f..89c77ecbb5a 100644 --- a/docs/clients/go/index.rst +++ b/docs/clients/go/index.rst @@ -1,9 +1,267 @@ .. _edgedb-go-intro: -== -Go -== +================ +EdgeDB Go Driver +================ -The documentation for the Go client is automatically generated -from https://github.com/edgedb/edgedb-go by the build pipeline of -the edgedb.com website. + +.. toctree:: + :maxdepth: 3 + :hidden: + + api + types + codegen + + + +Package edgedb is the official Go driver for `EdgeDB `_. Additionally, +`edgeql-go `_ is a code generator that +generates go functions from edgeql files. + +Typical client usage looks like this: + +.. code-block:: go + + package main + + import ( + "context" + "log" + + "github.com/edgedb/edgedb-go" + ) + + func main() { + ctx := context.Background() + client, err := edgedb.CreateClient(ctx, edgedb.Options{}) + if err != nil { + log.Fatal(err) + } + defer client.Close() + + var ( + age int64 = 21 + users []struct { + ID edgedb.UUID `edgedb:"id"` + Name string `edgedb:"name"` + } + ) + + query := "SELECT User{name} FILTER .age = $0" + err = client.Query(ctx, query, &users, age) + ... + } + +We recommend using environment variables for connection parameters. See the +`client connection docs `_ for more information. + +You may also connect to a database using a DSN: + +.. code-block:: go + + url := "edgedb://edgedb@localhost/edgedb" + client, err := edgedb.CreateClientDSN(ctx, url, opts) + +Or you can use Option fields. + +.. code-block:: go + + opts := edgedb.Options{ + Database: "edgedb", + User: "edgedb", + Concurrency: 4, + } + + client, err := edgedb.CreateClient(ctx, opts) + + +Errors +------ + +edgedb never returns underlying errors directly. +If you are checking for things like context expiration +use errors.Is() or errors.As(). + +.. code-block:: go + + err := client.Query(...) + if errors.Is(err, context.Canceled) { ... } + +Most errors returned by the edgedb package will satisfy the edgedb.Error +interface which has methods for introspecting. + +.. code-block:: go + + err := client.Query(...) + + var edbErr edgedb.Error + if errors.As(err, &edbErr) && edbErr.Category(edgedb.NoDataError){ + ... + } + + +Datatypes +--------- + +The following list shows the marshal/unmarshal +mapping between EdgeDB types and go types: + +.. code-block:: go + + EdgeDB Go + --------- --------- + Set []anytype + array []anytype + tuple struct + named tuple struct + Object struct + bool bool, edgedb.OptionalBool + bytes []byte, edgedb.OptionalBytes + str string, edgedb.OptionalStr + anyenum string, edgedb.OptionalStr + datetime time.Time, edgedb.OptionalDateTime + cal::local_datetime edgedb.LocalDateTime, + edgedb.OptionalLocalDateTime + cal::local_date edgedb.LocalDate, edgedb.OptionalLocalDate + cal::local_time edgedb.LocalTime, edgedb.OptionalLocalTime + duration edgedb.Duration, edgedb.OptionalDuration + cal::relative_duration edgedb.RelativeDuration, + edgedb.OptionalRelativeDuration + float32 float32, edgedb.OptionalFloat32 + float64 float64, edgedb.OptionalFloat64 + int16 int16, edgedb.OptionalFloat16 + int32 int32, edgedb.OptionalInt16 + int64 int64, edgedb.OptionalInt64 + uuid edgedb.UUID, edgedb.OptionalUUID + json []byte, edgedb.OptionalBytes + bigint *big.Int, edgedb.OptionalBigInt + + decimal user defined (see Custom Marshalers) + +Note that EdgeDB's std::duration type is represented in int64 microseconds +while go's time.Duration type is int64 nanoseconds. It is incorrect to cast +one directly to the other. + +Shape fields that are not required must use optional types for receiving +query results. The edgedb.Optional struct can be embedded to make structs +optional. + +.. code-block:: go + + type User struct { + edgedb.Optional + Email string `edgedb:"email"` + } + + var result User + err := client.QuerySingle(ctx, `SELECT User { email } LIMIT 0`, $result) + fmt.Println(result.Missing()) + // Output: true + + err := client.QuerySingle(ctx, `SELECT User { email } LIMIT 1`, $result) + fmt.Println(result.Missing()) + // Output: false + +Not all types listed above are valid query parameters. To pass a slice of +scalar values use array in your query. EdgeDB doesn't currently support +using sets as parameters. + +.. code-block:: go + + query := `select User filter .id in array_unpack(>$1)` + client.QuerySingle(ctx, query, $user, []edgedb.UUID{...}) + +Nested structures are also not directly allowed but you can use `json `_ +instead. + +By default EdgeDB will ignore embedded structs when marshaling/unmarshaling. +To treat an embedded struct's fields as part of the parent struct's fields, +tag the embedded struct with \`edgedb:"$inline"\`. + +.. code-block:: go + + type Object struct { + ID edgedb.UUID + } + + type User struct { + Object `edgedb:"$inline"` + Name string + } + + +Custom Marshalers +----------------- + +Interfaces for user defined marshaler/unmarshalers are documented in the +internal/marshal package. + + + +Usage Example +------------- + +.. code-block:: go + + package edgedb_test + + import ( + "context" + "fmt" + "log" + "time" + + edgedb "github.com/edgedb/edgedb-go" + ) + + type User struct { + ID edgedb.UUID `edgedb:"id"` + Name string `edgedb:"name"` + DOB time.Time `edgedb:"dob"` + } + + func Example() { + opts := edgedb.Options{Concurrency: 4} + ctx := context.Background() + db, err := edgedb.CreateClientDSN(ctx, "edgedb://edgedb@localhost/test", opts) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + // create a user object type. + err = db.Execute(ctx, ` + CREATE TYPE User { + CREATE REQUIRED PROPERTY name -> str; + CREATE PROPERTY dob -> datetime; + } + `) + if err != nil { + log.Fatal(err) + } + + // Insert a new user. + var inserted struct{ id edgedb.UUID } + err = db.QuerySingle(ctx, ` + INSERT User { + name := $0, + dob := $1 + } + `, &inserted, "Bob", time.Date(1984, 3, 1, 0, 0, 0, 0, time.UTC)) + if err != nil { + log.Fatal(err) + } + + // Select users. + var users []User + args := map[string]interface{}{"name": "Bob"} + query := "SELECT User {name, dob} FILTER .name = $name" + err = db.Query(ctx, query, &users, args) + if err != nil { + log.Fatal(err) + } + + fmt.Println(users) + } + diff --git a/docs/clients/go/types.rst b/docs/clients/go/types.rst new file mode 100644 index 00000000000..9d521a8eb79 --- /dev/null +++ b/docs/clients/go/types.rst @@ -0,0 +1,3569 @@ +Datatypes +========= + + +*type* DateDuration +------------------- + +DateDuration represents the elapsed time between two dates in a fuzzy human +way. + + +.. code-block:: go + + type DateDuration struct { + // contains filtered or unexported fields + } + + +*function* NewDateDuration +.......................... + +.. code-block:: go + + func NewDateDuration(months int32, days int32) DateDuration + +NewDateDuration returns a new DateDuration + + + + +*method* MarshalText +.................... + +.. code-block:: go + + func (dd DateDuration) MarshalText() ([]byte, error) + +MarshalText returns dd marshaled as text. + + + + +*method* String +............... + +.. code-block:: go + + func (dd DateDuration) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (dd *DateDuration) UnmarshalText(b []byte) error + +UnmarshalText unmarshals bytes into \*dd. + + + + +*type* Duration +--------------- + +Duration represents the elapsed time between two instants +as an int64 microsecond count. + + +.. code-block:: go + + type Duration int64 + + +*function* DurationFromNanoseconds +.................................. + +.. code-block:: go + + func DurationFromNanoseconds(d time.Duration) Duration + +DurationFromNanoseconds creates a Duration represented as microseconds +from a `time.Duration `_ represented as nanoseconds. + + + + +*function* ParseDuration +........................ + +.. code-block:: go + + func ParseDuration(s string) (Duration, error) + +ParseDuration parses an EdgeDB duration string. + + + + +*method* AsNanoseconds +...................... + +.. code-block:: go + + func (d Duration) AsNanoseconds() (time.Duration, error) + +AsNanoseconds returns `time.Duration `_ represented as nanoseconds, +after transforming from Duration microsecond representation. +Returns an error if the Duration is too long and would cause an overflow of +the internal int64 representation. + + + + +*method* String +............... + +.. code-block:: go + + func (d Duration) String() string + + + + +*type* LocalDate +---------------- + +LocalDate is a date without a time zone. +`docs/stdlib/datetime#type::cal::local_date `_ + + +.. code-block:: go + + type LocalDate struct { + // contains filtered or unexported fields + } + + +*function* NewLocalDate +....................... + +.. code-block:: go + + func NewLocalDate(year int, month time.Month, day int) LocalDate + +NewLocalDate returns a new LocalDate + + + + +*method* MarshalText +.................... + +.. code-block:: go + + func (d LocalDate) MarshalText() ([]byte, error) + +MarshalText returns d marshaled as text. + + + + +*method* String +............... + +.. code-block:: go + + func (d LocalDate) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (d *LocalDate) UnmarshalText(b []byte) error + +UnmarshalText unmarshals bytes into \*d. + + + + +*type* LocalDateTime +-------------------- + +LocalDateTime is a date and time without timezone. +`docs/stdlib/datetime#type::cal::local_datetime `_ + + +.. code-block:: go + + type LocalDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewLocalDateTime +........................... + +.. code-block:: go + + func NewLocalDateTime( + year int, month time.Month, day, hour, minute, second, microsecond int, + ) LocalDateTime + +NewLocalDateTime returns a new LocalDateTime + + + + +*method* MarshalText +.................... + +.. code-block:: go + + func (dt LocalDateTime) MarshalText() ([]byte, error) + +MarshalText returns dt marshaled as text. + + + + +*method* String +............... + +.. code-block:: go + + func (dt LocalDateTime) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (dt *LocalDateTime) UnmarshalText(b []byte) error + +UnmarshalText unmarshals bytes into \*dt. + + + + +*type* LocalTime +---------------- + +LocalTime is a time without a time zone. +`docs/stdlib/datetime#type::cal::local_time `_ + + +.. code-block:: go + + type LocalTime struct { + // contains filtered or unexported fields + } + + +*function* NewLocalTime +....................... + +.. code-block:: go + + func NewLocalTime(hour, minute, second, microsecond int) LocalTime + +NewLocalTime returns a new LocalTime + + + + +*method* MarshalText +.................... + +.. code-block:: go + + func (t LocalTime) MarshalText() ([]byte, error) + +MarshalText returns t marshaled as text. + + + + +*method* String +............... + +.. code-block:: go + + func (t LocalTime) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (t *LocalTime) UnmarshalText(b []byte) error + +UnmarshalText unmarshals bytes into \*t. + + + + +*type* Memory +------------- + +Memory represents memory in bytes. + + +.. code-block:: go + + type Memory int64 + + +*method* MarshalText +.................... + +.. code-block:: go + + func (m Memory) MarshalText() ([]byte, error) + +MarshalText returns m marshaled as text. + + + + +*method* String +............... + +.. code-block:: go + + func (m Memory) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (m *Memory) UnmarshalText(b []byte) error + +UnmarshalText unmarshals bytes into \*m. + + + + +*type* MultiRangeDateTime +------------------------- + +MultiRangeDateTime is a type alias for a slice of RangeDateTime values. + + +.. code-block:: go + + type MultiRangeDateTime = []RangeDateTime + + +*type* MultiRangeFloat32 +------------------------ + +MultiRangeFloat32 is a type alias for a slice of RangeFloat32 values. + + +.. code-block:: go + + type MultiRangeFloat32 = []RangeFloat32 + + +*type* MultiRangeFloat64 +------------------------ + +MultiRangeFloat64 is a type alias for a slice of RangeFloat64 values. + + +.. code-block:: go + + type MultiRangeFloat64 = []RangeFloat64 + + +*type* MultiRangeInt32 +---------------------- + +MultiRangeInt32 is a type alias for a slice of RangeInt32 values. + + +.. code-block:: go + + type MultiRangeInt32 = []RangeInt32 + + +*type* MultiRangeInt64 +---------------------- + +MultiRangeInt64 is a type alias for a slice of RangeInt64 values. + + +.. code-block:: go + + type MultiRangeInt64 = []RangeInt64 + + +*type* MultiRangeLocalDate +-------------------------- + +MultiRangeLocalDate is a type alias for a slice of +RangeLocalDate values. + + +.. code-block:: go + + type MultiRangeLocalDate = []RangeLocalDate + + +*type* MultiRangeLocalDateTime +------------------------------ + +MultiRangeLocalDateTime is a type alias for a slice of +RangeLocalDateTime values. + + +.. code-block:: go + + type MultiRangeLocalDateTime = []RangeLocalDateTime + + +*type* Optional +--------------- + +Optional represents a shape field that is not required. +Optional is embedded in structs to make them optional. For example: + +.. code-block:: go + + type User struct { + edgedb.Optional + Name string `edgedb:"name"` + } + + +.. code-block:: go + + type Optional struct { + // contains filtered or unexported fields + } + + +*method* Missing +................ + +.. code-block:: go + + func (o *Optional) Missing() bool + +Missing returns true if the value is missing. + + + + +*method* SetMissing +................... + +.. code-block:: go + + func (o *Optional) SetMissing(missing bool) + +SetMissing sets the structs missing status. true means missing and false +means present. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *Optional) Unset() + +Unset marks the value as missing + + + + +*type* OptionalBigInt +--------------------- + +OptionalBigInt is an optional \*big.Int. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalBigInt struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalBigInt +............................ + +.. code-block:: go + + func NewOptionalBigInt(v *big.Int) OptionalBigInt + +NewOptionalBigInt is a convenience function for creating an OptionalBigInt +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalBigInt) Get() (*big.Int, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalBigInt) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalBigInt) Set(val *big.Int) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalBigInt) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalBigInt) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalBool +------------------- + +OptionalBool is an optional bool. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalBool struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalBool +.......................... + +.. code-block:: go + + func NewOptionalBool(v bool) OptionalBool + +NewOptionalBool is a convenience function for creating an OptionalBool with +its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalBool) Get() (bool, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalBool) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalBool) Set(val bool) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalBool) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalBool) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalBytes +-------------------- + +OptionalBytes is an optional []byte. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalBytes struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalBytes +........................... + +.. code-block:: go + + func NewOptionalBytes(v []byte) OptionalBytes + +NewOptionalBytes is a convenience function for creating an OptionalBytes +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalBytes) Get() ([]byte, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalBytes) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalBytes) Set(val []byte) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalBytes) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalBytes) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalDateDuration +--------------------------- + +OptionalDateDuration is an optional DateDuration. Optional types +must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalDateDuration struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalDateDuration +.................................. + +.. code-block:: go + + func NewOptionalDateDuration(v DateDuration) OptionalDateDuration + +NewOptionalDateDuration is a convenience function for creating an +OptionalDateDuration with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o *OptionalDateDuration) Get() (DateDuration, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalDateDuration) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalDateDuration) Set(val DateDuration) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalDateDuration) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalDateDuration) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalDateTime +----------------------- + +OptionalDateTime is an optional time.Time. Optional types must be used for +out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalDateTime +.............................. + +.. code-block:: go + + func NewOptionalDateTime(v time.Time) OptionalDateTime + +NewOptionalDateTime is a convenience function for creating an +OptionalDateTime with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalDateTime) Get() (time.Time, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalDateTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalDateTime) Set(val time.Time) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalDateTime) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalDateTime) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalDuration +----------------------- + +OptionalDuration is an optional Duration. Optional types must be used for +out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalDuration struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalDuration +.............................. + +.. code-block:: go + + func NewOptionalDuration(v Duration) OptionalDuration + +NewOptionalDuration is a convenience function for creating an +OptionalDuration with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalDuration) Get() (Duration, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalDuration) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalDuration) Set(val Duration) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalDuration) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalDuration) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalFloat32 +---------------------- + +OptionalFloat32 is an optional float32. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalFloat32 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalFloat32 +............................. + +.. code-block:: go + + func NewOptionalFloat32(v float32) OptionalFloat32 + +NewOptionalFloat32 is a convenience function for creating an OptionalFloat32 +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalFloat32) Get() (float32, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalFloat32) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalFloat32) Set(val float32) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalFloat32) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalFloat32) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalFloat64 +---------------------- + +OptionalFloat64 is an optional float64. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalFloat64 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalFloat64 +............................. + +.. code-block:: go + + func NewOptionalFloat64(v float64) OptionalFloat64 + +NewOptionalFloat64 is a convenience function for creating an OptionalFloat64 +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalFloat64) Get() (float64, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalFloat64) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalFloat64) Set(val float64) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalFloat64) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalFloat64) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalInt16 +-------------------- + +OptionalInt16 is an optional int16. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalInt16 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalInt16 +........................... + +.. code-block:: go + + func NewOptionalInt16(v int16) OptionalInt16 + +NewOptionalInt16 is a convenience function for creating an OptionalInt16 +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalInt16) Get() (int16, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalInt16) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalInt16) Set(val int16) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalInt16) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalInt16) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalInt32 +-------------------- + +OptionalInt32 is an optional int32. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalInt32 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalInt32 +........................... + +.. code-block:: go + + func NewOptionalInt32(v int32) OptionalInt32 + +NewOptionalInt32 is a convenience function for creating an OptionalInt32 +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalInt32) Get() (int32, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalInt32) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalInt32) Set(val int32) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalInt32) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalInt32) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalInt64 +-------------------- + +OptionalInt64 is an optional int64. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalInt64 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalInt64 +........................... + +.. code-block:: go + + func NewOptionalInt64(v int64) OptionalInt64 + +NewOptionalInt64 is a convenience function for creating an OptionalInt64 +with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalInt64) Get() (int64, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalInt64) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalInt64) Set(val int64) *OptionalInt64 + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalInt64) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalInt64) Unset() *OptionalInt64 + +Unset marks the value as missing. + + + + +*type* OptionalLocalDate +------------------------ + +OptionalLocalDate is an optional LocalDate. Optional types must be used for +out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalLocalDate struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalLocalDate +............................... + +.. code-block:: go + + func NewOptionalLocalDate(v LocalDate) OptionalLocalDate + +NewOptionalLocalDate is a convenience function for creating an +OptionalLocalDate with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalLocalDate) Get() (LocalDate, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalLocalDate) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalLocalDate) Set(val LocalDate) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalLocalDate) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalLocalDate) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalLocalDateTime +---------------------------- + +OptionalLocalDateTime is an optional LocalDateTime. Optional types must be +used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalLocalDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalLocalDateTime +................................... + +.. code-block:: go + + func NewOptionalLocalDateTime(v LocalDateTime) OptionalLocalDateTime + +NewOptionalLocalDateTime is a convenience function for creating an +OptionalLocalDateTime with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalLocalDateTime) Get() (LocalDateTime, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalLocalDateTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalLocalDateTime) Set(val LocalDateTime) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalLocalDateTime) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalLocalDateTime) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalLocalTime +------------------------ + +OptionalLocalTime is an optional LocalTime. Optional types must be used for +out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalLocalTime struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalLocalTime +............................... + +.. code-block:: go + + func NewOptionalLocalTime(v LocalTime) OptionalLocalTime + +NewOptionalLocalTime is a convenience function for creating an +OptionalLocalTime with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalLocalTime) Get() (LocalTime, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalLocalTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalLocalTime) Set(val LocalTime) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalLocalTime) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalLocalTime) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalMemory +--------------------- + +OptionalMemory is an optional Memory. Optional types must be used for +out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalMemory struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalMemory +............................ + +.. code-block:: go + + func NewOptionalMemory(v Memory) OptionalMemory + +NewOptionalMemory is a convenience function for creating an +OptionalMemory with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalMemory) Get() (Memory, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalMemory) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalMemory) Set(val Memory) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalMemory) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalMemory) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeDateTime +---------------------------- + +OptionalRangeDateTime is an optional RangeDateTime. Optional +types must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeDateTime +................................... + +.. code-block:: go + + func NewOptionalRangeDateTime(v RangeDateTime) OptionalRangeDateTime + +NewOptionalRangeDateTime is a convenience function for creating an +OptionalRangeDateTime with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o *OptionalRangeDateTime) Get() (RangeDateTime, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o *OptionalRangeDateTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeDateTime) Set(val RangeDateTime) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeDateTime) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeDateTime) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeFloat32 +--------------------------- + +OptionalRangeFloat32 is an optional RangeFloat32. Optional +types must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeFloat32 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeFloat32 +.................................. + +.. code-block:: go + + func NewOptionalRangeFloat32(v RangeFloat32) OptionalRangeFloat32 + +NewOptionalRangeFloat32 is a convenience function for creating an +OptionalRangeFloat32 with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRangeFloat32) Get() (RangeFloat32, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRangeFloat32) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeFloat32) Set(val RangeFloat32) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeFloat32) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeFloat32) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeFloat64 +--------------------------- + +OptionalRangeFloat64 is an optional RangeFloat64. Optional +types must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeFloat64 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeFloat64 +.................................. + +.. code-block:: go + + func NewOptionalRangeFloat64(v RangeFloat64) OptionalRangeFloat64 + +NewOptionalRangeFloat64 is a convenience function for creating an +OptionalRangeFloat64 with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRangeFloat64) Get() (RangeFloat64, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRangeFloat64) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeFloat64) Set(val RangeFloat64) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeFloat64) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeFloat64) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeInt32 +------------------------- + +OptionalRangeInt32 is an optional RangeInt32. Optional types must be used +for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeInt32 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeInt32 +................................ + +.. code-block:: go + + func NewOptionalRangeInt32(v RangeInt32) OptionalRangeInt32 + +NewOptionalRangeInt32 is a convenience function for creating an +OptionalRangeInt32 with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRangeInt32) Get() (RangeInt32, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRangeInt32) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeInt32) Set(val RangeInt32) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeInt32) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeInt32) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeInt64 +------------------------- + +OptionalRangeInt64 is an optional RangeInt64. Optional +types must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeInt64 struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeInt64 +................................ + +.. code-block:: go + + func NewOptionalRangeInt64(v RangeInt64) OptionalRangeInt64 + +NewOptionalRangeInt64 is a convenience function for creating an +OptionalRangeInt64 with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRangeInt64) Get() (RangeInt64, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRangeInt64) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeInt64) Set(val RangeInt64) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeInt64) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeInt64) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeLocalDate +----------------------------- + +OptionalRangeLocalDate is an optional RangeLocalDate. Optional types must be +used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeLocalDate struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeLocalDate +.................................... + +.. code-block:: go + + func NewOptionalRangeLocalDate(v RangeLocalDate) OptionalRangeLocalDate + +NewOptionalRangeLocalDate is a convenience function for creating an +OptionalRangeLocalDate with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRangeLocalDate) Get() (RangeLocalDate, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRangeLocalDate) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeLocalDate) Set(val RangeLocalDate) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeLocalDate) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeLocalDate) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRangeLocalDateTime +--------------------------------- + +OptionalRangeLocalDateTime is an optional RangeLocalDateTime. Optional +types must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRangeLocalDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRangeLocalDateTime +........................................ + +.. code-block:: go + + func NewOptionalRangeLocalDateTime( + v RangeLocalDateTime, + ) OptionalRangeLocalDateTime + +NewOptionalRangeLocalDateTime is a convenience function for creating an +OptionalRangeLocalDateTime with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRangeLocalDateTime) Get() (RangeLocalDateTime, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRangeLocalDateTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRangeLocalDateTime) Set(val RangeLocalDateTime) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRangeLocalDateTime) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRangeLocalDateTime) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalRelativeDuration +------------------------------- + +OptionalRelativeDuration is an optional RelativeDuration. Optional types +must be used for out parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalRelativeDuration struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalRelativeDuration +...................................... + +.. code-block:: go + + func NewOptionalRelativeDuration(v RelativeDuration) OptionalRelativeDuration + +NewOptionalRelativeDuration is a convenience function for creating an +OptionalRelativeDuration with its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalRelativeDuration) Get() (RelativeDuration, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalRelativeDuration) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalRelativeDuration) Set(val RelativeDuration) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalRelativeDuration) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalRelativeDuration) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalStr +------------------ + +OptionalStr is an optional string. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalStr struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalStr +......................... + +.. code-block:: go + + func NewOptionalStr(v string) OptionalStr + +NewOptionalStr is a convenience function for creating an OptionalStr with +its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalStr) Get() (string, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalStr) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalStr) Set(val string) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalStr) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o. + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalStr) Unset() + +Unset marks the value as missing. + + + + +*type* OptionalUUID +------------------- + +OptionalUUID is an optional UUID. Optional types must be used for out +parameters when a shape field is not required. + + +.. code-block:: go + + type OptionalUUID struct { + // contains filtered or unexported fields + } + + +*function* NewOptionalUUID +.......................... + +.. code-block:: go + + func NewOptionalUUID(v UUID) OptionalUUID + +NewOptionalUUID is a convenience function for creating an OptionalUUID with +its value set to v. + + + + +*method* Get +............ + +.. code-block:: go + + func (o OptionalUUID) Get() (UUID, bool) + +Get returns the value and a boolean indicating if the value is present. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (o OptionalUUID) MarshalJSON() ([]byte, error) + +MarshalJSON returns o marshaled as json. + + + + +*method* Set +............ + +.. code-block:: go + + func (o *OptionalUUID) Set(val UUID) + +Set sets the value. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (o *OptionalUUID) UnmarshalJSON(bytes []byte) error + +UnmarshalJSON unmarshals bytes into \*o + + + + +*method* Unset +.............. + +.. code-block:: go + + func (o *OptionalUUID) Unset() + +Unset marks the value as missing. + + + + +*type* RangeDateTime +-------------------- + +RangeDateTime is an interval of time.Time values. + + +.. code-block:: go + + type RangeDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewRangeDateTime +........................... + +.. code-block:: go + + func NewRangeDateTime( + lower, upper OptionalDateTime, + incLower, incUpper bool, + ) RangeDateTime + +NewRangeDateTime creates a new RangeDateTime value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeDateTime) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeDateTime) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeDateTime) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeDateTime) Lower() OptionalDateTime + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeDateTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeDateTime) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeDateTime) Upper() OptionalDateTime + +Upper returns the upper bound. + + + + +*type* RangeFloat32 +------------------- + +RangeFloat32 is an interval of float32 values. + + +.. code-block:: go + + type RangeFloat32 struct { + // contains filtered or unexported fields + } + + +*function* NewRangeFloat32 +.......................... + +.. code-block:: go + + func NewRangeFloat32( + lower, upper OptionalFloat32, + incLower, incUpper bool, + ) RangeFloat32 + +NewRangeFloat32 creates a new RangeFloat32 value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeFloat32) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeFloat32) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeFloat32) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeFloat32) Lower() OptionalFloat32 + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeFloat32) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeFloat32) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeFloat32) Upper() OptionalFloat32 + +Upper returns the upper bound. + + + + +*type* RangeFloat64 +------------------- + +RangeFloat64 is an interval of float64 values. + + +.. code-block:: go + + type RangeFloat64 struct { + // contains filtered or unexported fields + } + + +*function* NewRangeFloat64 +.......................... + +.. code-block:: go + + func NewRangeFloat64( + lower, upper OptionalFloat64, + incLower, incUpper bool, + ) RangeFloat64 + +NewRangeFloat64 creates a new RangeFloat64 value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeFloat64) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeFloat64) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeFloat64) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeFloat64) Lower() OptionalFloat64 + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeFloat64) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeFloat64) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeFloat64) Upper() OptionalFloat64 + +Upper returns the upper bound. + + + + +*type* RangeInt32 +----------------- + +RangeInt32 is an interval of int32 values. + + +.. code-block:: go + + type RangeInt32 struct { + // contains filtered or unexported fields + } + + +*function* NewRangeInt32 +........................ + +.. code-block:: go + + func NewRangeInt32( + lower, upper OptionalInt32, + incLower, incUpper bool, + ) RangeInt32 + +NewRangeInt32 creates a new RangeInt32 value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeInt32) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeInt32) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeInt32) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeInt32) Lower() OptionalInt32 + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeInt32) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeInt32) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeInt32) Upper() OptionalInt32 + +Upper returns the upper bound. + + + + +*type* RangeInt64 +----------------- + +RangeInt64 is an interval of int64 values. + + +.. code-block:: go + + type RangeInt64 struct { + // contains filtered or unexported fields + } + + +*function* NewRangeInt64 +........................ + +.. code-block:: go + + func NewRangeInt64( + lower, upper OptionalInt64, + incLower, incUpper bool, + ) RangeInt64 + +NewRangeInt64 creates a new RangeInt64 value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeInt64) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeInt64) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeInt64) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeInt64) Lower() OptionalInt64 + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeInt64) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeInt64) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeInt64) Upper() OptionalInt64 + +Upper returns the upper bound. + + + + +*type* RangeLocalDate +--------------------- + +RangeLocalDate is an interval of LocalDate values. + + +.. code-block:: go + + type RangeLocalDate struct { + // contains filtered or unexported fields + } + + +*function* NewRangeLocalDate +............................ + +.. code-block:: go + + func NewRangeLocalDate( + lower, upper OptionalLocalDate, + incLower, incUpper bool, + ) RangeLocalDate + +NewRangeLocalDate creates a new RangeLocalDate value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeLocalDate) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeLocalDate) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeLocalDate) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeLocalDate) Lower() OptionalLocalDate + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeLocalDate) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeLocalDate) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeLocalDate) Upper() OptionalLocalDate + +Upper returns the upper bound. + + + + +*type* RangeLocalDateTime +------------------------- + +RangeLocalDateTime is an interval of LocalDateTime values. + + +.. code-block:: go + + type RangeLocalDateTime struct { + // contains filtered or unexported fields + } + + +*function* NewRangeLocalDateTime +................................ + +.. code-block:: go + + func NewRangeLocalDateTime( + lower, upper OptionalLocalDateTime, + incLower, incUpper bool, + ) RangeLocalDateTime + +NewRangeLocalDateTime creates a new RangeLocalDateTime value. + + + + +*method* Empty +.............. + +.. code-block:: go + + func (r RangeLocalDateTime) Empty() bool + +Empty returns true if the range is empty. + + + + +*method* IncLower +................. + +.. code-block:: go + + func (r RangeLocalDateTime) IncLower() bool + +IncLower returns true if the lower bound is inclusive. + + + + +*method* IncUpper +................. + +.. code-block:: go + + func (r RangeLocalDateTime) IncUpper() bool + +IncUpper returns true if the upper bound is inclusive. + + + + +*method* Lower +.............. + +.. code-block:: go + + func (r RangeLocalDateTime) Lower() OptionalLocalDateTime + +Lower returns the lower bound. + + + + +*method* MarshalJSON +.................... + +.. code-block:: go + + func (r RangeLocalDateTime) MarshalJSON() ([]byte, error) + +MarshalJSON returns r marshaled as json. + + + + +*method* UnmarshalJSON +...................... + +.. code-block:: go + + func (r *RangeLocalDateTime) UnmarshalJSON(data []byte) error + +UnmarshalJSON unmarshals bytes into \*r. + + + + +*method* Upper +.............. + +.. code-block:: go + + func (r RangeLocalDateTime) Upper() OptionalLocalDateTime + +Upper returns the upper bound. + + + + +*type* RelativeDuration +----------------------- + +RelativeDuration represents the elapsed time between two instants in a fuzzy +human way. + + +.. code-block:: go + + type RelativeDuration struct { + // contains filtered or unexported fields + } + + +*function* NewRelativeDuration +.............................. + +.. code-block:: go + + func NewRelativeDuration( + months, days int32, + microseconds int64, + ) RelativeDuration + +NewRelativeDuration returns a new RelativeDuration + + + + +*method* MarshalText +.................... + +.. code-block:: go + + func (rd RelativeDuration) MarshalText() ([]byte, error) + +MarshalText returns rd marshaled as text. + + + + +*method* String +............... + +.. code-block:: go + + func (rd RelativeDuration) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (rd *RelativeDuration) UnmarshalText(b []byte) error + +UnmarshalText unmarshals bytes into \*rd. + + + + +*type* UUID +----------- + +UUID is a universally unique identifier +`docs/stdlib/uuid `_ + + +.. code-block:: go + + type UUID [16]byte + + +*function* ParseUUID +.................... + +.. code-block:: go + + func ParseUUID(s string) (UUID, error) + +ParseUUID parses s into a UUID or returns an error. + + + + +*method* MarshalText +.................... + +.. code-block:: go + + func (id UUID) MarshalText() ([]byte, error) + +MarshalText returns the id as a byte string. + + + + +*method* String +............... + +.. code-block:: go + + func (id UUID) String() string + + + + +*method* UnmarshalText +...................... + +.. code-block:: go + + func (id *UUID) UnmarshalText(b []byte) error + +UnmarshalText unmarshals the id from a string. + diff --git a/docs/clients/index.rst b/docs/clients/index.rst index df6f536da90..575fae31270 100644 --- a/docs/clients/index.rst +++ b/docs/clients/index.rst @@ -6,15 +6,11 @@ Client Libraries ================ -**Official Client Libraries** +**Client Libraries** * `Python `_ * `TypeScript/JavaScript `_ * `Go `_ -* `Rust `_ -* `.NET `_ -* `Java `_ -* `Elixir `_ **HTTP Protocols** @@ -30,10 +26,5 @@ Client Libraries js/index python/index go/index - rust/index - dart/index - dotnet/index - java/index - elixir/index http/index graphql/index diff --git a/docs/clients/java/index.rst b/docs/clients/java/index.rst deleted file mode 100644 index 7e28eb27dee..00000000000 --- a/docs/clients/java/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _edgedb-java-intro: - -==== -Java -==== - -The documentation for the Java client is automatically pulled -from https://github.com/edgedb/edgedb-java/tree/master/docs by the -build pipeline of the edgedb.com website. diff --git a/docs/clients/js/delete.rst b/docs/clients/js/delete.rst new file mode 100644 index 00000000000..c299a3b1791 --- /dev/null +++ b/docs/clients/js/delete.rst @@ -0,0 +1,19 @@ +.. _edgedb-js-delete: + +Delete +------ + +Delete objects with ``e.delete``. + +.. code-block:: typescript + + e.delete(e.Movie, movie => ({ + filter: e.op(movie.release_year, ">", 2000), + filter_single: { id: "abc..." }, + order_by: movie.title, + offset: 10, + limit: 10 + })); + +The only supported keys are ``filter``, ``filter_single``, ``order_by``, +``offset``, and ``limit``. diff --git a/docs/clients/js/driver.rst b/docs/clients/js/driver.rst new file mode 100644 index 00000000000..0dc62a325b6 --- /dev/null +++ b/docs/clients/js/driver.rst @@ -0,0 +1,468 @@ +.. _edgedb-js-driver: + + +Client +====== + +The ``Client`` class implements the basic functionality required to establish a +connection to your database and execute queries. + +.. _edgedb-js-create-client: + +Creating clients +---------------- + +A *client* represents a connection to your database and provides methods for +executing queries. + +.. note:: + + In actuality, the client maintains a *pool* of connections under the hood. + When your server is under load, queries will be run in parallel across many + connections, instead of being bottlenecked by a single connection. + +To create a client: + +.. code-block:: js + + const edgedb = require("edgedb"); + + const client = edgedb.createClient(); + + +If you're using TypeScript or have ES modules enabled, you can use +``import`` syntax instead: + +.. code-block:: js + + import * as edgedb from "edgedb"; + + const client = edgedb.createClient(); + + +Connections +^^^^^^^^^^^ + +Notice we didn't pass any arguments into ``createClient``. That's intentional. + +**In development**, we recommend using ``edgedb project init`` to create an +instance and link it to your project directory. As long as you're inside this +directory, ``createClient()`` with auto-detect the project and connect to the +associated instance automatically. + +**In production** you should use environment variables to provide connection +information to ``createClient``. See the :ref:`Connection parameters +` docs for details. + +Configuring clients +^^^^^^^^^^^^^^^^^^^ + +Clients can be configured using a set of *immutable* methods that start with +``with``. + +.. note:: + + These methods return a *new Client instance* that *shares a connection pool* + with the original client! This is important. Each call to ``createClient`` + instantiates a new connection pool. + +The code example below demonstrates all available configuration settings. The +value specified below is the *default value* for that setting. + +.. code-block:: typescript + + import {createClient, Duration, IsolationLevel} from "edgedb"; + + const baseClient = createClient(); + const client = baseClient + .withConfig({ + // 10 seconds + session_idle_transaction_timeout: Duration.from({seconds: 10}), + // 0 seconds === no timeout + query_execution_timeout: Duration.from({seconds: 0}), + allow_bare_ddl: "NeverAllow", + allow_user_specified_id: false, + apply_access_policies: true, + }) + .withRetryOptions({ + attempts: 3, + backoff: (attemptNo: number) => { + // exponential backoff + return 2 ** attemptNo * 100 + Math.random() * 100; + }, + }) + .withTransactionOptions({ + isolation: IsolationLevel.Serializable, // only supported value + deferrable: false, + readonly: false, + }); + +Running queries +--------------- + +To execute a basic query: + +.. code-block:: js + + const edgedb = require("edgedb"); + + const client = edgedb.createClient(); + + async function main() { + const result = await client.query(`select 2 + 2;`); + console.log(result); // [4] + } + + +.. _edgedb-js-typescript: + +In TypeScript, you can supply a type hint to receive a strongly typed result. + +.. code-block:: js + + const result = await client.query(`select 2 + 2;`); + // number[] + +``.query`` method +^^^^^^^^^^^^^^^^^ + +The ``.query`` method always returns an array of results. It places no +constraints on cardinality. + +.. code-block:: js + + await client.query(`select 2 + 2;`); // [4] + await client.query(`select [1, 2, 3];`); // [[1, 2, 3]] + await client.query(`select {};`); // [] + await client.query(`select {1, 2, 3};`); // [1, 2, 3] + +``.querySingle`` method +^^^^^^^^^^^^^^^^^^^^^^^ + +If you know your query will only return a single element, you can tell EdgeDB +to expect a *singleton result* by using the ``.querySingle`` method. This is +intended for queries that return *zero or one* elements. If the query returns +a set with more than one elements, the ``Client`` will throw a runtime error. + +.. note:: + + Note that if you're selecting an array or tuple, the returned value may + still be an array. + +.. code-block:: js + + await client.querySingle(`select 2 + 2;`); // 4 + await client.querySingle(`select [1, 2, 3];`); // [1, 2, 3] + await client.querySingle(`select {};`); // null + await client.querySingle(`select {1, 2, 3};`); // Error + +``.queryRequiredSingle`` method +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use ``queryRequiredSingle`` for queries that return *exactly one* element. If +the query returns an empty set or a set with multiple elements, the ``Client`` +will throw a runtime error. + +.. code-block:: js + + await client.queryRequiredSingle(`select 2 + 2;`); // 4 + await client.queryRequiredSingle(`select [1, 2, 3];`); // [1, 2, 3] + await client.queryRequiredSingle(`select {};`); // Error + await client.queryRequiredSingle(`select {1, 2, 3};`); // Error + +TypeScript +^^^^^^^^^^ + +The TypeScript signatures of these methods reflects their behavior. + +.. code-block:: typescript + + await client.query(`select 2 + 2;`); + // number[] + + await client.querySingle(`select 2 + 2;`); + // number | null + + await client.queryRequiredSingle(`select 2 + 2;`); + // number + + +Type conversion +--------------- + +The client converts EdgeDB types into a corresponding JavaScript data +structure. Some EdgeDB types like ``duration`` don't have a corresponding type +in the JavaScript type system, so we've implemented classes like +:js:class:`Duration` to represent them. + +.. list-table:: + + * - **EdgeDB type** + - **JavaScript type** + * - Sets + - ``Array`` + * - Arrays + - ``Array`` + * - Tuples ``tuple`` + - ``Array`` + * - Named tuples ``tuple`` + - ``object`` + * - Enums + - ``string`` + * - ``Object`` + - ``object`` + * - ``str`` + - ``string`` + * - ``bool`` + - ``boolean`` + * - ``float32`` ``float64`` ``int16`` ``int32`` ``int64`` + - ``number`` + * - ``json`` + - ``string`` + * - ``uuid`` + - ``string`` + * - ``bigint`` + - ``BigInt`` + * - ``decimal`` + - ``string`` + * - ``bytes`` + - ``Uint8Array`` + * - ``datetime`` + - ``Date`` + * - ``duration`` + - :js:class:`Duration` + * - ``e.cal.relative_duration`` + - :js:class:`RelativeDuration` + * - ``e.cal.date_duration`` + - :js:class:`DateDuration` + * - ``cal::local_date`` + - :js:class:`LocalDate` + * - ``cal::local_time`` + - :js:class:`LocalTime` + * - ``cal::local_datetime`` + - :js:class:`LocalDateTime` + * - ``cfg::memory`` + - :js:class:`ConfigMemory` + * - Ranges ``range`` + - :js:class:`Range` + + +To learn more about the client's built-in type classes, refer to the reference +documentation. + +- :js:class:`Duration` +- :js:class:`RelativeDuration` +- :js:class:`DateDuration` +- :js:class:`LocalDate` +- :js:class:`LocalTime` +- :js:class:`LocalDateTime` +- :js:class:`ConfigMemory` +- :js:class:`Range` + + +.. .. note:: + +.. **A message for query builder users** + +.. Everything below this point isn't necessary/applicable for query builder users. Continue to the :ref:`Query Builder ` docs. + + +JSON results +------------ + +Client provide additional methods for running queries and retrieving results +as a *serialized JSON string*. This serialization happens inside the database +and is typically more performant than running ``JSON.stringify`` yourself. + +.. code-block:: js + + await client.queryJSON(`select {1, 2, 3};`); + // "[1, 2, 3]" + + await client.querySingleJSON(`select {};`); + // "null" + + await client.queryRequiredSingleJSON(`select 3.14;`); + // "3.14" + +Non-returning queries +--------------------- + +To execute a query without retrieving a result, use the ``.execute`` method. +This is especially useful for mutations, where there's often no need for the +query to return a value. + +.. code-block:: js + + await client.execute(`insert Movie { + title := "Avengers: Endgame" + };`); + +With EdgeDB 2.0 or later, you can execute a "script" consisting of multiple +semicolon-separated statements in a single ``.execute`` call. + +.. code-block:: js + + await client.execute(` + insert Person { name := "Robert Downey Jr." }; + insert Person { name := "Scarlett Johansson" }; + insert Movie { + title := $title, + actors := ( + select Person filter .name in { + "Robert Downey Jr.", + "Scarlett Johansson" + } + ) + } + `, { title: "Iron Man 2" }); + +Parameters +---------- + +If your query contains parameters (e.g. ``$foo``), you can pass in values as +the second argument. This is true for all ``query*`` methods and ``execute``. + +.. code-block:: js + + const INSERT_MOVIE = `insert Movie { + title := $title + }` + const result = await client.querySingle(INSERT_MOVIE, { + title: "Iron Man" + }); + console.log(result); + // {id: "047c5893..."} + +Remember that :ref:`parameters ` can only be *scalars* or +*arrays of scalars*. + +Scripts +------- + +Both ``execute`` and the ``query*`` methods support scripts (queries +containing multiple statements). The statements are run in an implicit +transaction (unless already in an explicit transaction), so the whole script +remains atomic. For the ``query*`` methods only the result of the final +statement in the script will be returned. + +.. code-block:: js + + const result = await client.query(` + insert Movie { + title := $title + }; + insert Person { + name := $name + }; + `, { + title: "Thor: Ragnarok", + name: "Anson Mount" + }); + // [{id: "5dd2557b..."}] + +For more fine grained control of atomic exectution of multiple statements, use +the ``transaction()`` API. + +Checking connection status +-------------------------- + +The client maintains a dynamically sized *pool* of connections under the hood. +These connections are initialized *lazily*, so no connection will be +established until the first time you execute a query. + +If you want to explicitly ensure that the client is connected without running +a query, use the ``.ensureConnected()`` method. + +.. code-block:: js + + const edgedb = require("edgedb"); + + const client = edgedb.createClient(); + + async function main() { + await client.ensureConnected(); + } + +.. _edgedb-js-api-transaction: + +Transactions +------------ + +The most robust way to execute transactional code is to use +the ``transaction()`` API: + +.. code-block:: js + + await client.transaction(tx => { + await tx.execute("insert User {name := 'Don'}"); + }); + +Note that we execute queries on the ``tx`` object in the above +example, rather than on the original ``client`` object. + +The ``transaction()`` API guarantees that: + +1. Transactions are executed atomically; +2. If a transaction fails due to retryable error (like + a network failure or a concurrent update error), the transaction + would be retried; +3. If any other, non-retryable error occurs, the transaction is rolled + back and the ``transaction()`` block throws. + +The *transaction* object exposes ``query()``, ``execute()``, ``querySQL()``, +``executeSQL()``, and other ``query*()`` methods that *clients* expose, with +the only difference that queries will run within the current transaction +and can be retried automatically. + +The key implication of retrying transactions is that the entire +nested code block can be re-run, including any non-querying +JavaScript code. Here is an example: + +.. code-block:: js + + const email = "timmy@edgedb.com" + + await client.transaction(async tx => { + await tx.execute( + `insert User { email := $email }`, + { email }, + ) + + await sendWelcomeEmail(email); + + await tx.execute( + `insert LoginHistory { + user := (select User filter .email = $email), + timestamp := datetime_current() + }`, + { email }, + ) + }) + +In the above example, the welcome email may be sent multiple times if the +transaction block is retried. Generally, the code inside the transaction block +shouldn't have side effects or run for a significant amount of time. + +.. note:: + + Transactions allocate expensive server resources and having + too many concurrently running long-running transactions will + negatively impact the performance of the DB server. + +.. note:: + + * RFC1004_ + * :js:meth:`Client.transaction\` + + .. _RFC1004: https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst + + +Next up +------- + +If you're a TypeScript user and want autocompletion and type inference, head +over to the :ref:`Query Builder docs `. If you're using plain +JavaScript that likes writing queries with composable code-first syntax, you +should check out the query builder too! If you're content writing queries as +strings, the vanilla Client API will meet your needs. diff --git a/docs/clients/js/for.rst b/docs/clients/js/for.rst new file mode 100644 index 00000000000..da1c7702ff3 --- /dev/null +++ b/docs/clients/js/for.rst @@ -0,0 +1,342 @@ +.. _edgedb-js-for: + + +For Loops +========= + +``for`` loops let you iterate over any set of values. + +.. code-block:: typescript + + const query = e.for(e.set(1, 2, 3, 4), (number) => { + return e.op(2, '^', number); + }); + const result = query.run(client); + // [2, 4, 8, 16] + +.. _edgedb-js-for-bulk-inserts: + +Bulk inserts +------------ + +It's common to use ``for`` loops to perform bulk inserts. The raw data is +passed in as a ``json`` parameter, converted to a set of ``json`` objects with +``json_array_unpack``, then passed into a ``for`` loop for insertion. + +.. code-block:: typescript + + const query = e.params({ items: e.json }, (params) => { + return e.for(e.json_array_unpack(params.items), (item) => { + return e.insert(e.Movie, { + title: e.cast(e.str, item.title), + release_year: e.cast(e.int64, item.release_year), + }); + }); + }); + + const result = await query.run(client, { + items: [ + { title: "Deadpool", release_year: 2016 }, + { title: "Deadpool 2", release_year: 2018 }, + { title: "Deadpool 3", release_year: 2024 }, + { title: "Deadpool 4", release_year: null }, + ], + }); + +Note that any optional properties values must be explicitly set to ``null``. +They cannot be set to ``undefined`` or omitted; doing so will cause a runtime +error. + +.. _edgedb-js-for-bulk-inserts-conflicts: + +Handling conflicts in bulk inserts +---------------------------------- + +Here's a more complex example, demonstrating how to complete a nested insert +with conflicts on the inner items. First, take a look at the schema for this +database: + +.. code-block:: sdl + + module default { + type Character { + required name: str { + constraint exclusive; + } + portrayed_by: str; + multi movies: Movie; + } + + type Movie { + required title: str { + constraint exclusive; + }; + release_year: int64; + } + } + +Note that the ``Movie`` type's ``title`` property has an exclusive constraint. + +Here's the data we want to bulk insert: + +.. code-block:: js + + [ + { + portrayed_by: "Robert Downey Jr.", + name: "Iron Man", + movies: ["Iron Man", "Iron Man 2", "Iron Man 3"] + }, + { + portrayed_by: "Chris Evans", + name: "Captain America", + movies: [ + "Captain America: The First Avenger", + "The Avengers", + "Captain America: The Winter Soldier", + ] + }, + { + portrayed_by: "Mark Ruffalo", + name: "The Hulk", + movies: ["The Avengers", "Iron Man 3", "Avengers: Age of Ultron"] + } + ] + +This is potentially a problem because some of the characters appear in the same +movies. We can't just naively insert all the movies because we'll eventually +hit a conflict. Since we're going to write this as a single query, chaining +``.unlessConflict`` on our query won't help. It only handles conflicts with +objects that existed *before* the current query. + +Let's look at a query that can accomplish this insert, and then we'll break it +down. + +.. code-block:: typescript + + const query = e.params( + { + characters: e.array( + e.tuple({ + portrayed_by: e.str, + name: e.str, + movies: e.array(e.str), + }) + ), + }, + (params) => { + const movies = e.for( + e.op( + "distinct", + e.array_unpack(e.array_unpack(params.characters).movies) + ), + (movieTitle) => { + return e + .insert(e.Movie, { + title: movieTitle, + }) + .unlessConflict((movie) => ({ + on: movie.title, + else: movie, + })); + } + ); + return e.with( + [movies], + e.for(e.array_unpack(params.characters), (character) => { + return e.insert(e.Character, { + name: character.name, + portrayed_by: character.portrayed_by, + movies: e.assert_distinct( + e.select(movies, (movie) => ({ + filter: e.op(movie.title, "in", e.array_unpack(character.movies)), + })) + ), + }); + }) + ); + } + ); + +.. _edgedb-js-for-bulk-inserts-conflicts-params: + +Structured params +~~~~~~~~~~~~~~~~~ + +.. code-block:: typescript + + const query = e.params( + { + characters: e.array( + e.tuple({ + portrayed_by: e.str, + name: e.str, + movies: e.array(e.str), + }) + ), + }, + (params) => { ... + +In raw EdgeQL, you can only have scalar types as parameters. We could mirror +that here with something like this: ``e.params({characters: e.json})``, but +this would then require us to cast all the values inside the JSON like +``portrayed_by`` and ``name``. + +By doing it this way — typing ``characters`` with ``e.array`` and the character +objects as named tuples by passing an object to ``e.tuple`` — all the data in +the array will be properly cast for us. It will also better type check the data +you pass to the query's ``run`` method. + +.. _edgedb-js-for-bulk-inserts-conflicting-data: + +Inserting the inner conflicting data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: typescript + + ... + (params) => { + const movies = e.for( + e.op("distinct", e.array_unpack(e.array_unpack(params.characters).movies)), + (movie) => { + return e + .insert(e.Movie, { + title: movie, + }) + .unlessConflict((movie) => ({ + on: movie.title, + else: movie, + })); + } + ); + ... + +We need to separate this movie insert query so that we can use ``distinct`` on +it. We could just nest an insert inside our character insert if movies weren't +duplicated across characters (e.g., two characters have "The Avengers" in +``movies``). Even though the query is separated from the character inserts +here, it will still be built as part of a single EdgeDB query using ``with`` +which we'll get to a bit later. + +The ``distinct`` operator can only operate on sets. We use ``array_unpack`` to +make these arrays into sets. We need to call it twice because +``params.characters`` is an array and ``.movies`` is an array nested inside +each character. + +Chaining ``unlessConflict`` takes care of any movies that already exist in the +database *before* we run this query, but it won't handle conflicts that come +about over the course of this query. The ``distinct`` operator we used earlier +pro-actively eliminates any conflicts we might have had among this data. + +.. _edgedb-js-for-bulk-inserts-outer-data: + +Inserting the outer data +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: typescript + + ... + return e.with( + [movies], + e.for(e.array_unpack(params.characters), (character) => { + return e.insert(e.Character, { + name: character.name, + portrayed_by: character.portrayed_by, + movies: e.assert_distinct( + e.select(movies, (movie) => ({ + filter: e.op(movie.title, "in", e.array_unpack(character.movies)), + })) + ), + }); + }) + ); + ... + +The query builder will try to automatically use EdgeQL's ``with``, but in this +instance, it doesn't know where to place the ``with``. By using ``e.with`` +explicitly, we break our movie insert out to the top-level of the query. By +default, it would be scoped *inside* the query, so our ``distinct`` operator +would be applied only to each character's movies instead of to all of the +movies. This would have caused the query to fail. + +The rest of the query is relatively straightforward. We unpack +``params.characters`` to a set so that we can pass it to ``e.for`` to iterate +over the characters. For each character, we build an ``insert`` query with +their ``name`` and ``portrayed_by`` values. + +For the character's ``movies``, we ``select`` everything in the +``movies`` insert query we wrote previously, filtering for those with titles +that match values in the ``character.movies`` array. + +All that's left is to run the query, passing the data to the query's ``run`` +method! + +.. _edgedb-js-for-bulk-updates: + +Bulk updates +^^^^^^^^^^^^ + +Just like with inserts, you can run bulk updates using a ``for`` loop. Pass in +your data, iterate over it, and build an ``update`` query for each item. + +In this example, we use ``name`` to filter for the character to be updated +since ``name`` has an exclusive constraint in the schema (meaning a given name +will correspond to, at most, a single object). That filtering is done using the +``filter_single`` property of the object returned from your ``update`` +callback. Then the ``last_appeared`` value is updated by including it in the +nested ``set`` object. + +.. code-block:: typescript + + const query = e.params( + { + characters: e.array( + e.tuple({ + name: e.str, + last_appeared: e.int64, + }) + ), + }, + (params) => { + return e.for(e.array_unpack(params.characters), (character) => { + return e.update(e.Character, () => ({ + filter_single: { name: character.name }, + set: { + last_appeared: character.last_appeared, + }, + })); + }); + } + ); + + await query.run(client, { + characters: [ + { name: "Iron Man", last_appeared: 2019 }, + { name: "Captain America", last_appeared: 2019 }, + { name: "The Hulk", last_appeared: 2021 }, + ], + }); + +e.for vs JS for or .forEach +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You may be tempted to use JavaScript's ``for`` or the JavaScript array's +``.forEach`` method to avoid having to massage your data into a set for +consumption by ``e.for``. This approach comes at a cost of performance. + +If you use ``for`` or ``.forEach`` to iterate over a standard JavaScript data +structure and run separate queries for each item in your iterable, you're doing +just that: running separate queries for each item in your iterable. By +iterating inside your query using ``e.for``, you're guaranteed everything will +happen in a single query. + +In addition to the performance implications, a single query means that either +everything succeeds or everything fails. You will never end up with only some +of your data inserted. This ensures your data integrity is maintained. You +could achieve this yourself by wrapping your batch queryies with :ref:`a +transaction `, but a single query is already atomic +without any additional work on your part. + +Using ``e.for`` to run a single query is generally the best approach. When +dealing with extremely large datasets, it may become more practical to batch +queries and run them individually. diff --git a/docs/clients/js/funcops.rst b/docs/clients/js/funcops.rst new file mode 100644 index 00000000000..fd38ef4054f --- /dev/null +++ b/docs/clients/js/funcops.rst @@ -0,0 +1,81 @@ +.. _edgedb-js-funcops: + +Functions and Operators +----------------------- + +Function syntax +^^^^^^^^^^^^^^^ + +All built-in standard library functions are reflected as functions in ``e``. + +.. code-block:: typescript + + e.str_upper(e.str("hello")); + // str_upper("hello") + + e.op(e.int64(2), '+', e.int64(2)); + // 2 + 2 + + const nums = e.set(e.int64(3), e.int64(5), e.int64(7)) + e.op(e.int64(4), 'in', nums); + // 4 in {3, 5, 7} + + e.math.mean(nums); + // math::mean({3, 5, 7}) + + +.. _edgedb-js-funcops-prefix: + +Prefix operators +^^^^^^^^^^^^^^^^ + +Unlike functions, operators do *not* correspond to a top-level function on the +``e`` object. Instead, they are expressed with the ``e.op`` function. + +Prefix operators operate on a single argument: ``OPERATOR ``. + +.. code-block:: typescript + + e.op('not', e.bool(true)); // not true + e.op('exists', e.set('hi')); // exists {'hi'} + e.op('distinct', e.set('hi', 'hi')); // distinct {'hi', 'hi'} + +.. list-table:: + + * - ``"exists"`` ``"distinct"`` ``"not"`` + + +.. _edgedb-js-funcops-infix: + +Infix operators +^^^^^^^^^^^^^^^ + +Infix operators operate on two arguments: `` OPERATOR ``. + +.. code-block:: typescript + + e.op(e.str('Hello '), '++', e.str('World!')); + // 'Hello ' ++ 'World!' + +.. list-table:: + + * - ``"="`` ``"?="`` ``"!="`` ``"?!="`` ``">="`` ``">"`` ``"<="`` ``"<"`` + ``"or"`` ``"and"`` ``"+"`` ``"-"`` ``"*"`` ``"/"`` ``"//"`` ``"%"`` + ``"^"`` ``"in"`` ``"not in"`` ``"union"`` ``"??"`` ``"++"`` ``"like"`` + ``"ilike"`` ``"not like"`` ``"not ilike"`` + + +.. _edgedb-js-funcops-ternary: + +Ternary operators +^^^^^^^^^^^^^^^^^ + +Ternary operators operate on three arguments: `` OPERATOR OPERATOR +``. Currently there's only one ternary operator: the ``if else`` +statement. + +.. code-block:: typescript + + e.op(e.str('😄'), 'if', e.bool(true), 'else', e.str('😢')); + // 😄 if true else 😢 + diff --git a/docs/clients/js/generation.rst b/docs/clients/js/generation.rst new file mode 100644 index 00000000000..759e3133306 --- /dev/null +++ b/docs/clients/js/generation.rst @@ -0,0 +1,122 @@ +.. _edgedb-js-generators: + +Generators +========== + +The ``@edgedb/generate`` package provides a set of code generation tools that +are useful when developing an EdgeDB-backed applications with +TypeScript/JavaScript. + +To get started with generators, first initialize an :ref:`EdgeDB project +` in the root of your application. Generators will +look for an ``edgedb.toml`` file to determine the root of your application. See +the :ref:`Overview ` page for details on installing. + +.. note:: + + Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema. + +Run a generator with the following command. + +.. tabs:: + + .. code-tab:: bash + :caption: npm + + $ npx @edgedb/generate [options] + + .. code-tab:: bash + :caption: yarn + + $ yarn run -B generate [options] + + .. code-tab:: bash + :caption: pnpm + + $ pnpm exec generate [options] + + .. code-tab:: bash + :caption: Deno + + $ deno run \ + --allow-all \ + --unstable \ + https://deno.land/x/edgedb/generate.ts [options] + + .. code-tab:: bash + :caption: bun + + $ bunx @edgedb/generate [options] + +The value of ```` should be one of the following: + +.. list-table:: + :class: funcoptable + + * - ``edgeql-js`` + - Generates the query builder which provides a **code-first** way to write + **fully-typed** EdgeQL queries with TypeScript. We recommend it for + TypeScript users, or anyone who prefers writing queries with code. + - :ref:`docs ` + + * - ``queries`` + - Scans your project for ``*.edgeql`` files and generates functions that + allow you to execute these queries in a typesafe way. + - :ref:`docs ` + + * - ``interfaces`` + - Introspects your schema and generates file containing *TypeScript + interfaces* that correspond to each object type. This is useful for + writing typesafe code to interact with EdgeDB. + - :ref:`docs ` + +Connection +^^^^^^^^^^ + +The generators require a connection to an active EdgeDB database. It does +**not** simply read your local ``.esdl`` schema files. Generators rely on the +database to introspect the schema and analyze queries. Doing so without a +database connection would require implementing a full EdgeQL parser and static +analyzer in JavaScript—which we don't intend to do anytime soon. + +.. note:: + + Make sure your development database is up-to-date with your latest schema + before running a generator! + +If you're using ``edgedb project init``, the connection is automatically handled +for you. Otherwise, you'll need to explicitly pass connection information via +environment variables or CLI flags, just like any other CLI command. See +:ref:`Client Libraries > Connection ` for guidance. + +.. _edgedb_qb_target: + +Targets +^^^^^^^ + +All generators look at your environment and guess what kind of files to generate +(``.ts`` vs ``.js + .d.ts``) and what module system to use (CommonJS vs ES +modules). You can override this with the ``--target`` flag. + +.. list-table:: + + * - ``--target ts`` + - Generate TypeScript files (``.ts``) + * - ``--target mts`` + - Generate TypeScript files (``.mts``) with extensioned ESM imports + * - ``--target esm`` + - Generate ``.js`` with ESM syntax and ``.d.ts`` declaration files + * - ``--target cjs`` + - Generate JavaScript with CommonJS syntax and and ``.d.ts`` declaration + files + * - ``--target deno`` + - Generate TypeScript files with Deno-style ESM imports + +Help +^^^^ + +To see helptext for the ``@edgedb/generate`` command, run the following. + +.. code-block:: bash + + $ npx @edgedb/generate --help diff --git a/docs/clients/js/group.rst b/docs/clients/js/group.rst new file mode 100644 index 00000000000..63fbf7525a5 --- /dev/null +++ b/docs/clients/js/group.rst @@ -0,0 +1,269 @@ +.. _edgedb-js-group: + +Group +===== + +.. note:: + + The ``group`` statement is only available in EdgeDB 2.0 or later. + +The ``group`` statement provides a powerful mechanism for categorizing a set +of objects (e.g., movies) into *groups*. You can group by properties, +expressions, or combinatations thereof. + +.. note:: + + This page does not aim to describe how the ``group`` statement works, merely + the syntax for writing ``e.group`` statements with the query builder. For + full documentation, refer to :ref:`EdgeQL > Group `. + +Simple grouping +--------------- + +Sort a set of objects by a simple property. + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + return { + by: {release_year: movie.release_year} + } + }); + /* + [ + { + key: {release_year: 2008}, + grouping: ["release_year"], + elements: [{id: "..."}, {id: "..."}] + }, + { + key: { release_year: 2009 }, + grouping: ["release_year"], + elements: [{id: "..."}, {id: "..."}] + }, + // ... + ] + */ + + .. code-tab:: edgeql + + group Movie + by .release_year + +Add a shape that will be applied to ``elements``. The ``by`` key is a special +key, similar to ``filter``, etc. in ``e.select``. All other keys are +interpreted as *shape elements* and support the same functionality as +``e.select`` (nested shapes, computeds, etc.). + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + return { + title: true, + actors: {name: true}, + num_actors: e.count(movie.characters), + by: {release_year: movie.release_year} + } + }); + /* [ + { + key: {release_year: 2008}, + grouping: ["release_year"], + elements: [{ + title: "Iron Man", + actors: [...], + num_actors: 5 + }, { + title: "The Incredible Hulk", + actors: [...], + num_actors: 3 + }] + }, + // ... + ] */ + + .. code-tab:: edgeql + + group Movie { + title, + num_actors := count(.actors) + } + by .release_year + +Group by a tuple of properties. + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + const release_year = movie.release_year; + const first_letter = movie.title[0]; + return { + title: true, + by: {release_year, first_letter} + }; + }); + /* + [ + { + key: {release_year: 2008, first_letter: "I"}, + grouping: ["release_year", "first_letter"], + elements: [{title: "Iron Man"}] + }, + { + key: {release_year: 2008, first_letter: "T"}, + grouping: ["release_year", "first_letter"], + elements: [{title: "The Incredible Hulk"}] + }, + // ... + ] + */ + + .. code-tab:: edgeql + + group Movie { title } + using first_letter := .title[0] + by .release_year, first_letter + +Using grouping sets to group by several expressions simultaneously. + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + const release_year = movie.release_year; + const first_letter = movie.title[0]; + return { + title: true, + by: e.group.set({release_year, first_letter}) + }; + }); + /* [ + { + key: {release_year: 2008}, + grouping: ["release_year"], + elements: [{title: "Iron Man"}, {title: "The Incredible Hulk"}] + }, + { + key: {first_letter: "I"}, + grouping: ["first_letter"], + elements: [{title: "Iron Man"}, {title: "Iron Man 2"}, {title: "Iron Man 3"}], + }, + // ... + ] */ + + .. code-tab:: edgeql + + group Movie { title } + using first_letter := .title[0] + by {.release_year, first_letter} + + +Using a combination of tuples and grouping sets. + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + const release_year = movie.release_year; + const first_letter = movie.title[0]; + const cast_size = e.count(movie.actors); + return { + title: true, + by: e.group.tuple(release_year, e.group.set({first_letter, cast_size})) + // by .release_year, { first_letter, cast_size } + // equivalent to + // by (.release_year, first_letter), (.release_year, cast_size), + }; + }); + /* [ + { + key: {release_year: 2008, first_letter: "I"}, + grouping: ["release_year", "first_letter"], + elements: [{title: "Iron Man"}] + }, + { + key: {release_year: 2008, cast_size: 3}, + grouping: ["release_year", "cast_size"], + elements: [{title: "The Incredible Hulk"}] + }, + // ... + ] */ + + .. code-tab:: edgeql + + group Movie { title } + using + first_letter := .title[0], + cast_size := count(.actors) + by .release_year, {first_letter, cast_size} + + + +The ``group`` statement provides a syntactic sugar for defining certain common +grouping sets: ``cube`` and ``rollup``. Here's a quick primer on how they work: + +.. code-block:: + + ROLLUP (a, b, c) + is equivalent to + {(), (a), (a, b), (a, b, c)} + + CUBE (a, b) + is equivalent to + {(), (a), (b), (a, b)} + +To use these in the query builder use the ``e.group.cube`` and +``e.group.rollup`` functions. + + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + const release_year = movie.release_year; + const first_letter = movie.title[0]; + const cast_size = e.count(movie.actors); + return { + title: true, + by: e.group.rollup({release_year, first_letter, cast_size}) + }; + }); + + .. code-tab:: edgeql + + group Movie { title } + using + first_letter := .title[0], + cast_size := count(.actors) + by rollup(.release_year, first_letter, cast_size) + +.. tabs:: + + .. code-tab:: typescript + + e.group(e.Movie, movie => { + const release_year = movie.release_year; + const first_letter = movie.title[0]; + const cast_size = e.count(movie.actors); + return { + title: true, + by: e.group.cube({release_year, first_letter, cast_size}) + }; + }); + + .. code-tab:: edgeql + + group Movie { title } + using + first_letter := .title[0], + cast_size := count(.actors) + by cube(.release_year, first_letter, cast_size) diff --git a/docs/clients/js/index.rst b/docs/clients/js/index.rst index 6a6a657e1cd..be5b75017d3 100644 --- a/docs/clients/js/index.rst +++ b/docs/clients/js/index.rst @@ -1,9 +1,407 @@ .. _edgedb-js-intro: +=========================== +EdgeDB TypeScript/JS Client +=========================== + +.. toctree:: + :maxdepth: 3 + :hidden: + + driver + generation + queries + interfaces + querybuilder + literals + types + funcops + parameters + objects + select + insert + update + delete + with + for + group + reference + +This is the official EdgeDB client library for JavaScript and TypeScript. It’s +the easiest way to connect to your database and execute queries from a Node.js +or Deno backend. + +.. _edgedb-js-installation: + + +Installation +============ + +You can install the published database driver and optional (but recommended!) +generators from npm using your package manager of choice. + +.. tabs:: + + .. code-tab:: bash + :caption: npm + + $ npm install --save-prod edgedb # database driver + $ npm install --save-dev @edgedb/generate # generators + + .. code-tab:: bash + :caption: yarn + + $ yarn add edgedb # database driver + $ yarn add --dev @edgedb/generate # generators + + .. code-tab:: bash + :caption: pnpm + + $ pnpm add --save-prod edgedb # database driver + $ pnpm add --save-dev @edgedb/generate # generators + + .. code-tab:: typescript + :caption: deno + + import * as edgedb from "http://deno.land/x/edgedb/mod.ts"; + + .. code-tab:: bash + :caption: bun + + $ bun add edgedb # database driver + $ bun add --dev @edgedb/generate # generators + +.. note:: Deno users + + Create these two files in your project root: + + .. code-block:: json + :caption: importMap.json + + { + "imports": { + "edgedb": "https://deno.land/x/edgedb/mod.ts", + "edgedb/": "https://deno.land/x/edgedb/" + } + } + + .. code-block:: json + :caption: deno.js + + { + "importMap": "./importMap.json" + } + + +.. _edgedb-js-quickstart: + +Quickstart ========== -JavaScript -========== -The documentation for the JavaScript client is automatically pulled -from https://github.com/edgedb/edgedb-js/tree/master/docs by the -build pipeline of the edgedb.com website. +Setup +^^^^^ + +This section assumes you have gone through the :ref:`Quickstart Guide +` and understand how to update schemas, run migrations, and have +a working EdgeDB project. Let's update the schema to make the ``title`` property +of the ``Movie`` type exclusive. This will help with filtering by +``Movie.title`` in our queries. + +.. code-block:: sdl-diff + :caption: dbschema/default.esdl + + module default { + type Person { + required name: str; + } + + type Movie { + - required title: str; + + required title: str { + + constraint exclusive; + + }; + multi actors: Person; + } + } + +Generate the new migration and apply them: + +.. code-block:: bash + + $ edgedb migration create + $ edgedb migrate + +We'll be using TypeScript and Node for this example, so let's setup a simple +app: + +.. code-block:: bash + + $ npm init -y # initialize a new npm project + $ npm i edgedb + $ npm i -D typescript @types/node @edgedb/generate tsx + $ npx tsc --init # initialize a basic TypeScript project + +Client +^^^^^^ + +The ``Client`` class implements the core functionality required to establish a +connection to your database and execute queries. If you prefer writing queries +as strings, the Client API is all you need. + +Let's create a simple Node.js script that seeds the database by running an +insert query directly with the driver: + +.. code-block:: typescript + :caption: seed.ts + + import * as edgedb from "edgedb"; + + const client = edgedb.createClient(); + + async function main() { + await client.execute(` + insert Person { name := "Robert Downey Jr." }; + insert Person { name := "Scarlett Johansson" }; + insert Movie { + title := $title, + actors := ( + select Person filter .name in { + "Robert Downey Jr.", + "Scarlett Johansson" + } + ) + } + `, { title: "Iron Man 2" }); + } + + main(); + +We can now seed the database by running this script with ``tsx`` + +.. code-block:: bash + + $ npx tsx seed.ts + +Feel free to explore the database in the :ref:`EdgeDB UI `, +where you will find the new data you inserted through this script, as well as +any data you inserted when running the Quickstart. + +.. note:: A word on module systems + + Different build tools and runtimes have different specifications for how + modules are imported, and we support a wide-range of those styles. For + clarity, we will be sticking to standard TypeScript-style ESM module importing + without a file extension throughout this documentation. Please see your build + or environment tooling's guidance on how to adapt this style. + +Querying with plain strings +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Now, let's write a Node.js script that queries the database for details about +Iron Man 2: + +.. code-block:: typescript + :caption: query.ts + + import * as edgedb from "edgedb"; + + const client = edgedb.createClient(); + + async function main() { + const result = await client.querySingle(` + select Movie { + id, + title, + actors: { + id, + name, + } + } filter .title = "Iron Man 2" + `); + + console.log(JSON.stringify(result, null, 2)); + } + + main(); + +Interfaces +^^^^^^^^^^ + +Since we're using TypeScript, it would be nice to be able to type the return +value of this query, so let's use our first generator, the :ref:`interfaces +generator ` to tell TypeScript what the type of our result +is. + +First we run the generator: + +.. code-block:: bash + + $ npx @edgedb/generate interfaces + +This generator introspects your database schema and generates a set of +equivalent TypeScript interfaces. + +Now we can annotate our query since we are selecting the whole ``Movie`` type: + +.. code-block:: typescript-diff + :caption: query.ts + + import * as edgedb from "edgedb"; + import { Movie } from "./dbschema/interfaces" + + const client = edgedb.createClient(); + + async function main() { + // result will be inferred as Movie | null + - const result = await client.querySingle(` + + const result = await client.querySingle(` + select Movie { + id, + title, + actors: { + id, + name, + } + } filter .title = "Iron Man 2" + `); + + console.log(JSON.stringify(result, null, 2)); + } + + main(); + +You can now run the script with ``tsx``: + +.. code-block:: bash + + $ npx tsx query.ts + +Queries generator +^^^^^^^^^^^^^^^^^ + +Wouldn't it be great if we could write any arbitrary query and get a type-safe +function that we could call? Good news, that's exactly what the next generator +does! The :ref:`queries generator ` scans your project for +``*.edgeql`` files and generates a file containing a strongly-typed function. + +First, move the query into a separate file called ``getMovie.edgeql``. + +.. code-block:: edgeql + :caption: getMovie.edgeql + + select Movie { + id, + title, + actors: { + id, + name, + } + }; + + +Next, we'll run the ``queries`` generator, specifying the ``--file`` option +which will compile all the queries it finds into a single TypeScript module: + +.. code-block:: bash + + $ npx @edgedb/generate queries --file + +Now, let's update our query script to call the generated function, which will +provide us with type-safe querying. + +.. code-block:: typescript-diff + :caption: query.ts + + import * as edgedb from "edgedb"; + - import { Movie } from "./dbschema/interfaces" + + import { getMovie } from "./dbschema/queries" + + const client = edgedb.createClient(); + + async function main() { + // result will be inferred as Movie | null + - const result = await client.querySingle(` + - select Movie { + - id, + - title, + - actors: { + - id, + - name, + - } + - } filter .title = "Iron Man 2" + - `); + + const result = await getMovie(client); + + console.log(JSON.stringify(result, null, 2)); + } + + main(); + +Now, if you change the query to return different data, or take parameters, and +run the queries generator again, the type of the newly generated function will +change. It'll be completely type safe! + +Query builder +^^^^^^^^^^^^^ + +At last we've arrived at the most powerful API for querying your EdgeDB +instance: the query builder. The EdgeDB query builder provides a **code-first** +way to write **fully-typed** EdgeQL queries with TypeScript. We recommend it for +TypeScript users, or anyone who prefers writing queries with code. + +First, we'll run the query builder generator: + +.. code-block:: bash + + $ npx @edgedb/generate edgeql-js + +.. note:: Version control + + The first time you run the generator, you'll be prompted to add the generated + files to your ``.gitignore``. Confirm this prompt to automatically add a line + to your ``.gitignore`` that excludes the generated files. + + For consistency, we recommend omitting the generated files from version + control and re-generating them as part of your deployment process. However, + there may be circumstances where checking the generated files into version + control is desirable, e.g. if you are building Docker images that must contain + the full source code of your application. + +Now, we can import the generated query builder and express our query completely +in TypeScript, getting editor completion, type checking, and type inferrence: + +.. code-block:: typescript-diff + :caption: query.ts + + import * as edgedb from "edgedb"; + - import { getMovie } from "./dbschema/queries"; + + import e from "./dbschema/edgeql-js"; + + const client = edgedb.createClient(); + + async function main() { + - // result will be inferred as Movie | null + + // result will be inferred based on the query + - const result = await getMovie(client); + + const result = await e + + .select(e.Movie, () => ({ + + id: true, + + title: true, + + actors: () => ({ id: true, name: true }), + + filter_single: { title: "Iron Man 2" }, + + })) + + .run(client); + + console.log(JSON.stringify(result, null, 2)); + } + + main(); + +What's next +=========== + +We recommend reading the :ref:`client docs ` first and getting +familiar with configuring the client. You'll find important APIs like +``withGlobals`` and connection details there. After that, depending on your +preferences, look through the :ref:`query builder ` documentation +and use the other pages as a reference for writing code-first EdgeDB queries. diff --git a/docs/clients/js/insert.rst b/docs/clients/js/insert.rst new file mode 100644 index 00000000000..d246b04f01d --- /dev/null +++ b/docs/clients/js/insert.rst @@ -0,0 +1,162 @@ +.. _edgedb-js-insert: + +Insert +------ + +Insert new data with ``e.insert``. + +.. code-block:: typescript + + e.insert(e.Movie, { + title: e.str("Spider-Man: No Way Home"), + release_year: e.int64(2021) + }); + +For convenience, the second argument of ``e.insert`` function can also accept +plain JS data or a named tuple. + +.. code-block:: typescript + + e.insert(e.Movie, { + title: "Spider-Man: No Way Home", + actors: e.select(e.Person, person => ({ + filter: e.op(person.name, "=", "Robert Downey Jr."), + '@character_name': e.str("Iron Man") + })) + }); + + +.. code-block:: typescript + + e.params({ + movie: e.tuple({ + title: e.str, + release_year: e.int64 + }) + }, $ => + e.insert(e.Movie, $.movie) + ); + + +Link properties +^^^^^^^^^^^^^^^ + +As in EdgeQL, link properties are inserted inside the shape of a subquery. + +.. code-block:: typescript + + const query = e.insert(e.Movie, { + title: "Iron Man", + actors: e.select(e.Person, person => ({ + filter_single: {name: "Robert Downey Jr."}, + "@character_name": e.str("Tony Stark") + + // link props must correspond to expressions + "@character_name": "Tony Stark" // invalid + )) + }); + + +.. note:: + + For technical reasons, link properties must correspond to query + builder expressions, not plain JS data. + +Similarly you can directly include link properties inside nested ``e.insert`` +queries: + +.. code-block:: typescript + + const query = e.insert(e.Movie, { + title: "Iron Man", + release_year: 2008, + actors: e.insert(e.Person, { + name: "Robert Downey Jr.", + "@character_name": e.str("Tony Start") + }), + }); + +Handling conflicts +^^^^^^^^^^^^^^^^^^ +:index: querybuilder unlessconflict unless conflict constraint + +In EdgeQL, "upsert" functionality is achieved by handling **conflicts** on +``insert`` statements with the ``unless conflict`` clause. In the query +builder, this is possible with the ``.unlessConflict`` method (available only +on ``insert`` expressions). + +In the simplest case, adding ``.unlessConflict`` (no arguments) will prevent +EdgeDB from throwing an error if the insertion would violate an exclusivity +contstraint. Instead, the query returns an empty set (``null``). + +.. code-block:: typescript + + e.insert(e.Movie, { + title: "Spider-Man: No Way Home", + release_year: 2021 + }).unlessConflict(); + // => null + + +Provide an ``on`` clause to "catch" conflicts only on a specific property/link. + +.. code-block:: typescript + + e.insert(e.Movie, { + title: "Spider-Man: No Way Home", + release_year: 2021 + }).unlessConflict(movie => ({ + on: movie.title, // can be any expression + })); + + +You can also provide an ``else`` expression which will be executed and returned +in case of a conflict. You must specify an ``on`` clause in order to use ``else``. + +The following query simply returns the pre-existing (conflicting) object. + +.. code-block:: typescript + + e.insert(e.Movie, { + title: "Spider-Man: Homecoming", + release_year: 2021 + }).unlessConflict(movie => ({ + on: movie.title, + else: movie + })); + + +Or you can perform an upsert operation with an ``e.update`` in the ``else``. + +.. code-block:: typescript + + e.insert(e.Movie, { + title: "Spider-Man: Homecoming", + release_year: 2021 + }).unlessConflict(movie => ({ + on: movie.title, + else: e.update(movie, () => ({ + set: { + release_year: 2021 + } + })), + })); + + +If the constraint you're targeting is a composite constraint, wrap the +properties in a tuple. + +.. code-block:: typescript + + e.insert(e.Movie, { + title: "Spider-Man: No Way Home", + release_year: 2021 + }).unlessConflict(movie => ({ + on: e.tuple([movie.title, movie.release_year]) + })); + +Bulk inserts +^^^^^^^^^^^^ + +You can use a :ref:`for loop ` to perform :ref:`bulk inserts +`. diff --git a/docs/clients/js/interfaces.rst b/docs/clients/js/interfaces.rst new file mode 100644 index 00000000000..0a4b4c1bfab --- /dev/null +++ b/docs/clients/js/interfaces.rst @@ -0,0 +1,189 @@ +.. _edgedb-js-interfaces: + +==================== +Interfaces Generator +==================== + +The ``interfaces`` generator introspects your schema and generates file containing *TypeScript interfaces* that correspond to each object type. This is useful for writing typesafe code to interact with EdgeDB. + +Installation +------------ + +To get started, install the following packages. (If you're using Deno, you can skip this step.) + +Install the ``edgedb`` package. + +.. code-block:: bash + + $ npm install edgedb # npm users + $ yarn add edgedb # yarn users + $ bun add edgedb # bun users + +Then install ``@edgedb/generate`` as a dev dependency. + +.. code-block:: bash + + $ npm install @edgedb/generate --save-dev # npm users + $ yarn add @edgedb/generate --dev # yarn users + $ bun add --dev @edgedb/generate # bun users + + +Generation +---------- + +Assume your database contains the following EdgeDB schema. + +.. code-block:: sdl + + module default { + type Person { + required name: str; + } + + scalar type Genre extending enum; + + type Movie { + required title: str; + genre: Genre; + multi actors: Person; + } + } + +The following command will run the ``interfaces`` generator. + +.. tabs:: + + .. code-tab:: bash + :caption: Node.js + + $ npx @edgedb/generate interfaces + + .. code-tab:: bash + :caption: Deno + + $ deno run --allow-all --unstable https://deno.land/x/edgedb/generate.ts interfaces + + .. code-tab:: bash + :caption: Bun + + $ bunx @edgedb/generate interfaces + +.. note:: Deno users + + Create these two files in your project root: + + .. code-block:: json + :caption: importMap.json + + { + "imports": { + "edgedb": "https://deno.land/x/edgedb/mod.ts", + "edgedb/": "https://deno.land/x/edgedb/" + } + } + + .. code-block:: json + :caption: deno.js + + { + "importMap": "./importMap.json" + } + +This will introspect your schema and generate TypeScript interfaces that correspond to each object type. By default, these interfaces will be written to a single file called ``interfaces.ts`` into the ``dbschema`` directory in your project root. The file will contain the following contents (roughly): + +.. code-block:: typescript + + export interface Person { + id: string; + name: string; + } + + export type Genre = "Horror" | "Comedy" | "Drama"; + + export interface Movie { + id: string; + title: string; + genre?: Genre | null; + actors: Person[]; + } + +Any types declared in a non-``default`` module will be generated into an accordingly named ``namespace``. + +.. note:: + + Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema. + + +Customize file path +~~~~~~~~~~~~~~~~~~~ + +Pass a ``--file`` flag to specify the output file path. + +.. code-block:: bash + + $ npx @edgedb/generate interfaces --file schema.ts + +If the value passed as ``--file`` is a relative path, it will be evaluated relative to the current working directory (``process.cwd()``). If the value is an absolute path, it will be used as-is. + +.. note:: + + Because this generator is TypeScript-specific, the ``--target`` flag is not supported as in other generators. + + +Version control +~~~~~~~~~~~~~~~ + +To exclude the generated file, add the following lines to your ``.gitignore`` file. + +.. code-block:: text + + dbschema/interfaces.ts + +Usage +----- + +The generated interfaces can be imported like so. + +.. code-block:: typescript + + import {Genre, Movie} from "./dbschema/interfaces"; + +You will need to manipulate the generated interfaces to match your application's needs. For example, you may wish to strip the ``id`` property for a ``createMovie`` mutation. + +.. code-block:: typescript + + function createMovie(data: Omit) { + // ... + } + +.. note:: + + Refer to the `TypeScript docs `_ for information about built-in utility types like ``Pick``, ``Omit``, and ``Partial``. + +For convenience, the file also exports a namespace called ``helper`` containing a couple useful utilities for extracting the properties or links from an object type interface. + +.. code-block:: typescript + + import {Movie, helper} from "./dbschema/interfaces"; + + type MovieProperties = helper.Props; + // { id: string; title: string; ... } + + type MovieLinks = helper.Links; + // { actors: Person[]; } + + +Enums +~~~~~ + +Note that an ``enum`` in your schema will be represented in the generated code as a union of string literals. + +.. code-block:: typescript + + export type Genre = "Horror" | "Comedy" | "Drama"; + +We do *not* generate TypeScript enums for a number of reasons. + +- In TypeScript, enums are nominally typed. Two identically named enums are not + considered equal, even if they have the same members. +- Enums are both a runtime and static construct. Hovever, for simplicity we want the ``interfaces`` generator to produce exclusively static (type-level) code. diff --git a/docs/clients/js/literals.rst b/docs/clients/js/literals.rst new file mode 100644 index 00000000000..68ffad2c744 --- /dev/null +++ b/docs/clients/js/literals.rst @@ -0,0 +1,417 @@ +.. _edgedb-js-literals: + + +Literals +-------- + +The query builder provides a set of "helper functions" that convert JavaScript +literals into *expressions* that can be used in queries. For the most part, +these helper functions correspond to the *name* of the type. + + + +Primitives +^^^^^^^^^^ + +Primitive literal expressions are created using constructor functions that +correspond to EdgeDB datatypes. Each expression below is accompanied by the +EdgeQL it produces. + +.. code-block:: typescript + + e.str("asdf") // "asdf" + e.int64(123) // 123 + e.float64(123.456) // 123.456 + e.bool(true) // true + e.bigint(12345n) // 12345n + e.decimal("1234.1234n") // 1234.1234n + e.uuid("599236a4...") // "599236a4..." + + e.bytes(Uint8Array.from('binary data')); + // b'binary data' + +Strings +^^^^^^^ + +String expressions have some special functionality: they support indexing and +slicing, as in EdgeQL. + +.. code-block:: typescript + + const myString = e.str("hello world"); + + myString[5]; // "hello world"[5] + myString['2:5']; // "hello world"[2:5] + myString[':5']; // "hello world"[:5] + myString['2:']; // "hello world"[2:] + +There are also equivalent ``.index`` and ``.slice`` methods that can accept +integer expressions as arguments. + +.. code-block:: typescript + + const myString = e.str("hello world"); + const start = e.int64(2); + const end = e.int64(5); + + myString.index(start); // "hello world"[2] + myString.slice(start, end); // "hello world"[2:5] + myString.slice(null, end); // "hello world"[:5] + myString.slice(start, null); // "hello world"[2:] + +Enums +^^^^^ + +Enum literals are available as properties defined on the enum type. + +.. code-block:: typescript + + e.Colors.green; + // Colors.green; + + e.sys.VersionStage.beta; + // sys::VersionStage.beta + +Dates and times +^^^^^^^^^^^^^^^ + +To create an instance of ``datetime``, pass a JavaScript ``Date`` object into +``e.datetime``: + +.. code-block:: typescript + + e.datetime(new Date('1999-01-01')); + // '1999-01-01T00:00:00.000Z' + +EdgeDB's other temporal datatypes don't have equivalents in the JavaScript +type system: ``duration``, ``cal::relative_duration``, ``cal::date_duration``, +``cal::local_date``, ``cal::local_time``, and ``cal::local_datetime``, + +To resolve this, each of these datatypes can be represented with an instance +of a corresponding class, as defined in ``edgedb`` module. Clients use +these classes to represent these values in query results; they are documented +on the :ref:`Client API ` docs. + +.. list-table:: + + * - ``e.duration`` + - :js:class:`Duration` + * - ``e.cal.relative_duration`` + - :js:class:`RelativeDuration` + * - ``e.cal.date_duration`` + - :js:class:`DateDuration` + * - ``e.cal.local_date`` + - :js:class:`LocalDate` + * - ``e.cal.local_time`` + - :js:class:`LocalTime` + * - ``e.cal.local_datetime`` + - :js:class:`LocalDateTime` + * - ``e.cal.local_datetime`` + - :js:class:`LocalDateTime` + * - ``e.cal.local_datetime`` + - :js:class:`LocalDateTime` + +The code below demonstrates how to declare each kind of temporal literal, +along with the equivalent EdgeQL. + +.. code-block:: typescript + + import * as edgedb from "edgedb"; + + const myDuration = new edgedb.Duration(0, 0, 0, 0, 1, 2, 3); + e.duration(myDuration); + + const myLocalDate = new edgedb.LocalDate(1776, 7, 4); + e.cal.local_date(myLocalDate); + + const myLocalTime = new edgedb.LocalTime(13, 15, 0); + e.cal.local_time(myLocalTime); + + const myLocalDateTime = new edgedb.LocalDateTime(1776, 7, 4, 13, 15, 0); + e.cal.local_datetime(myLocalDateTime); + + +You can also declare these literals by casting an appropriately formatted +``str`` expression, as in EdgeQL. Casting :ref:`is documented +` in more detail later in the docs. + +.. code-block:: typescript + + e.cast(e.duration, e.str('5 minutes')); + // '5 minutes' + + e.cast(e.cal.local_datetime, e.str('1999-03-31T15:17:00')); + // '1999-03-31T15:17:00' + + e.cast(e.cal.local_date, e.str('1999-03-31')); + // '1999-03-31' + + e.cast(e.cal.local_time, e.str('15:17:00')); + // '15:17:00' + + +JSON +^^^^ + +JSON literals are created with the ``e.json`` function. You can pass in any +EdgeDB-compatible data structure. + + +What does "EdgeDB-compatible" mean? It means any JavaScript data structure +with an equivalent in EdgeDB: strings, number, booleans, ``bigint``\ s, +``Uint8Array``\ s, ``Date``\ s, and instances of EdgeDB's built-in classes: +(``LocalDate`` ``LocalTime``, ``LocalDateTime``, ``DateDuration``, +``Duration``, and ``RelativeDuration``), and any array or object of these +types. Other JavaScript data structures like symbols, instances of custom +classes, sets, maps, and `typed arrays `_ are not supported. + +.. code-block:: typescript + + const query = e.json({ name: "Billie" }) + // to_json('{"name": "Billie"}') + + const data = e.json({ + name: "Billie", + numbers: [1,2,3], + nested: { foo: "bar"}, + duration: new edgedb.Duration(1, 3, 3) + }) + +JSON expressions support indexing, as in EdgeQL. The returned expression also +has a ``json`` type. + +.. code-block:: typescript + + const query = e.json({ numbers: [0,1,2] }); + + query.toEdgeQL(); // to_json((numbers := [0,1,2])) + + query.numbers[0].toEdgeQL(); + // to_json('{"numbers":[0,1,2]}')['numbers'][0] + +.. Keep in mind that JSON expressions are represented as strings when returned from a query. + +.. .. code-block:: typescript + +.. await e.json({ +.. name: "Billie", +.. numbers: [1,2,3] +.. }).run(client) +.. // => '{"name": "Billie", "numbers": [1, 2, 3]}'; + +The inferred type associated with a ``json`` expression is ``unknown``. + +.. code-block:: typescript + + const result = await query.run(client) + // unknown + +Arrays +^^^^^^ + +Declare array expressions by passing an array of expressions into ``e.array``. + +.. code-block:: typescript + + e.array([e.str("a"), e.str("b"), e.str("b")]); + // ["a", "b", "c"] + +EdgeQL semantics are enforced by TypeScript, so arrays can't contain elements +with incompatible types. + +.. code-block:: typescript + + e.array([e.int64(5), e.str("foo")]); + // TypeError! + +For convenience, the ``e.array`` can also accept arrays of plain JavaScript +data as well. + +.. code-block:: typescript + + e.array(['a', 'b', 'c']); + // ['a', 'b', 'c'] + + // you can intermixing expressions and plain data + e.array([1, 2, e.int64(3)]); + // [1, 2, 3] + +Array expressions also support indexing and slicing operations. + +.. code-block:: typescript + + const myArray = e.array(['a', 'b', 'c', 'd', 'e']); + // ['a', 'b', 'c', 'd', 'e'] + + myArray[1]; + // ['a', 'b', 'c', 'd', 'e'][1] + + myArray['1:3']; + // ['a', 'b', 'c', 'd', 'e'][1:3] + +There are also equivalent ``.index`` and ``.slice`` methods that can accept +other expressions as arguments. + +.. code-block:: typescript + + const start = e.int64(1); + const end = e.int64(3); + + myArray.index(start); + // ['a', 'b', 'c', 'd', 'e'][1] + + myArray.slice(start, end); + // ['a', 'b', 'c', 'd', 'e'][1:3] + +Tuples +^^^^^^ + +Declare tuples with ``e.tuple``. Pass in an array to declare a "regular" +(unnamed) tuple; pass in an object to declare a named tuple. + +.. code-block:: typescript + + e.tuple([e.str("Peter Parker"), e.int64(18)]); + // ("Peter Parker", 18) + + e.tuple({ + name: e.str("Peter Parker"), + age: e.int64(18) + }); + // (name := "Peter Parker", age := 18) + +Tuple expressions support indexing. + +.. code-block:: typescript + + // Unnamed tuples + const spidey = e.tuple([ + e.str("Peter Parker"), + e.int64(18) + ]); + spidey[0]; // => ("Peter Parker", 18)[0] + + // Named tuples + const spidey = e.tuple({ + name: e.str("Peter Parker"), + age: e.int64(18) + }); + spidey.name; + // (name := "Peter Parker", age := 18).name + +Set literals +^^^^^^^^^^^^ + +Declare sets with ``e.set``. + +.. code-block:: typescript + + e.set(e.str("asdf"), e.str("qwer")); + // {'asdf', 'qwer'} + +As in EdgeQL, sets can't contain elements with incompatible types. These +semantics are enforced by TypeScript. + +.. code-block:: typescript + + e.set(e.int64(1234), e.str('sup')); + // TypeError + +Empty sets +^^^^^^^^^^ + +To declare an empty set, cast an empty set to the desired type. As in EdgeQL, +empty sets are not allowed without a cast. + +.. code-block:: typescript + + e.cast(e.int64, e.set()); + // {} + + +Range literals +^^^^^^^^^^^^^^ + +As in EdgeQL, declare range literals with the built-in ``range`` function. + +.. code-block:: typescript + + const myRange = e.range(0, 8); + + myRange.toEdgeQL(); + // => std::range(0, 8); + +Ranges can be created for all numerical types, as well as ``datetime``, ``local_datetime``, and ``local_date``. + +.. code-block:: typescript + + e.range(e.decimal('100'), e.decimal('200')); + e.range(Date.parse("1970-01-01"), Date.parse("2022-01-01")); + e.range(new LocalDate(1970, 1, 1), new LocalDate(2022, 1, 1)); + +Supply named parameters as the first argument. + +.. code-block:: typescript + + e.range({inc_lower: true, inc_upper: true, empty: true}, 0, 8); + // => std::range(0, 8, true, true); + +JavaScript doesn't have a native way to represent range values. Any range value returned from a query will be encoded as an instance of the :js:class:`Range` class, which is exported from the ``edgedb`` package. + +.. code-block:: typescript + + const query = e.range(0, 8); + const result = await query.run(client); + // => Range; + + console.log(result.lower); // 0 + console.log(result.upper); // 8 + console.log(result.isEmpty); // false + console.log(result.incLower); // true + console.log(result.incUpper); // false + + +.. Modules +.. ------- + +.. All *types*, *functions*, and *commands* are available on the ``e`` object, properly namespaced by module. + +.. .. code-block:: typescript + +.. // commands +.. e.select; +.. e.insert; +.. e.update; +.. e.delete; + +.. // types +.. e.std.str; +.. e.std.int64; +.. e.std.bool; +.. e.cal.local_datetime; +.. e.default.User; // user-defined object type +.. e.my_module.Foo; // object type in user-defined module + +.. // functions +.. e.std.len; +.. e.std.str_upper; +.. e.math.floor; +.. e.sys.get_version; + +.. For convenience, the contents of the ``std`` and ``default`` modules are also exposed at the top-level of ``e``. + +.. .. code-block:: typescript + +.. e.str; +.. e.int64; +.. e.bool; +.. e.len; +.. e.str_upper; +.. e.User; + +.. .. note:: + +.. If there are any name conflicts (e.g. a user-defined module called ``len``), +.. ``e.len`` will point to the user-defined module; in that scenario, you must +.. explicitly use ``e.std.len`` to access the built-in ``len`` function. diff --git a/docs/clients/js/objects.rst b/docs/clients/js/objects.rst new file mode 100644 index 00000000000..bf2870cc668 --- /dev/null +++ b/docs/clients/js/objects.rst @@ -0,0 +1,124 @@ +.. _edgedb-js-objects: + + +Objects and Paths +================= + +All queries on this page assume the following schema. + +.. code-block:: sdl + + module default { + type Person { + required name: str; + } + + abstract type Content { + required title: str { + constraint exclusive + }; + multi actors: Person { + character_name: str; + }; + } + + type Movie extending Content { + release_year: int64; + } + + type TVShow extending Content { + num_seasons: int64; + } + } + +Object types +^^^^^^^^^^^^ + +All object types in your schema are reflected into the query builder, properly +namespaced by module. + +.. code-block:: typescript + + e.default.Person; + e.default.Movie; + e.default.TVShow; + e.my_module.SomeType; + +For convenience, the contents of the ``default`` module are also available at +the top-level of ``e``. + +.. code-block:: typescript + + e.Person; + e.Movie; + e.TVShow; + +.. As in EdgeQL, type names like ``Movie`` serve two purposes. + +.. - They can be used to represent the set of all Movie objects: ``select Movie``. +.. - They can be used to represent the Movie *type* in operations like type intersections: ``select Content[is Movie]`` + +Paths +^^^^^ + +EdgeQL-style *paths* are supported on object type references. + +.. code-block:: typescript + + e.Person.name; // Person.name + e.Movie.title; // Movie.title + e.TVShow.actors.name; // Movie.actors.name + +Paths can be constructed from any object expression, not just the root types. + +.. code-block:: typescript + + e.select(e.Person).name; + // (select Person).name + + e.op(e.Movie, 'union', e.TVShow).actors; + // (Movie union TVShow).actors + + const ironMan = e.insert(e.Movie, { + title: "Iron Man" + }); + ironMan.title; + // (insert Movie { title := "Iron Man" }).title + + +.. _edgedb-js-objects-type-intersections: + +Type intersections +^^^^^^^^^^^^^^^^^^ + +Use the type intersection operator to narrow the type of a set of objects. For +instance, to represent the elements of an Account's watchlist that are of type +``TVShow``: + +.. code-block:: typescript + + e.Person.acted_in.is(e.TVShow); + // Person.acted_in[is TVShow] + + +Backlinks +^^^^^^^^^ + +All possible backlinks are auto-generated and can be auto-completed by +TypeScript. They behave just like forward links. However, because they contain +special characters, you must use bracket syntax instead of simple dot notation. + +.. code-block:: typescript + + e.Person[" + e.op('Yer a wizard, ', '++', params.name) + ); + /* with name := $name + select name; + */ + + +The first argument is an object defining the parameter names and their +corresponding types. The second argument is a closure that returns an +expression; use the ``params`` argument to construct the rest of your query. + +Passing parameter data +^^^^^^^^^^^^^^^^^^^^^^ + +To executing a query with parameters, pass the parameter data as the second +argument to ``.run()``; this argument is *fully typed*! + +.. code-block:: typescript + + await helloQuery.run(client, { name: "Harry Styles" }) + // => "Yer a wizard, Harry Styles" + + await helloQuery.run(client, { name: 16 }) + // => TypeError: number is not assignable to string + +Top-level usage +^^^^^^^^^^^^^^^ + +Note that you must call ``.run`` on the result of ``e.params``; in other +words, you can only use ``e.params`` at the *top level* of your query, not as +an expression inside a larger query. + +.. code-block:: typescript + + // ❌ TypeError + const wrappedQuery = e.select(helloQuery); + wrappedQuery.run(client, {name: "Harry Styles"}); + + +.. _edgedb-js-optional-parameters: + +Optional parameters +^^^^^^^^^^^^^^^^^^^ + +A type can be made optional with the ``e.optional`` function. + +.. code-block:: typescript + + const query = e.params( + { + title: e.str, + duration: e.optional(e.duration), + }, + (params) => { + return e.insert(e.Movie, { + title: params.title, + duration: params.duration, + }); + } + ); + + // works with duration + const result = await query.run(client, { + title: 'The Eternals', + duration: Duration.from({hours: 2, minutes: 3}) + }); + + // or without duration + const result = await query.run(client, {title: 'The Eternals'}); + +Complex types +^^^^^^^^^^^^^ + +In EdgeQL, parameters can only be primitives or arrays of primitives. That's +not true with the query builder! Parameter types can be arbitrarily complex. +Under the hood, the query builder serializes the parameters to JSON and +deserializes them on the server. + +.. code-block:: typescript + + const insertMovie = e.params( + { + title: e.str, + release_year: e.int64, + actors: e.array( + e.tuple({ + name: e.str, + }) + ), + }, + (params) => + e.insert(e.Movie, { + title: params.title, + }) + ); + + await insertMovie.run(client, { + title: 'Dune', + release_year: 2021, + actors: [{name: 'Timmy'}, {name: 'JMo'}], + }); + diff --git a/docs/clients/js/queries.rst b/docs/clients/js/queries.rst new file mode 100644 index 00000000000..ec1471bf34b --- /dev/null +++ b/docs/clients/js/queries.rst @@ -0,0 +1,281 @@ +.. _edgedb-js-queries: + +================= +Queries Generator +================= + +The ``queries`` generator scans your project for ``*.edgeql`` files and generates functions that allow you to execute these queries in a typesafe way. + +Installation +------------ + +To get started, install the following packages. + +.. note:: + + If you're using Deno, you can skip this step. + +Install the ``edgedb`` package. + +.. code-block:: bash + + $ npm install edgedb # npm users + $ yarn add edgedb # yarn users + $ bun add edgedb # bun users + +Then install ``@edgedb/generate`` as a dev dependency. + +.. code-block:: bash + + $ npm install @edgedb/generate --save-dev # npm users + $ yarn add @edgedb/generate --dev # yarn users + $ bun add --dev @edgedb/generate # bun users + + +Generation +---------- + +Consider the following file tree. + +.. code-block:: text + + . + ├── package.json + ├── edgedb.toml + ├── index.ts + ├── dbschema + └── queries + └── getUser.edgeql + + +The following command will run the ``queries`` generator. + +.. tabs:: + + .. code-tab:: bash + :caption: Node.js + + $ npx @edgedb/generate queries + + .. code-tab:: bash + :caption: Deno + + $ deno run --allow-all --unstable https://deno.land/x/edgedb/generate.ts queries + + .. code-tab:: bash + :caption: Bun + + $ bunx @edgedb/generate queries + +.. note:: Deno users + + Create these two files in your project root: + + .. code-block:: json + :caption: importMap.json + + { + "imports": { + "edgedb": "https://deno.land/x/edgedb/mod.ts", + "edgedb/": "https://deno.land/x/edgedb/" + } + } + + .. code-block:: json + :caption: deno.js + + { + "importMap": "./importMap.json" + } + +The generator will detect the project root by looking for an ``edgedb.toml``, +then scan the directory for ``*.edgeql`` files. In this case, there's only one: +``queries/getUser.edgeql``. + +.. code-block:: edgeql + :caption: getUser.edgeql + + select User { name, email } filter .id = $user_id; + +For each ``.edgeql`` file, the generator will read the contents and send the +query to the database, which returns type information about its parameters and +return type. The generator uses this information to create a new file +``getUser.query.ts`` alongside the original ``getUser.edgeql`` file. + +.. code-block:: text + + . + ├── package.json + ├── edgedb.toml + ├── index.ts + ├── dbschema + └── queries + └── getUser.edgeql + └── getUser.query.ts <-- generated file + + +.. note:: + + This example assumes you are using TypeScript. The generator tries to + auto-detect the language you're using; you can also specify the language with + the ``--target`` flag. See the :ref:`Targets ` section for + more information. + +The generated file will look something like this: + +.. code-block:: typescript + + import type { Client } from "edgedb"; + + export type GetUserArgs = { + user_id: string; + }; + + export type GetUserReturns = { + name: string; + email: string; + } | null; + + export async function getUser( + client: Client, + args: GetUserArgs + ): Promise { + return await client.querySingle( + `select User { name, email } filter .id = $user_id;`, + args + ); + } + +Some things to note: + +- The first argument is a ``Client`` instance. This is the same client you would use to execute a query manually. You can use the same client for both manual and generated queries. +- The second argument is a parameter object. The keys of this object are the names of the parameters in the query. +- The code uses the ``querySingle`` method, since the query is only expected to return a single result. +- We export the type of the parameter object and the return value unwrapped from the promise. + +We can now use this function in our code. + +.. code-block:: typescript + + import { getUser } from "./queries/getUser.query"; + import { + createClient, + type GetUserArgs, + type GetUserReturns, + } from "edgedb"; + + const client = await createClient(); + + const newUser: GetUserArgs = { + user_id: "00000000-0000-0000-0000-000000000000" + }; + + const user = await getUser(client, newUser); // GetUserReturns + + if (user) { + user.name; // string + user.email; // string + } + +.. note:: + + Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema. + + +Single-file mode +---------------- + +Pass the ``--file`` flag to generate a single file that contains functions for all detected ``.edgeql`` files. This lets you import all your queries from a single file. + +Let's say we start with the following file tree. + +.. code-block:: text + + . + ├── package.json + ├── edgedb.toml + ├── index.ts + ├── dbschema + └── queries + └── getUser.edgeql + └── getMovies.edgeql + +The following command will run the generator in ``--file`` mode. + +.. code-block:: bash + + $ npx @edgedb/generate queries --file + +A single file will be generated that exports two functions, ``getUser`` and ``getMovies``. By default this file is generated into the ``dbschema`` directory. + +.. code-block:: text + + . + ├── package.json + ├── edgedb.toml + ├── index.ts + ├── dbschema + │ └── queries.ts <-- generated file + └── queries + └── getUser.edgeql + └── getMovies.edgeql + + +We can now use these functions in our code. + +.. code-block:: typescript + + import * as queries from "./dbschema/queries"; + import {createClient} from "edgedb"; + + const client = await createClient(); + + const movies = await queries.getMovies(client); + const user = await queries.getUser(client, { + user_id: "00000000-0000-0000-0000-000000000000" + }); + +To override the file path and name, you can optionally pass a value to the ``--file`` flag. Note that you should *exclude the extension*. + +.. code-block:: bash + + $ npx @edgedb/generate queries --file path/to/myqueries + +The file extension is determined by the generator ``--target`` and will be automatically appended to the provided path. Extensionless "absolute" paths will work; relative paths will be resolved relative to the current working directory. + +This will result in the following file tree. + +.. code-block:: text + + . + ├── package.json + ├── edgedb.toml + ├── path + │ └── to + │ └── myqueries.ts + ├── queries + │ └── getUser.edgeql + │ └── getMovies.edgeql + └── index.ts + +Version control +--------------- + +To exclude the generated files, add the following lines to your ``.gitignore`` file. + +.. code-block:: text + + **/*.edgeql.ts + dbschema/queries.* + +Writing Queries with Parameters +------------------------------- + +To inject external values into your EdgeQL queries, you can use `parameters `__. + +When using the queries generator, you may be tempted to declare the same parameter in multiple places. +However, it's better practice to declare it once by assigning it to a variable in a `with block `__ +and reference that variable in the rest of your query. This way you avoid mismatched types in your declarations, +such as forgetting to mark them all as `optional `__. + +Check out the `EdgeQL docs `__ to learn more about writing queries. diff --git a/docs/clients/js/querybuilder.rst b/docs/clients/js/querybuilder.rst new file mode 100644 index 00000000000..8db198ef656 --- /dev/null +++ b/docs/clients/js/querybuilder.rst @@ -0,0 +1,625 @@ +.. _edgedb-js-qb: + +======================= +Query Builder Generator +======================= +:index: querybuilder generator typescript + +The EdgeDB query builder provides a **code-first** way to write +**fully-typed** EdgeQL queries with TypeScript. We recommend it for TypeScript +users, or anyone who prefers writing queries with code. + +.. code-block:: typescript + + import * as edgedb from "edgedb"; + import e from "./dbschema/edgeql-js"; + + const client = edgedb.createClient(); + + async function run() { + const query = e.select(e.Movie, ()=>({ + id: true, + title: true, + actors: { name: true } + })); + + const result = await query.run(client) + /* + { + id: string; + title: string; + actors: { name: string; }[]; + }[] + */ + } + + run(); + +.. note:: Is it an ORM? + + No—it's better! Like any modern TypeScript ORM, the query builder gives you + full typesafety and autocompletion, but without the power and `performance + `_ + tradeoffs. You have access to the **full power** of EdgeQL and can write + EdgeQL queries of arbitrary complexity. And since EdgeDB compiles each + EdgeQL query into a single, highly-optimized SQL query, your queries stay + fast, even when they're complex. + +Why use the query builder? +-------------------------- + +*Type inference!* If you're using TypeScript, the result type of *all +queries* is automatically inferred for you. For the first time, you don't +need an ORM to write strongly typed queries. + +*Auto-completion!* You can write queries full autocompletion on EdgeQL +keywords, standard library functions, and link/property names. + +*Type checking!* In the vast majority of cases, the query builder won't let +you construct invalid queries. This eliminates an entire class of bugs and +helps you write valid queries the first time. + +*Close to EdgeQL!* The goal of the query builder is to provide an API that is as +close as possible to EdgeQL itself while feeling like idiomatic TypeScript. + +Installation +------------ + +To get started, install the following packages. + +.. note:: + + If you're using Deno, you can skip this step. + +Install the ``edgedb`` package. + +.. code-block:: bash + + $ npm install edgedb # npm users + $ yarn add edgedb # yarn users + $ bun add edgedb # bun users + +Then install ``@edgedb/generate`` as a dev dependency. + +.. code-block:: bash + + $ npm install @edgedb/generate --save-dev # npm users + $ yarn add @edgedb/generate --dev # yarn users + $ bun add --dev @edgedb/generate # bun users + + +Generation +---------- + +The following command will run the ``edgeql-js`` query builder generator. + +.. tabs:: + + .. code-tab:: bash + :caption: Node.js + + $ npx @edgedb/generate edgeql-js + + .. code-tab:: bash + :caption: Deno + + $ deno run --allow-all --unstable https://deno.land/x/edgedb/generate.ts edgeql-js + + .. code-tab:: bash + :caption: Bun + + $ bunx @edgedb/generate edgeql-js + +.. note:: Deno users + + Create these two files in your project root: + + .. code-block:: json + :caption: importMap.json + + { + "imports": { + "edgedb": "https://deno.land/x/edgedb/mod.ts", + "edgedb/": "https://deno.land/x/edgedb/" + } + } + + .. code-block:: json + :caption: deno.js + + { + "importMap": "./importMap.json" + } + +The generation command is configurable in a number of ways. + +``--output-dir `` + Sets the output directory for the generated files. + +``--target `` + What type of files to generate. + +``--force-overwrite`` + To avoid accidental changes, you'll be prompted to confirm whenever the + ``--target`` has changed from the previous run. To avoid this prompt, pass + ``--force-overwrite``. + +The generator also supports all the :ref:`connection flags +` supported by the EdgeDB CLI. These aren't +necessary when using a project or environment variables to configure a +connection. + +.. note:: + + Generators work by connecting to the database to get information about the current state of the schema. Make sure you run the generators again any time the schema changes so that the generated code is in-sync with the current state of the schema. + +.. _edgedb-js-execution: + +Expressions +----------- + +Throughout the documentation, we use the term "expression" a lot. This is a +catch-all term that refers to *any query or query fragment* you define with +the query builder. They all conform to an interface called ``Expression`` with +some common functionality. + +Most importantly, any expression can be executed with the ``.run()`` method, +which accepts a ``Client`` instead as the first argument. The result is +``Promise``, where ``T`` is the inferred type of the query. + +.. code-block:: typescript + + await e.str("hello world").run(client); + // => "hello world" + + await e.set(e.int64(1), e.int64(2), e.int64(3)).run(client); + // => [1, 2, 3] + + await e + .select(e.Movie, () => ({ + title: true, + actors: { name: true }, + })) + .run(client); + // => [{ title: "The Avengers", actors: [...]}] + +Note that the ``.run`` method accepts an instance of :js:class:`Client` (or +``Transaction``) as it's first argument. See :ref:`Creating a Client +` for details on creating clients. The second +argument is for passing :ref:`$parameters `, more on +that later. + +.. code-block:: typescript + + .run(client: Client | Transaction, params: Params): Promise + + +Converting to EdgeQL +-------------------- +:index: querybuilder toedgeql + +You can extract an EdgeQL representation of any expression calling the +``.toEdgeQL()`` method. Below is a number of expressions and the EdgeQL they +produce. (The actual EdgeQL the create may look slightly different, but it's +equivalent.) + +.. code-block:: typescript + + e.str("hello world").toEdgeQL(); + // => select "hello world" + + e.set(e.int64(1), e.int64(2), e.int64(3)).toEdgeQL(); + // => select {1, 2, 3} + + e.select(e.Movie, () => ({ + title: true, + actors: { name: true } + })).toEdgeQL(); + // => select Movie { title, actors: { name }} + +Extracting the inferred type +---------------------------- + +The query builder *automatically infers* the TypeScript type that best +represents the result of a given expression. This inferred type can be +extracted with the ``$infer`` type helper. + +.. code-block:: typescript + + import e, { type $infer } from "./dbschema/edgeql-js"; + + const query = e.select(e.Movie, () => ({ id: true, title: true })); + type result = $infer; + // { id: string; title: string }[] + +Cheatsheet +---------- + +Below is a set of examples to get you started with the query builder. It is +not intended to be comprehensive, but it should provide a good starting point. + +.. note:: + + Modify the examples below to fit your schema, paste them into ``script.ts``, + and execute them with the ``npx`` command from the previous section! Note + how the signature of ``result`` changes as you modify the query. + +Insert an object +^^^^^^^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.insert(e.Movie, { + title: 'Doctor Strange 2', + release_year: 2022 + }); + + const result = await query.run(client); + // { id: string } + // by default INSERT only returns the id of the new object + +.. _edgedb-js-qb-transaction: + +Transaction +^^^^^^^^^^^ + +We can also run the same query as above, build with the query builder, in a +transaction. + +.. code-block:: typescript + + const query = e.insert(e.Movie, { + title: 'Doctor Strange 2', + release_year: 2022 + }); + + await client.transaction(async (tx) => { + const result = await query.run(tx); + // { id: string } + }); + + +Select objects +^^^^^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.select(e.Movie, () => ({ + id: true, + title: true, + })); + + const result = await query.run(client); + // { id: string; title: string; }[] + +To select all properties of an object, use the spread operator with the +special ``*`` property: + +.. code-block:: typescript + + const query = e.select(e.Movie, () => ({ + ...e.Movie['*'] + })); + + const result = await query.run(client); + /* + { + id: string; + title: string; + release_year: number | null; # optional property + }[] + */ + +Nested shapes +^^^^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.select(e.Movie, () => ({ + id: true, + title: true, + actors: { + name: true, + } + })); + + const result = await query.run(client); + /* + { + id: string; + title: string; + actors: { name: string; }[]; + }[] + */ + +Filtering +^^^^^^^^^ + +Pass a boolean expression as the special key ``filter`` to filter the results. + +.. code-block:: typescript + + const query = e.select(e.Movie, (movie) => ({ + id: true, + title: true, + // special "filter" key + filter: e.op(movie.release_year, ">", 1999) + })); + + const result = await query.run(client); + // { id: string; title: number }[] + +Since ``filter`` is a reserved keyword in EdgeQL, the special ``filter`` key can +live alongside your property keys without a risk of collision. + +.. note:: + + The ``e.op`` function is used to express EdgeQL operators. It is documented in + more detail below and on the :ref:`Functions and operators + ` page. + +Select a single object +^^^^^^^^^^^^^^^^^^^^^^ + +To select a particular object, use the ``filter_single`` key. This tells the +query builder to expect a singleton result. + +.. code-block:: typescript + + const query = e.select(e.Movie, (movie) => ({ + id: true, + title: true, + release_year: true, + + filter_single: e.op( + movie.id, + "=", + e.uuid("2053a8b4-49b1-437a-84c8-e1b0291ccd9f") + }, + })); + + const result = await query.run(client); + // { id: string; title: string; release_year: number | null } + +For convenience ``filter_single`` also supports a simplified syntax that +eliminates the need for ``e.op`` when used on exclusive properties: + +.. code-block:: typescript + + e.select(e.Movie, (movie) => ({ + id: true, + title: true, + release_year: true, + + filter_single: { id: "2053a8b4-49b1-437a-84c8-e1b0291ccd9f" }, + })); + +This also works if an object type has a composite exclusive constraint: + +.. code-block:: typescript + + /* + type Movie { + ... + constraint exclusive on (.title, .release_year); + } + */ + + e.select(e.Movie, (movie) => ({ + title: true, + filter_single: { + title: "The Avengers", + release_year: 2012 + }, + })); + + +Ordering and pagination +^^^^^^^^^^^^^^^^^^^^^^^ + +The special keys ``order_by``, ``limit``, and ``offset`` correspond to +equivalent EdgeQL clauses. + +.. code-block:: typescript + + const query = e.select(e.Movie, (movie) => ({ + id: true, + title: true, + + order_by: movie.title, + limit: 10, + offset: 10 + })); + + const result = await query.run(client); + // { id: true; title: true }[] + +Operators +^^^^^^^^^ + +Note that the filter expression above uses ``e.op`` function, which is how to +use *operators* like ``=``, ``>=``, ``++``, and ``and``. + +.. code-block:: typescript + + // prefix (unary) operators + e.op("not", e.bool(true)); // not true + e.op("exists", e.set("hi")); // exists {"hi"} + + // infix (binary) operators + e.op(e.int64(2), "+", e.int64(2)); // 2 + 2 + e.op(e.str("Hello "), "++", e.str("World!")); // "Hello " ++ "World!" + + // ternary operator (if/else) + e.op(e.str("😄"), "if", e.bool(true), "else", e.str("😢")); + // "😄" if true else "😢" + + +Update objects +^^^^^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.update(e.Movie, (movie) => ({ + filter_single: { title: "Doctor Strange 2" }, + set: { + title: "Doctor Strange in the Multiverse of Madness", + }, + })); + + const result = await query.run(client); + +Delete objects +^^^^^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.delete(e.Movie, (movie) => ({ + filter: e.op(movie.title, 'ilike', "the avengers%"), + })); + + const result = await query.run(client); + // { id: string }[] + +Delete multiple objects using an array of properties: + +.. code-block:: typescript + + const titles = ["The Avengers", "Doctor Strange 2"]; + const query = e.delete(e.Movie, (movie) => ({ + filter: e.op( + movie.title, + "in", + e.array_unpack(e.literal(e.array(e.str), titles)) + ) + })); + const result = await query.run(client); + // { id: string }[] + +Note that we have to use ``array_unpack`` to cast our ``array`` into a +``set`` since the ``in`` operator works on sets. And we use ``literal`` to +create a custom literal since we're inlining the titles array into our query. + +Here's an example of how to do this with params: + +.. code-block:: typescript + + const query = e.params({ titles: e.array(e.str) }, ({ titles }) => + e.delete(e.Movie, (movie) => ({ + filter: e.op(movie.title, "in", e.array_unpack(titles)), + })) + ); + + const result = await query.run(client, { + titles: ["The Avengers", "Doctor Strange 2"], + }); + // { id: string }[] + +Compose queries +^^^^^^^^^^^^^^^ + +All query expressions are fully composable; this is one of the major +differentiators between this query builder and a typical ORM. For instance, we +can ``select`` an ``insert`` query in order to fetch properties of the object we +just inserted. + + +.. code-block:: typescript + + const newMovie = e.insert(e.Movie, { + title: "Iron Man", + release_year: 2008 + }); + + const query = e.select(newMovie, () => ({ + title: true, + release_year: true, + num_actors: e.count(newMovie.actors) + })); + + const result = await query.run(client); + // { title: string; release_year: number; num_actors: number } + +Or we can use subqueries inside mutations. + +.. code-block:: typescript + + // select Doctor Strange + const drStrange = e.select(e.Movie, (movie) => ({ + filter_single: { title: "Doctor Strange" } + })); + + // select actors + const actors = e.select(e.Person, (person) => ({ + filter: e.op( + person.name, + "in", + e.set("Benedict Cumberbatch", "Rachel McAdams") + ) + })); + + // add actors to cast of drStrange + const query = e.update(drStrange, () => ({ + actors: { "+=": actors } + })); + + const result = await query.run(client); + + +Parameters +^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.params({ + title: e.str, + release_year: e.int64, + }, + (params) => { + return e.insert(e.Movie, { + title: params.title, + release_year: params.release_year, + })) + }; + + const result = await query.run(client, { + title: "Thor: Love and Thunder", + release_year: 2022, + }); + // { id: string } + +.. note:: + + Continue reading for more complete documentation on how to express any + EdgeQL query with the query builder. + + +.. _ref_edgedbjs_globals: + +Globals +^^^^^^^ + +Reference global variables. + +.. code-block:: typescript + + e.global.user_id; + e.default.global.user_id; // same as above + e.my_module.global.some_value; + +Other modules +^^^^^^^^^^^^^ + +Reference entities in modules other than ``default``. + +The ``Vampire`` type in a module named ``characters``: + +.. code-block:: typescript + + e.characters.Vampire; + +As shown in "Globals," a global ``some_value`` in a module ``my_module``: + +.. code-block:: typescript + + e.my_module.global.some_value; diff --git a/docs/clients/js/reference.rst b/docs/clients/js/reference.rst new file mode 100644 index 00000000000..3f2895bd561 --- /dev/null +++ b/docs/clients/js/reference.rst @@ -0,0 +1,1301 @@ +.. _edgedb-js-api-reference: + +######### +Reference +######### + +.. _edgedb-js-api-client: + +Client +====== + +.. js:function:: createClient( \ + options: string | ConnectOptions | null \ + ): Client + + Creates a new :js:class:`Client` instance. + + :param options: + This is an optional parameter. When it is not specified the client + will connect to the current EdgeDB Project instance. + + If this parameter is a string it can represent either a + DSN or an instance name: + + * when the string does not start with ``edgedb://`` it is a + :ref:`name of an instance `; + + * otherwise it specifies a single string in the connection URI format: + ``edgedb://user:password@host:port/database?option=value``. + + See the :ref:`Connection Parameters ` + docs for full details. + + Alternatively the parameter can be a ``ConnectOptions`` config; + see the documentation of valid options below. + + :param string options.dsn: + Specifies the DSN of the instance. + + :param string options.credentialsFile: + Path to a file containing credentials. + + :param string options.host: + Database host address as either an IP address or a domain name. + + :param number options.port: + Port number to connect to at the server host. + + :param string options.user: + The name of the database role used for authentication. + + :param string options.database: + The name of the database to connect to. + + :param string options.password: + Password to be used for authentication, if the server requires one. + + :param string options.tlsCAFile: + Path to a file containing the root certificate of the server. + + :param boolean options.tlsSecurity: + Determines whether certificate and hostname verification is enabled. + Valid values are ``'strict'`` (certificate will be fully validated), + ``'no_host_verification'`` (certificate will be validated, but + hostname may not match), ``'insecure'`` (certificate not validated, + self-signed certificates will be trusted), or ``'default'`` (acts as + ``strict`` by default, or ``no_host_verification`` if ``tlsCAFile`` + is set). + + The above connection options can also be specified by their corresponding + environment variable. If none of ``dsn``, ``credentialsFile``, ``host`` or + ``port`` are explicitly specified, the client will connect to your + linked project instance, if it exists. For full details, see the + :ref:`Connection Parameters ` docs. + + + :param number options.timeout: + Connection timeout in milliseconds. + + :param number options.waitUntilAvailable: + If first connection fails, the number of milliseconds to keep retrying + to connect (Defaults to 30 seconds). Useful if your development + instance and app are started together, to allow the server time to + be ready. + + :param number options.concurrency: + The maximum number of connection the ``Client`` will create in it's + connection pool. If not specified the concurrency will be controlled + by the server. This is recommended as it allows the server to better + manage the number of client connections based on it's own available + resources. + + :returns: + Returns an instance of :js:class:`Client`. + + Example: + + .. code-block:: js + + // Use the Node.js assert library to test results. + const assert = require("assert"); + const edgedb = require("edgedb"); + + async function main() { + const client = edgedb.createClient(); + + const data = await client.querySingle("select 1 + 1"); + + // The result is a number 2. + assert(typeof data === "number"); + assert(data === 2); + } + + main(); + + +.. js:class:: Client + + A ``Client`` allows you to run queries on an EdgeDB instance. + + Since opening connections is an expensive operation, ``Client`` also + maintains a internal pool of connections to the instance, allowing + connections to be automatically reused, and you to run multiple queries + on the client simultaneously, enhancing the performance of + database interactions. + + :js:class:`Client` is not meant to be instantiated directly; + :js:func:`createClient` should be used instead. + + + .. _edgedb-js-api-async-optargs: + + .. note:: + + Some methods take query arguments as an *args* parameter. The type of + the *args* parameter depends on the query: + + * If the query uses positional query arguments, the *args* parameter + must be an ``array`` of values of the types specified by each query + argument's type cast. + * If the query uses named query arguments, the *args* parameter must + be an ``object`` with property names and values corresponding to + the query argument names and type casts. + + If a query argument is defined as ``optional``, the key/value can be + either omitted from the *args* object or be a ``null`` value. + + .. js:method:: execute(query: string, args?: QueryArgs): Promise + + Execute an EdgeQL command (or commands). + + :param query: Query text. + + This method takes :ref:`optional query arguments + `. + + Example: + + .. code-block:: js + + await client.execute(` + CREATE TYPE MyType { + CREATE PROPERTY a -> int64 + }; + + for x in {100, 200, 300} + union (insert MyType { a := x }); + `) + + .. js:method:: query(query: string, args?: QueryArgs): Promise + + Run an EdgeQL query and return the results as an array. + This method **always** returns an array. + + This method takes :ref:`optional query arguments + `. + + .. js:method:: queryRequired( \ + query: string, \ + args?: QueryArgs \ + ): Promise<[T, ...T[]]> + + Run a query that returns at least one element and return the result as an + array. + + This method takes :ref:`optional query arguments + `. + + The *query* must return at least one element. If the query less than one + element, a ``ResultCardinalityMismatchError`` error is thrown. + + .. js:method:: querySingle( \ + query: string, \ + args?: QueryArgs \ + ): Promise + + Run an optional singleton-returning query and return the result. + + This method takes :ref:`optional query arguments + `. + + The *query* must return no more than one element. If the query returns + more than one element, a ``ResultCardinalityMismatchError`` error is + thrown. + + .. js:method:: queryRequiredSingle( \ + query: string, \ + args?: QueryArgs \ + ): Promise + + Run a singleton-returning query and return the result. + + This method takes :ref:`optional query arguments + `. + + The *query* must return exactly one element. If the query returns + more than one element, a ``ResultCardinalityMismatchError`` error is + thrown. If the query returns an empty set, a ``NoDataError`` error is + thrown. + + .. js:method:: queryJSON(query: string, args?: QueryArgs): Promise + + Run a query and return the results as a JSON-encoded string. + + This method takes :ref:`optional query arguments + `. + + .. note:: + + Caution is advised when reading ``decimal`` or ``bigint`` + values using this method. The JSON specification does not + have a limit on significant digits, so a ``decimal`` or a + ``bigint`` number can be losslessly represented in JSON. + However, JSON decoders in JavaScript will often read all + such numbers as ``number`` values, which may result in + precision loss. If such loss is unacceptable, then + consider casting the value into ``str`` and decoding it on + the client side into a more appropriate type, such as + BigInt_. + + .. js:method:: queryRequiredJSON( \ + query: string, \ + args?: QueryArgs \ + ): Promise + + Run a query that returns at least one element and return the result as a + JSON-encoded string. + + This method takes :ref:`optional query arguments + `. + + The *query* must return at least one element. If the query less than one + element, a ``ResultCardinalityMismatchError`` error is thrown. + + .. note:: + + Caution is advised when reading ``decimal`` or ``bigint`` + values using this method. The JSON specification does not + have a limit on significant digits, so a ``decimal`` or a + ``bigint`` number can be losslessly represented in JSON. + However, JSON decoders in JavaScript will often read all + such numbers as ``number`` values, which may result in + precision loss. If such loss is unacceptable, then + consider casting the value into ``str`` and decoding it on + the client side into a more appropriate type, such as + BigInt_. + + .. js:method:: querySingleJSON( \ + query: string, \ + args?: QueryArgs \ + ): Promise + + Run an optional singleton-returning query and return its element + as a JSON-encoded string. + + This method takes :ref:`optional query arguments + `. + + The *query* must return at most one element. If the query returns + more than one element, an ``ResultCardinalityMismatchError`` error + is thrown. + + .. note:: + + Caution is advised when reading ``decimal`` or ``bigint`` + values using this method. The JSON specification does not + have a limit on significant digits, so a ``decimal`` or a + ``bigint`` number can be losslessly represented in JSON. + However, JSON decoders in JavaScript will often read all + such numbers as ``number`` values, which may result in + precision loss. If such loss is unacceptable, then + consider casting the value into ``str`` and decoding it on + the client side into a more appropriate type, such as + BigInt_. + + .. js:method:: queryRequiredSingleJSON( \ + query: string, \ + args?: QueryArgs \ + ): Promise + + Run a singleton-returning query and return its element as a + JSON-encoded string. + + This method takes :ref:`optional query arguments + `. + + The *query* must return exactly one element. If the query returns + more than one element, a ``ResultCardinalityMismatchError`` error + is thrown. If the query returns an empty set, a ``NoDataError`` error + is thrown. + + .. note:: + + Caution is advised when reading ``decimal`` or ``bigint`` + values using this method. The JSON specification does not + have a limit on significant digits, so a ``decimal`` or a + ``bigint`` number can be losslessly represented in JSON. + However, JSON decoders in JavaScript will often read all + such numbers as ``number`` values, which may result in + precision loss. If such loss is unacceptable, then + consider casting the value into ``str`` and decoding it on + the client side into a more appropriate type, such as + BigInt_. + + .. js:method:: executeSQL(query: string, args?: unknown[]): Promise + + Execute a SQL command. + + :param query: SQL query text. + + This method takes optional query arguments. + + Example: + + .. code-block:: js + + await client.executeSQL(` + INSERT INTO "MyType"(prop) VALUES ("value"); + `) + + .. js:method:: querySQL(query: string, args?: unknown[]): Promise + + Run a SQL query and return the results as an array. + This method **always** returns an array. + + The array will contain the returned rows. By default, rows are + ``Objects`` with columns addressable by name. + + This can controlled with ``client.withSQLRowMode('array' | 'object')`` + API. + + This method takes optional query arguments. + + Example: + + .. code-block:: js + + let vals = await client.querySQL(`SELECT 1 as foo`) + console.log(vals); // [{'foo': 1}] + + vals = await client + .withSQLRowMode('array') + .querySQL(`SELECT 1 as foo`); + + console.log(vals); // [[1]] + + .. js:method:: transaction( \ + action: (tx: Transaction) => Promise \ + ): Promise + + Execute a retryable transaction. The ``Transaction`` object passed to + the action function has the same ``execute`` and ``query*`` methods + as ``Client``. + + This is the preferred method of initiating and running a database + transaction in a robust fashion. The ``transaction()`` method + will attempt to re-execute the transaction body if a transient error + occurs, such as a network error or a transaction serialization error. + The number of times ``transaction()`` will attempt to execute the + transaction, and the backoff timeout between retries can be + configured with :js:meth:`Client.withRetryOptions`. + + See :ref:`edgedb-js-api-transaction` for more details. + + Example: + + .. code-block:: js + + await client.transaction(async tx => { + const value = await tx.querySingle("select Counter.value") + await tx.execute( + `update Counter set { value := $value }`, + {value: value + 1}, + ) + }); + + Note that we are executing queries on the ``tx`` object rather + than on the original ``client``. + + .. js:method:: ensureConnected(): Promise + + If the client does not yet have any open connections in its pool, + attempts to open a connection, else returns immediately. + + Since the client lazily creates new connections as needed (up to the + configured ``concurrency`` limit), the first connection attempt will + only occur when the first query is run a client. ``ensureConnected`` + can be useful to catch any errors resulting from connection + mis-configuration by triggering the first connection attempt + explicitly. + + Example: + + .. code-block:: js + + import {createClient} from 'edgedb'; + + async function getClient() { + try { + return await createClient('custom_instance').ensureConnected(); + } catch (err) { + // handle connection error + } + } + + function main() { + const client = await getClient(); + + await client.query('select ...'); + } + + .. js:method:: withGlobals(globals: {[name: string]: any}): Client + + Returns a new ``Client`` instance with the specified global values. + The ``globals`` argument object is merged with any existing globals + defined on the current client instance. + + Equivalent to using the ``set global`` command. + + Example: + + .. code-block:: js + + const user = await client.withGlobals({ + userId: '...' + }).querySingle(` + select User {name} filter .id = global userId + `); + + .. js:method:: withModuleAliases(aliases: {[name: string]: string}): Client + + Returns a new ``Client`` instance with the specified module aliases. + The ``aliases`` argument object is merged with any existing module + aliases defined on the current client instance. + + If the alias ``name`` is ``module`` this is equivalent to using + the ``set module`` command, otherwise it is equivalent to the + ``set alias`` command. + + Example: + + .. code-block:: js + + const user = await client.withModuleAliases({ + module: 'sys' + }).querySingle(` + select get_version_as_str() + `); + // "2.0" + + .. js:method:: withConfig(config: {[name: string]: any}): Client + + Returns a new ``Client`` instance with the specified client session + configuration. The ``config`` argument object is merged with any + existing session config defined on the current client instance. + + Equivalent to using the ``configure session`` command. For available + configuration parameters refer to the + :ref:`Config documentation `. + + .. js:method:: withRetryOptions(opts: { \ + attempts?: number \ + backoff?: (attempt: number) => number \ + }): Client + + Returns a new ``Client`` instance with the specified retry attempts + number and backoff time function (the time that retrying methods will + wait between retry attempts, in milliseconds), where options not given + are inherited from the current client instance. + + The default number of attempts is ``3``. The default backoff + function returns a random time between 100 and 200ms multiplied by + ``2 ^ attempt number``. + + .. note:: + + The new client instance will share the same connection pool as the + client it's created from, so calling the ``ensureConnected``, + ``close`` and ``terminate`` methods will affect all clients + sharing the pool. + + Example: + + .. code-block:: js + + import {createClient} from 'edgedb'; + + function main() { + const client = createClient(); + + // By default transactions will retry if they fail + await client.transaction(async tx => { + // ... + }); + + const nonRetryingClient = client.withRetryOptions({ + attempts: 1 + }); + + // This transaction will not retry + await nonRetryingClient.transaction(async tx => { + // ... + }); + } + + .. js:method:: close(): Promise + + Close the client's open connections gracefully. When a client is + closed, all its underlying connections are awaited to complete their + pending operations, then closed. A warning is produced if the pool + takes more than 60 seconds to close. + + .. note:: + + Clients will not prevent Node.js from exiting once all of it's + open connections are idle and Node.js has no further tasks it is + awaiting on, so it is not necessary to explicitly call ``close()`` + if it is more convenient for your application. + + (This does not apply to Deno, since Deno is missing the + required API's to ``unref`` idle connections) + + .. js:method:: isClosed(): boolean + + Returns true if ``close()`` has been called on the client. + + .. js:method:: terminate(): void + + Terminate all connections in the client, closing all connections non + gracefully. If the client is already closed, return without doing + anything. + + +.. _edgedb-js-datatypes: + +Type conversion +=============== + +The client automatically converts EdgeDB types to the corresponding JavaScript +types and vice versa. + +The table below shows the correspondence between EdgeDB and JavaScript types. + + +.. list-table:: + + * - **EdgeDB Type** + - **JavaScript Type** + * - ``multi`` set + - ``Array`` + * - ``array`` + - ``Array`` + * - ``anytuple`` + - ``Array`` + * - ``anyenum`` + - ``string`` + * - ``Object`` + - ``object`` + * - ``bool`` + - ``boolean`` + * - ``bytes`` + - ``Uint8Array`` + * - ``str`` + - ``string`` + * - ``float32``, ``float64``, ``int16``, ``int32``, ``int64`` + - ``number`` + * - ``bigint`` + - ``BigInt`` + * - ``decimal`` + - n/a + * - ``json`` + - ``unknown`` + * - ``uuid`` + - ``string`` + * - ``datetime`` + - ``Date`` + * - ``cal::local_date`` + - :js:class:`LocalDate` + * - ``cal::local_time`` + - :js:class:`LocalTime` + * - ``cal::local_datetime`` + - :js:class:`LocalDateTime` + * - ``duration`` + - :js:class:`Duration` + * - ``cal::relative_duration`` + - :js:class:`RelativeDuration` + * - ``cal::date_duration`` + - :js:class:`DateDuration` + * - ``range`` + - :js:class:`Range` + * - ``cfg::memory`` + - :js:class:`ConfigMemory` + + +.. note:: + + Inexact single-precision ``float`` values may have a different + representation when decoded into a JavaScript number. This is inherent + to the implementation of limited-precision floating point types. + If you need the decimal representation to match, cast the expression + to ``float64`` in your query. + +.. note:: + + Due to precision limitations the ``decimal`` type cannot be decoded to a + JavaScript number. Use an explicit cast to ``float64`` if the precision + degradation is acceptable or a cast to ``str`` for an exact decimal + representation. + + +Arrays +====== + +EdgeDB ``array`` maps onto the JavaScript ``Array``. + +.. code-block:: js + + // Use the Node.js assert library to test results. + const assert = require("assert"); + const edgedb = require("edgedb"); + + async function main() { + const client = edgedb.createClient("edgedb://edgedb@localhost/"); + + const data = await client.querySingle("select [1, 2, 3]"); + + // The result is an Array. + assert(data instanceof Array); + assert(typeof data[0] === "number"); + assert(data.length === 3); + assert(data[2] === 3); + } + + main(); + +.. _edgedb-js-types-object: + +Objects +======= + +``Object`` represents an object instance returned from a query. The value of an +object property or a link can be accessed through a corresponding object key: + +.. code-block:: js + + // Use the Node.js assert library to test results. + const assert = require("assert"); + const edgedb = require("edgedb"); + + async function main() { + const client = edgedb.createClient("edgedb://edgedb@localhost/"); + + const data = await client.querySingle(` + select schema::Property { + name, + annotations: {name, @value} + } + filter .name = 'listen_port' + and .source.name = 'cfg::Config' + limit 1 + `); + + // The property 'name' is accessible. + assert(typeof data.name === "string"); + // The link 'annotaions' is accessible and is a Set. + assert(typeof data.annotations === "object"); + assert(data.annotations instanceof edgedb.Set); + // The Set of 'annotations' is array-like. + assert(data.annotations.length > 0); + assert(data.annotations[0].name === "cfg::system"); + assert(data.annotations[0]["@value"] === "true"); + } + + main(); + +Tuples +====== + +A regular EdgeDB ``tuple`` becomes an ``Array`` in JavaScript. + +.. code-block:: js + + // Use the Node.js assert library to test results. + const assert = require("assert"); + const edgedb = require("edgedb"); + + async function main() { + const client = edgedb.createClient("edgedb://edgedb@localhost/"); + + const data = await client.querySingle(` + select (1, 'a', [3]) + `); + + // The resulting tuple is an Array. + assert(data instanceof Array); + assert(data.length === 3); + assert(typeof data[0] === "number"); + assert(typeof data[1] === "string"); + assert(data[2] instanceof Array); + } + + main(); + +Named Tuples +============ + +A named EdgeDB ``tuple`` becomes an ``Array``-like ``object`` in JavaScript, +where the elements are accessible either by their names or indexes. + +.. code-block:: js + + // Use the Node.js assert library to test results. + const assert = require("assert"); + const edgedb = require("edgedb"); + + async function main() { + const client = edgedb.createClient("edgedb://edgedb@localhost/"); + + const data = await client.querySingle(` + select (a := 1, b := 'a', c := [3]) + `); + + // The resulting tuple is an Array. + assert(data instanceof Array); + assert(data.length === 3); + assert(typeof data[0] === "number"); + assert(typeof data[1] === "string"); + assert(data[2] instanceof Array); + // Elements can be accessed by their names. + assert(typeof data.a === "number"); + assert(typeof data["b"] === "string"); + assert(data.c instanceof Array); + } + + main(); + + +Local Date +========== + +.. js:class:: LocalDate(\ + year: number, \ + month: number, \ + day: number) + + A JavaScript representation of an EdgeDB ``local_date`` value. Implements + a subset of the `TC39 Temporal Proposal`_ ``PlainDate`` type. + + Assumes the calendar is always `ISO 8601`_. + + .. js:attribute:: year: number + + The year value of the local date. + + .. js:attribute:: month: number + + The numerical month value of the local date. + + .. note:: + + Unlike the JS ``Date`` object, months in ``LocalDate`` start at 1. + ie. Jan = 1, Feb = 2, etc. + + .. js:attribute:: day: number + + The day of the month value of the local date (starting with 1). + + .. js:attribute:: dayOfWeek: number + + The weekday number of the local date. Returns a value between 1 and 7 + inclusive, where 1 = Monday and 7 = Sunday. + + .. js:attribute:: dayOfYear: number + + The ordinal day of the year of the local date. Returns a value between + 1 and 365 (or 366 in a leap year). + + .. js:attribute:: weekOfYear: number + + The ISO week number of the local date. Returns a value between 1 and + 53, where ISO week 1 is defined as the week containing the first + Thursday of the year. + + .. js:attribute:: daysInWeek: number + + The number of days in the week of the local date. Always returns 7. + + .. js:attribute:: daysInMonth: number + + The number of days in the month of the local date. Returns a value + between 28 and 31 inclusive. + + .. js:attribute:: daysInYear: number + + The number of days in the year of the local date. Returns either 365 or + 366 if the year is a leap year. + + .. js:attribute:: monthsInYear: number + + The number of months in the year of the local date. Always returns 12. + + .. js:attribute:: inLeapYear: boolean + + Return whether the year of the local date is a leap year. + + .. js:method:: toString(): string + + Get the string representation of the ``LocalDate`` in the + ``YYYY-MM-DD`` format. + + .. js:method:: toJSON(): number + + Same as :js:meth:`~LocalDate.toString`. + + .. js:method:: valueOf(): never + + Always throws an Error. ``LocalDate`` objects are not comparable. + + +Local Time +========== + +.. js:class:: LocalTime(\ + hour: number = 0, \ + minute: number = 0, \ + second: number = 0, \ + millisecond: number = 0, \ + microsecond: number = 0, \ + nanosecond: number = 0) + + A JavaScript representation of an EdgeDB ``local_time`` value. Implements + a subset of the `TC39 Temporal Proposal`_ ``PlainTime`` type. + + .. note:: + + The EdgeDB ``local_time`` type only has microsecond precision, any + nanoseconds specified in the ``LocalTime`` will be ignored when + encoding to an EdgeDB ``local_time``. + + .. js:attribute:: hour: number + + The hours component of the local time in 0-23 range. + + .. js:attribute:: minute: number + + The minutes component of the local time in 0-59 range. + + .. js:attribute:: second: number + + The seconds component of the local time in 0-59 range. + + .. js:attribute:: millisecond: number + + The millisecond component of the local time in 0-999 range. + + .. js:attribute:: microsecond: number + + The microsecond component of the local time in 0-999 range. + + .. js:attribute:: nanosecond: number + + The nanosecond component of the local time in 0-999 range. + + .. js:method:: toString(): string + + Get the string representation of the ``local_time`` in the ``HH:MM:SS`` + 24-hour format. + + .. js:method:: toJSON(): string + + Same as :js:meth:`~LocalTime.toString`. + + .. js:method:: valueOf(): never + + Always throws an Error. ``LocalTime`` objects are not comparable. + + +Local Date and Time +=================== + +.. js:class:: LocalDateTime(\ + year: number, \ + month: number, \ + day: number, \ + hour: number = 0, \ + minute: number = 0, \ + second: number = 0, \ + millisecond: number = 0, \ + microsecond: number = 0, \ + nanosecond: number = 0) extends LocalDate, LocalTime + + A JavaScript representation of an EdgeDB ``local_datetime`` value. + Implements a subset of the `TC39 Temporal Proposal`_ ``PlainDateTime`` + type. + + Inherits all properties from the :js:class:`~LocalDate` and + :js:class:`~LocalTime` types. + + .. js:method:: toString(): string + + Get the string representation of the ``local_datetime`` in the + ``YYYY-MM-DDTHH:MM:SS`` 24-hour format. + + .. js:method:: toJSON(): string + + Same as :js:meth:`~LocalDateTime.toString`. + + .. js:method:: valueOf(): never + + Always throws an Error. ``LocalDateTime`` objects are not comparable. + + +Duration +======== + +.. js:class:: Duration(\ + years: number = 0, \ + months: number = 0, \ + weeks: number = 0, \ + days: number = 0, \ + hours: number = 0, \ + minutes: number = 0, \ + seconds: number = 0, \ + milliseconds: number = 0, \ + microseconds: number = 0, \ + nanoseconds: number = 0) + + A JavaScript representation of an EdgeDB ``duration`` value. This class + attempts to conform to the `TC39 Temporal Proposal`_ ``Duration`` type as + closely as possible. + + No arguments may be infinite and all must have the same sign. + Any non-integer arguments will be rounded towards zero. + + .. note:: + + The Temporal ``Duration`` type can contain both absolute duration + components, such as hours, minutes, seconds, etc. and relative + duration components, such as years, months, weeks, and days, where + their absolute duration changes depending on the exact date they are + relative to (eg. different months have a different number of days). + + The EdgeDB ``duration`` type only supports absolute durations, so any + ``Duration`` with non-zero years, months, weeks, or days will throw + an error when trying to encode them. + + .. note:: + + The EdgeDB ``duration`` type only has microsecond precision, any + nanoseconds specified in the ``Duration`` will be ignored when + encoding to an EdgeDB ``duration``. + + .. note:: + + Temporal ``Duration`` objects can be unbalanced_, (ie. have a greater + value in any property than it would naturally have, eg. have a seconds + property greater than 59), but EdgeDB ``duration`` objects are always + balanced. + + Therefore in a round-trip of a ``Duration`` object to EdgeDB and back, + the returned object, while being an equivalent duration, may not + have exactly the same property values as the sent object. + + .. js:attribute:: years: number + + The number of years in the duration. + + .. js:attribute:: months: number + + The number of months in the duration. + + .. js:attribute:: weeks: number + + The number of weeks in the duration. + + .. js:attribute:: days: number + + The number of days in the duration. + + .. js:attribute:: hours: number + + The number of hours in the duration. + + .. js:attribute:: minutes: number + + The number of minutes in the duration. + + .. js:attribute:: seconds: number + + The number of seconds in the duration. + + .. js:attribute:: milliseconds: number + + The number of milliseconds in the duration. + + .. js:attribute:: microseconds: number + + The number of microseconds in the duration. + + .. js:attribute:: nanoseconds: number + + The number of nanoseconds in the duration. + + .. js:attribute:: sign: number + + Returns -1, 0, or 1 depending on whether the duration is negative, + zero or positive. + + .. js:attribute:: blank: boolean + + Returns ``true`` if the duration is zero. + + .. js:method:: toString(): string + + Get the string representation of the duration in `ISO 8601 duration`_ + format. + + .. js:method:: toJSON(): number + + Same as :js:meth:`~Duration.toString`. + + .. js:method:: valueOf(): never + + Always throws an Error. ``Duration`` objects are not comparable. + + +RelativeDuration +================ + +.. js:class:: RelativeDuration(\ + years: number = 0, \ + months: number = 0, \ + weeks: number = 0, \ + days: number = 0, \ + hours: number = 0, \ + minutes: number = 0, \ + seconds: number = 0, \ + milliseconds: number = 0, \ + microseconds: number = 0) + + A JavaScript representation of an EdgeDB + :eql:type:`cal::relative_duration` value. This type represents a + non-definite span of time such as "2 years 3 days". This cannot be + represented as a :eql:type:`duration` because a year has no absolute + duration; for instance, leap years are longer than non-leap years. + + This class attempts to conform to the `TC39 Temporal Proposal`_ + ``Duration`` type as closely as possible. + + Internally, a ``cal::relative_duration`` value is represented as an + integer number of months, days, and seconds. During encoding, other units + will be normalized to these three. Sub-second units like ``microseconds`` + will be ignored. + + .. js:attribute:: years: number + + The number of years in the relative duration. + + .. js:attribute:: months: number + + The number of months in the relative duration. + + .. js:attribute:: weeks: number + + The number of weeks in the relative duration. + + .. js:attribute:: days: number + + The number of days in the relative duration. + + .. js:attribute:: hours: number + + The number of hours in the relative duration. + + .. js:attribute:: minutes: number + + The number of minutes in the relative duration. + + .. js:attribute:: seconds: number + + The number of seconds in the relative duration. + + .. js:attribute:: milliseconds: number + + The number of milliseconds in the relative duration. + + .. js:attribute:: microseconds: number + + The number of microseconds in the relative duration. + + .. js:method:: toString(): string + + Get the string representation of the duration in `ISO 8601 duration`_ + format. + + .. js:method:: toJSON(): string + + Same as :js:meth:`~Duration.toString`. + + .. js:method:: valueOf(): never + + Always throws an Error. ``RelativeDuration`` objects are not + comparable. + + +DateDuration +============ + +.. js:class:: DateDuration( \ + years: number = 0, \ + months: number = 0, \ + weeks: number = 0, \ + days: number = 0, \ + ) + + A JavaScript representation of an EdgeDB + :eql:type:`cal::date_duration` value. This type represents a + non-definite span of time consisting of an integer number of *months* and + *days*. + + This type is primarily intended to simplify logic involving + :eql:type:`cal::local_date` values. + + .. code-block:: edgeql-repl + + db> select '5 days'; + {'P5D'} + db> select '2022-06-25' + '5 days'; + {'2022-06-30'} + db> select '2022-06-30' - '2022-06-25'; + {'P5D'} + + Internally, a ``cal::relative_duration`` value is represented as an + integer number of months and days. During encoding, other units will be + normalized to these two. + + .. js:attribute:: years: number + + The number of years in the relative duration. + + .. js:attribute:: months: number + + The number of months in the relative duration. + + .. js:attribute:: weeks: number + + The number of weeks in the relative duration. + + .. js:attribute:: days: number + + The number of days in the relative duration. + + .. js:method:: toString(): string + + Get the string representation of the duration in `ISO 8601 duration`_ + format. + + .. js:method:: toJSON(): string + + Same as :js:meth:`~Duration.toString`. + + .. js:method:: valueOf(): never + + Always throws an Error. ``DateDuration`` objects are not comparable. + + +Memory +====== + +.. js:class:: ConfigMemory(bytes: BigInt) + + A JavaScript representation of an EdgeDB ``cfg::memory`` value. + + .. js:attribute:: bytes: number + + The memory value in bytes (B). + + .. note:: + + The EdgeDB ``cfg::memory`` represents a number of bytes stored as + an ``int64``. Since JS the ``number`` type is a ``float64``, values + above ``~8191TiB`` will lose precision when represented as a JS + ``number``. To keep full precision use the ``bytesBigInt`` + property. + + .. js::attribute:: bytesBigInt: BigInt + + The memory value in bytes represented as a ``BigInt``. + + .. js:attribute:: kibibytes: number + + The memory value in kibibytes (KiB). + + .. js:attribute:: mebibytes: number + + The memory value in mebibytes (MiB). + + .. js:attribute:: gibibytes: number + + The memory value in gibibytes (GiB). + + .. js:attribute:: tebibytes: number + + The memory value in tebibytes (TiB). + + .. js:attribute:: pebibytes: number + + The memory value in pebibytes (PiB). + + .. js:method:: toString(): string + + Get the string representation of the memory value. Format is the same + as returned by string casting a ``cfg::memory`` value in EdgeDB. + +Range +===== + +.. js:class:: Range(\ + lower: T | null, \ + upper: T | null, \ + incLower: boolean = true, \ + incUpper: boolean = false \ + ) + + A JavaScript representation of an EdgeDB ``std::range`` value. This is a generic TypeScript class with the following type signature. + + .. code-block:: typescript + + class Range< + T extends number | Date | LocalDate | LocalDateTime | Duration + >{ + // ... + } + + .. js:attribute:: lower: T + + The lower bound of the range value. + + .. js:attribute:: upper: T + + The upper bound of the range value. + + .. js:attribute:: incLower: boolean + + Whether the lower bound is inclusive. + + .. js:attribute:: incUpper: boolean + + Whether the upper bound is inclusive. + + .. js:attribute:: empty: boolean + + Whether the range is empty. + + .. js:method:: toJSON(): { \ + lower: T | null; \ + upper: T | null; \ + inc_lower: boolean; \ + inc_upper: boolean; \ + empty?: undefined; \ + } + + Returns a JSON-encodable representation of the range. + + .. js:method:: empty(): Range + + A static method to declare an empty range (no bounds). + + .. code-block:: typescript + + Range.empty(); + + + + +.. _BigInt: + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt +.. _TC39 Temporal Proposal: https://tc39.es/proposal-temporal/docs/ +.. _ISO 8601: https://en.wikipedia.org/wiki/ISO_8601#Dates +.. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations +.. _unbalanced: https://tc39.es/proposal-temporal/docs/balancing.html diff --git a/docs/clients/js/select.rst b/docs/clients/js/select.rst new file mode 100644 index 00000000000..801e5be85dc --- /dev/null +++ b/docs/clients/js/select.rst @@ -0,0 +1,679 @@ +.. _edgedb-js-select: + +Select +====== + +The full power of the EdgeQL ``select`` statement is available as a top-level +``e.select`` function. All queries on this page assume the Netflix schema +described on the :ref:`Objects page `. + +Selecting scalars +----------------- + +Any scalar expression be passed into ``e.select``, though it's often +unnecessary, since expressions are ``run``\ able without being wrapped by +``e.select``. + +.. code-block:: typescript + + e.select(e.str('Hello world')); + // select 1234; + + e.select(e.op(e.int64(2), '+', e.int64(2))); + // select 2 + 2; + + +Selecting objects +----------------- + +As in EdgeQL, selecting an set of objects without a shape will return their +``id`` property only. This is reflected in the TypeScript type of the result. + +.. code-block:: typescript + + const query = e.select(e.Movie); + // select Movie; + + const result = await query.run(client); + // {id:string}[] + +Shapes +^^^^^^ + +To specify a shape, pass a function as the second argument. This function +should return an object that specifies which properties to include in the +result. This roughly corresponds to a *shape* in EdgeQL. + +.. code-block:: typescript + + const query = e.select(e.Movie, ()=>({ + id: true, + title: true, + release_year: true, + })); + /* + select Movie { + id, + title, + release_year + } + */ + +Note that the type of the query result is properly inferred from the shape. +This is true for all queries on this page. + +.. code-block:: typescript + + const result = await query.run(client); + /* { + id: string; + title: string; + release_year: number | null; + }[] */ + +As you can see, the type of ``release_year`` is ``number | null`` since +it's an optional property, whereas ``id`` and ``title`` are required. + +Passing a ``boolean`` value (as opposed to a ``true`` literal), which will +make the property optional. Passing ``false`` will exclude that property. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + id: true, + title: Math.random() > 0.5, + release_year: false, + })); + + const result = await query.run(client); + // {id: string; title: string | undefined; release_year: never}[] + +Selecting all properties +^^^^^^^^^^^^^^^^^^^^^^^^ + +For convenience, the query builder provides a shorthand for selecting all +properties of a given object. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + ...e.Movie['*'] + })); + + const result = await query.run(client); + // {id: string; title: string; release_year: number | null}[] + +This ``*`` property is just a strongly-typed, plain object: + +.. code-block:: typescript + + e.Movie['*']; + // => {id: true, title: true, release_year: true} + +Select a single object +^^^^^^^^^^^^^^^^^^^^^^ + +To select a particular object, use the ``filter_single`` key. This tells the query builder to expect a singleton result. + +.. code-block:: typescript + + e.select(e.Movie, (movie) => ({ + id: true, + title: true, + release_year: true, + + filter_single: {id: '2053a8b4-49b1-437a-84c8-e1b0291ccd9f'}, + })); + +This also works if an object type has a composite exclusive constraint: + +.. code-block:: typescript + + /* + type Movie { + ... + constraint exclusive on (.title, .release_year); + } + */ + + e.select(e.Movie, (movie) => ({ + title: true, + filter_single: {title: 'The Avengers', release_year: 2012}, + })); + +You can also pass an arbitrary boolean expression to ``filter_single`` if you prefer. + +.. code-block:: typescript + + const query = e.select(e.Movie, (movie) => ({ + id: true, + title: true, + release_year: true, + filter_single: e.op(movie.id, '=', '2053a8b4-49b1-437a-84c8-e1b0291ccd9f'), + })); + + const result = await query.run(client); + // {id: string; title: string; release_year: number | null} + + +Select many objects by ID +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: typescript + + const query = e.params({ ids: e.array(e.uuid) }, ({ ids }) => + e.select(e.Movie, (movie) => ({ + id: true, + title: true, + release_year: true, + filter: e.op(movie.id, 'in', e.array_unpack(ids)), + })) + + const result = await query.run(client, { + ids: [ + '2053a8b4-49b1-437a-84c8-e1b0291ccd9f', + '2053a8b4-49b1-437a-84c8-af5d3f383484', + ], + }) + // {id: string; title: string; release_year: number | null}[] + +Nesting shapes +^^^^^^^^^^^^^^ + +As in EdgeQL, shapes can be nested to fetch deeply related objects. + +.. code-block:: typescript + + const query = e.select(e.Movie, () => ({ + id: true, + title: true, + actors: { + name: true + } + })); + + const result = await query.run(client); + /* { + id: string; + title: string; + actors: { name: string }[] + }[] */ + + +Portable shapes +^^^^^^^^^^^^^^^ + +You can use ``e.shape`` to define a "portable shape" that can be defined +independently and used in multiple queries. + +.. code-block:: typescript + + const baseShape = e.shape(e.Movie, (m) => ({ + title: true, + num_actors: e.count(m) + })); + + const query = e.select(e.Movie, m => ({ + ...baseShape(m), + release_year: true, + filter_single: {title: 'The Avengers'} + })) + +.. note:: + + Note that the result of ``e.shape`` is a *function*. When you use the shape + in your final queries, be sure to pass in the *scope variable* (e.g. ``m`` + in the example above). This is required for the query builder to correctly + resolve the query. + +Why closures? +------------- + +In EdgeQL, a ``select`` statement introduces a new *scope*; within the clauses +of a select statement, you can refer to fields of the *elements being +selected* using leading dot notation. + +.. code-block:: edgeql + + select Movie { id, title } + filter .title = "The Avengers"; + +Here, ``.title`` is shorthand for the ``title`` property of the selected +``Movie`` elements. All properties/links on the ``Movie`` type can be +referenced using this shorthand anywhere in the ``select`` expression. In +other words, the ``select`` expression is *scoped* to the ``Movie`` type. + +To represent this scoping in the query builder, we use function scoping. This +is a powerful pattern that makes it painless to represent filters, ordering, +computed fields, and other expressions. Let's see it in action. + + +Filtering +--------- + +To add a filtering clause, just include a ``filter`` key in the returned +params object. This should correspond to a boolean expression. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + id: true, + title: true, + filter: e.op(movie.title, 'ilike', "The Matrix%") + })); + /* + select Movie { + id, + title + } filter .title ilike "The Matrix%" + */ + +.. note:: + + Since ``filter`` is a :ref:`reserved keyword ` in + EdgeDB, there is minimal danger of conflicting with a property or link named + ``filter``. All shapes can contain filter clauses, even nested ones. + +If you have many conditions you want to test for, your filter can start to get +difficult to read. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + id: true, + title: true, + filter: e.op( + e.op( + e.op(movie.title, 'ilike', "The Matrix%"), + 'and', + e.op(movie.release_year, '=', 1999) + ), + 'or', + e.op(movie.title, '=', 'Iron Man') + ) + })); + +To improve readability, we recommend breaking these operations out into named +variables and composing them. + +.. code-block:: typescript + + e.select(e.Movie, movie => { + const isAMatrixMovie = e.op(movie.title, 'ilike', "The Matrix%"); + const wasReleased1999 = e.op(movie.release_year, '=', 1999); + const isIronMan = e.op(movie.title, '=', 'Iron Man'); + return { + id: true, + title: true, + filter: e.op( + e.op( + isAMatrixMovie, + 'and', + wasReleased1999 + ), + 'or', + isIronMan + ) + } + }); + +You can combine compound conditions as much or as little as makes sense for +your application. + +.. code-block:: typescript + + e.select(e.Movie, movie => { + const isAMatrixMovie = e.op(movie.title, 'ilike', "The Matrix%"); + const wasReleased1999 = e.op(movie.release_year, '=', 1999); + const isAMatrixMovieReleased1999 = e.op( + isAMatrixMovie, + 'and', + wasReleased1999 + ); + const isIronMan = e.op(movie.title, '=', 'Iron Man'); + return { + id: true, + title: true, + filter: e.op( + isAMatrixMovieReleased1999 + 'or', + isIronMan + ) + } + }); + +If you need to string together several conditions with ``or``, ``e.any`` may be +a better choice. Be sure to wrap your conditions in ``e.set`` since ``e.any`` +takes a set. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + id: true, + title: true, + filter: e.any( + e.set( + e.op(movie.title, "=", "Iron Man"), + e.op(movie.title, "ilike", "guardians%"), + e.op(movie.title, "ilike", "captain%") + ) + ), + })); + +Similarly to ``e.any``, ``e.all`` can replace multiple conditions strung +together with ``and``. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + id: true, + title: true, + filter: e.all( + e.set( + e.op(movie.title, "ilike", "captain%"), + e.op(movie.title, "ilike", "%america%"), + e.op(movie.title, "ilike", "%:%") + ) + ), + })); + +The conditions passed to ``e.any`` or ``e.all`` can be composed just like +before. + +.. code-block:: typescript + + e.select(e.Movie, movie => { + const isIronMan = e.op(movie.title, "=", "Iron Man"); + const startsWithGuardians = e.op(movie.title, "ilike", "guardians%"); + const startsWithCaptain = e.op(movie.title, "ilike", "captain%"); + return { + id: true, + title: true, + filter: e.any( + e.set( + isIronMan, + startsWithGuardians, + startsWithCaptain + ) + ), + } + }); + + +Filters on links +---------------- + +Links can be filtered using traditional filters. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + title: true, + actors: actor => ({ + name: true, + filter: e.op(actor.name.slice(0, 1), '=', 'A'), + }), + filter_single: {title: 'Iron Man'} + })); + + +You can also use the :ref:`type intersection +` operator to filter a link based on its +type. For example, since ``actor.roles`` might be of type ``Movie`` or +``TVShow``, to only return ``roles`` that are ``Movie`` types, you would use +the ``.is`` type intersection operator: + +.. code-block:: typescript + + e.select(e.Actor, actor => ({ + movies: actor.roles.is(e.Movie), + })); + +This is how you would use the EdgeQL :eql:op:`[is type] ` type +intersection operator via the TypeScript query builder. + + +Filters on link properties +-------------------------- + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + title: true, + actors: actor => ({ + name: true, + filter: e.op(actor['@character_name'], 'ilike', 'Tony Stark'), + }), + filter_single: {title: 'Iron Man'} + })); + + +Ordering +-------- + +As with ``filter``, you can pass a value with the special ``order_by`` key. To +simply order by a property: + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + order_by: movie.title, + })); + +.. note:: + + Unlike ``filter``, ``order_by`` is *not* a reserved word in EdgeDB. Using + ``order_by`` as a link or property name will create a naming conflict and + likely cause bugs. + +The ``order_by`` key can correspond to an arbitrary expression. + +.. code-block:: typescript + + // order by length of title + e.select(e.Movie, movie => ({ + order_by: e.len(movie.title), + })); + /* + select Movie + order by len(.title) + */ + + // order by number of actors + e.select(e.Movie, movie => ({ + order_by: e.count(movie.actors), + })); + /* + select Movie + order by count(.actors) + */ + +You can customize the sort direction and empty-handling behavior by passing an +object into ``order_by``. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + order_by: { + expression: movie.title, + direction: e.DESC, + empty: e.EMPTY_FIRST, + }, + })); + /* + select Movie + order by .title desc empty first + */ + +.. list-table:: + + * - Order direction + - ``e.DESC`` ``e.ASC`` + * - Empty handling + - ``e.EMPTY_FIRST`` ``e.EMPTY_LAST`` + +Pass an array of objects to do multiple ordering. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + title: true, + order_by: [ + { + expression: movie.title, + direction: e.DESC, + }, + { + expression: e.count(movie.actors), + direction: e.ASC, + empty: e.EMPTY_LAST, + }, + ], + })); + + +Pagination +---------- + +Use ``offset`` and ``limit`` to paginate queries. You can pass an expression +with an integer type or a plain JS number. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + offset: 50, + limit: e.int64(10), + })); + /* + select Movie + offset 50 + limit 10 + */ + +Computeds +--------- + +To add a computed field, just add it to the returned shape alongside the other +elements. All reflected functions are typesafe, so the output type + +.. code-block:: typescript + + const query = e.select(e.Movie, movie => ({ + title: true, + uppercase_title: e.str_upper(movie.title), + title_length: e.len(movie.title), + })); + + const result = await query.run(client); + /* => + [ + { + title:"Iron Man", + uppercase_title: "IRON MAN", + title_length: 8 + }, + ... + ] + */ + // {name: string; uppercase_title: string, title_length: number}[] + + +Computed fields can "override" an actual link/property as long as the type +signatures agree. + +.. code-block:: typescript + + e.select(e.Movie, movie => ({ + title: e.str_upper(movie.title), // this works + release_year: e.str("2012"), // TypeError + + // you can override links too + actors: e.Person, + })); + + +.. _ref_qb_polymorphism: + +Polymorphism +------------ + +EdgeQL supports polymorphic queries using the ``[is type]`` prefix. + +.. code-block:: edgeql + + select Content { + title, + [is Movie].release_year, + [is TVShow].num_seasons + } + +In the query builder, this is represented with the ``e.is`` function. + +.. code-block:: typescript + + e.select(e.Content, content => ({ + title: true, + ...e.is(e.Movie, { release_year: true }), + ...e.is(e.TVShow, { num_seasons: true }), + })); + + const result = await query.run(client); + /* { + title: string; + release_year: number | null; + num_seasons: number | null; + }[] */ + +The ``release_year`` and ``num_seasons`` properties are nullable to reflect the +fact that they will only occur in certain objects. + +.. note:: + + In EdgeQL it is not valid to select the ``id`` property in a polymorphic + field. So for convenience when using the ``['*']`` all properties shorthand + with ``e.is``, the ``id`` property will be filtered out of the polymorphic + shape object. + + +Detached +-------- + +Sometimes you need to "detach" a set reference from the current scope. (Read the `reference docs `_ for details.) You can achieve this in the query builder with the top-level ``e.detached`` function. + +.. code-block:: typescript + + const query = e.select(e.Person, (outer) => ({ + name: true, + castmates: e.select(e.detached(e.Person), (inner) => ({ + name: true, + filter: e.op(outer.acted_in, 'in', inner.acted_in) + })), + })); + /* + with outer := Person + select Person { + name, + castmates := ( + select detached Person { name } + filter .acted_in in Person.acted_in + ) + } + */ + +Selecting free objects +---------------------- + +Select a free object by passing an object into ``e.select`` + +.. code-block:: typescript + + e.select({ + name: e.str("Name"), + number: e.int64(1234), + movies: e.Movie, + }); + /* select { + name := "Name", + number := 1234, + movies := Movie + } */ diff --git a/docs/clients/js/types.rst b/docs/clients/js/types.rst new file mode 100644 index 00000000000..e77736941a1 --- /dev/null +++ b/docs/clients/js/types.rst @@ -0,0 +1,131 @@ +.. _edgedb-js-types-and-casting: + + +Types +----- + +The entire type system of EdgeDB is reflected in the ``e`` object, including +scalar types, object types, and enums. These types are used in queries for thinks like *casting* and *declaring parameters*. + +.. code-block:: typescript + + e.str; + e.bool; + e.int16; + e.int32; + e.int64; + e.float32; + e.float64; + e.bigint; + e.decimal; + e.datetime; + e.duration; + e.bytes; + e.json; + e.cal.local_datetime; + e.cal.local_date; + e.cal.local_time; + e.cal.relative_duration; + e.cal.date_duration; + + e.Movie; // user-defined object type + e.Genre; // user-defined enum + +You can construct array and tuple types, as in EdgeQL. + +.. code-block:: typescript + + e.array(e.bool); + // array + + e.tuple([e.str, e.int64]); + // tuple + + e.tuple({ + name: e.str, + age: e.int64 + }); + // tuple + + +.. _ref_qb_casting: + +Casting +^^^^^^^ + +These types can be used to *cast* one expression to another type. + +.. code-block:: typescript + + e.cast(e.json, e.int64('123')); + // '123' + + e.cast(e.duration, e.str('127 hours')); + // '127 hours' + +.. note:: + + Scalar types like ``e.str`` serve a dual purpose. They can be used as + functions to instantiate literals (``e.str("hi")``) or used as variables + (``e.cast(e.str, e.int64(123))``). + +Custom literals +^^^^^^^^^^^^^^^ + +You can use ``e.literal`` to create literals corresponding to collection +types like tuples, arrays, and primitives. The first argument expects a type, +the second expects a *value* of that type. + +.. code-block:: typescript + + e.literal(e.str, "sup"); + // equivalent to: e.str("sup") + + e.literal(e.array(e.int16), [1, 2, 3]); + // >[1, 2, 3] + + e.literal(e.tuple([e.str, e.int64]), ['baz', 9000]); + // >("Goku", 9000) + + e.literal( + e.tuple({name: e.str, power_level: e.int64}), + {name: 'Goku', power_level: 9000} + ); + // >("asdf", false) + +Parameters +^^^^^^^^^^ + +Types are also necessary for declaring *query parameters*. + +Pass strongly-typed parameters into your query with ``e.params``. + +.. code-block:: typescript + + const query = e.params({name: e.str}, params => + e.op(e.str("Yer a wizard, "), "++", params.name) + ); + + await query.run(client, {name: "Harry"}); + // => "Yer a wizard, Harry" + + +The full documentation on using parameters is :ref:`here +`. + + +Polymorphism +^^^^^^^^^^^^ + +Types are also used to write polymorphic queries. For full documentation on +this, see :ref:`Polymorphism ` in the ``e.select`` +documentation. + +.. code-block:: typescript + + e.select(e.Content, content => ({ + title: true, + ...e.is(e.Movie, { release_year: true }), + ...e.is(e.TVShow, { num_seasons: true }), + })); + diff --git a/docs/clients/js/update.rst b/docs/clients/js/update.rst new file mode 100644 index 00000000000..b3d9a13d922 --- /dev/null +++ b/docs/clients/js/update.rst @@ -0,0 +1,171 @@ +.. _edgedb-js-update: + +Update +------ + +Update objects with the ``e.update`` function. + +.. code-block:: typescript + + e.update(e.Movie, () => ({ + filter_single: { title: "Avengers 4" }, + set: { + title: "Avengers: Endgame" + } + })) + + +You can reference the current value of the object's properties. + +.. code-block:: typescript + + e.update(e.Movie, (movie) => ({ + filter: e.op(movie.title[0], '=', ' '), + set: { + title: e.str_trim(movie.title) + } + })) + +You can conditionally update a property by using an :ref:`optional parameter +` and the :ref:`coalescing infix operator +`. + +.. code-block:: typescript + + e.params({ id: e.uuid, title: e.optional(e.str) }, (params) => + e.update(e.Movie, (movie) => ({ + filter_single: { id: params.id }, + set: { + title: e.op(params.title, "??", movie.title), + } + })) + ); + +Note that ``e.update`` will return just the ``{ id: true }`` of the updated object. If you want to select further properties, you can wrap the update in a ``e.select`` call. This is still just a single query to the database. + +.. code-block:: typescript + + e.params({ id: e.uuid, title: e.optional(e.str) }, (params) => { + const updated = e.update(e.Movie, (movie) => ({ + filter_single: { id: params.id }, + set: { + title: e.op(params.title, "??", movie.title), + }, + })); + return e.select(updated, (movie) => ({ + title: movie.title, + })); + }); + + + +Updating links +^^^^^^^^^^^^^^ + +EdgeQL supports some convenient syntax for appending to, subtracting from, and +overwriting links. + +.. code-block:: edgeql + + update Movie set { + # overwrite + actors := Person, + + # add to link + actors += Person, + + # subtract from link + actors -= Person + } + +In the query builder this is represented with the following syntax. + +**Overwrite a link** + +.. code-block:: typescript + + const actors = e.select(e.Person, ...); + e.update(e.Movie, movie => ({ + filter_single: {title: 'The Eternals'}, + set: { + actors: actors, + } + })) + +**Add to a link** + +.. code-block:: typescript + + const actors = e.select(e.Person, ...); + e.update(e.Movie, movie => ({ + filter_single: {title: 'The Eternals'}, + set: { + actors: { "+=": actors }, + } + })) + + +**Subtract from a link** + +.. code-block:: typescript + + const actors = e.select(e.Person, ...); + e.update(e.Movie, movie => ({ + filter_single: {title: 'The Eternals'}, + set: { + actors: { "-=": actors }, + } + })) + +**Updating a single link property** + +.. code-block:: typescript + + e.update(e.Movie, (movie) => ({ + filter_single: { title: "The Eternals" }, + set: { + actors: { + "+=": e.select(movie.actors, (actor) => ({ + "@character_name": e.str("Sersi"), + filter: e.op(actor.name, "=", "Gemma Chan") + })) + } + } + })); + +**Updating many link properties** + +.. code-block:: typescript + + const q = e.params( + { + cast: e.array(e.tuple({ name: e.str, character_name: e.str })), + }, + (params) => + e.update(e.Movie, (movie) => ({ + filter_single: { title: "The Eternals" }, + set: { + actors: { + "+=": e.for(e.array_unpack(params.cast), (cast) => + e.select(movie.characters, (character) => ({ + "@character_name": cast.character_name, + filter: e.op(cast.name, "=", character.name), + })), + ), + }, + }, + })), + ).run(client, { + cast: [ + { name: "Gemma Chan", character_name: "Sersi" }, + { name: "Richard Madden", character_name: "Ikaris" }, + { name: "Angelina Jolie", character_name: "Thena" }, + { name: "Salma Hayek", character_name: "Ajak" }, + ], + }); + +Bulk updates +^^^^^^^^^^^^ + +You can use a :ref:`for loop ` to perform :ref:`bulk updates +`. diff --git a/docs/clients/js/with.rst b/docs/clients/js/with.rst new file mode 100644 index 00000000000..255922acd1b --- /dev/null +++ b/docs/clients/js/with.rst @@ -0,0 +1,100 @@ +.. _edgedb-js-with: + +With Blocks +----------- + +During the query rendering step, the number of occurrences of each expression +are tracked. If an expression occurs more than once it is automatically +extracted into a ``with`` block. + +.. code-block:: typescript + + const x = e.int64(3); + const y = e.select(e.op(x, '^', x)); + + y.toEdgeQL(); + // with x := 3 + // select x ^ x + + const result = await y.run(client); + // => 27 + +This hold for expressions of arbitrary complexity. + +.. code-block:: typescript + + const robert = e.insert(e.Person, { + name: "Robert Pattinson" + }); + const colin = e.insert(e.Person, { + name: "Colin Farrell" + }); + const newMovie = e.insert(e.Movie, { + title: "The Batman", + actors: e.set(colin, robert) + }); + + /* + with + robert := (insert Person { name := "Robert Pattinson"}), + colin := (insert Person { name := "Colin Farrell"}), + insert Movie { + title := "The Batman", + actors := {robert, colin} + } + */ + +Note that ``robert`` and ``colin`` were pulled out into a top-level with +block. To force these variables to occur in an internal ``with`` block, you +can short-circuit this logic with ``e.with``. + + +.. code-block:: typescript + + const robert = e.insert(e.Person, { + name: "Robert Pattinson" + }); + const colin = e.insert(e.Person, { + name: "Colin Farrell" + }); + const newMovie = e.insert(e.Movie, { + actors: e.with([robert, colin], // list "dependencies" + e.select(e.set(robert, colin)) + ) + }) + + /* + insert Movie { + title := "The Batman", + actors := ( + with + robert := (insert Person { name := "Robert Pattinson"}), + colin := (insert Person { name := "Colin Farrell"}) + select {robert, colin} + ) + } + */ + + +.. note:: + + It's an error to pass an expression into multiple + ``e.with``\ s, or use an expression passed to ``e.with`` outside of that + block. + +To explicitly create a detached "alias" of another expression, use ``e.alias``. + +.. code-block:: typescript + + const a = e.set(1, 2, 3); + const b = e.alias(a); + + const query = e.select(e.op(a, '*', b)) + // WITH + // a := {1, 2, 3}, + // b := a + // SELECT a + b + + const result = await query.run(client); + // => [1, 2, 3, 2, 4, 6, 3, 6, 9] + diff --git a/docs/clients/python/api/advanced.rst b/docs/clients/python/api/advanced.rst new file mode 100644 index 00000000000..371c26f4a7c --- /dev/null +++ b/docs/clients/python/api/advanced.rst @@ -0,0 +1,288 @@ +.. _edgedb-python-advanced: + +============== +Advanced Usage +============== + +.. py:currentmodule:: edgedb + + +.. _edgedb-python-transaction-options: + +Transaction Options +=================== + +Transactions can be customized with different options: + +.. py:class:: TransactionOptions(isolation=IsolationLevel.Serializable, readonly=False, deferrable=False) + + :param IsolationLevel isolation: transaction isolation level + :param bool readonly: if true the transaction will be readonly + :param bool deferrable: if true the transaction will be deferrable + + .. py:method:: defaults() + :classmethod: + + Returns the default :py:class:`TransactionOptions`. + +.. py:class:: IsolationLevel + + Isolation level for transaction + + .. py:attribute:: Serializable + + Serializable isolation level + + .. py:attribute:: RepeatableRead + + Repeatable read isolation level (supported in read-only transactions) + +:py:class:`TransactionOptions` can be set on :py:class:`~edgedb.Client` or +:py:class:`~edgedb.AsyncIOClient` using one of these methods: + +* :py:meth:`edgedb.Client.with_transaction_options` +* :py:meth:`edgedb.AsyncIOClient.with_transaction_options` + +These methods return a "shallow copy" of the current client object with modified +transaction options. Both ``self`` and the returned object can be used, but +different transaction options will applied respectively. + +Transaction options are used by the future calls to the method +:py:meth:`edgedb.Client.transaction` or :py:meth:`edgedb.AsyncIOClient.transaction`. + + +.. _edgedb-python-retry-options: + +Retry Options +============= + +Individual EdgeQL commands or whole transaction blocks are automatically retried on +retryable errors. By default, edgedb-python will try at most 3 times, with an +exponential backoff time interval starting from 100ms, plus a random hash under 100ms. + +Retry rules can be granularly customized with different retry options: + +.. py:class:: RetryOptions(attempts, backoff=default_backoff) + + :param int attempts: the default number of attempts + :param Callable[[int], Union[float, int]] backoff: the default backoff function + + .. py:method:: with_rule(condition, attempts=None, backoff=None) + + Adds a backoff rule for a particular condition + + :param RetryCondition condition: condition that will trigger this rule + :param int attempts: number of times to retry + :param Callable[[int], Union[float, int]] backoff: + function taking the current attempt number and returning the number + of seconds to wait before the next attempt + + .. py:method:: defaults() + :classmethod: + + Returns the default :py:class:`RetryOptions`. + +.. py:class:: RetryCondition + + Specific condition to retry on for fine-grained control + + .. py:attribute:: TransactionConflict + + Triggered when a TransactionConflictError occurs. + + .. py:attribute:: NetworkError + + Triggered when a ClientError occurs. + +:py:class:`RetryOptions` can be set on :py:class:`~edgedb.Client` or +:py:class:`~edgedb.AsyncIOClient` using one of these methods: + +* :py:meth:`edgedb.Client.with_retry_options` +* :py:meth:`edgedb.AsyncIOClient.with_retry_options` + +These methods return a "shallow copy" of the current client object with modified +retry options. Both ``self`` and the returned object can be used, but different +retry options will applied respectively. + + +.. _edgedb-python-state: + +State +===== + +State is an execution context that affects the execution of EdgeQL commands in +different ways: default module, module aliases, session config and global values. + +.. py:class:: State(default_module=None, module_aliases={}, config={}, globals_={}) + + :type default_module: str or None + :param default_module: + The *default module* that the future commands will be executed with. + ``None`` means the default *default module* on the server-side, + which is usually just ``default``. + + :param dict[str, str] module_aliases: + Module aliases mapping of alias -> target module. + + :param dict[str, object] config: + Non system-level config settings mapping of config name -> config value. + + For available configuration parameters refer to the + :ref:`Config documentation `. + + :param dict[str, object] globals_: + Global values mapping of global name -> global value. + + .. note:: + The global name can be either a qualified name like + ``my_mod::glob2``, or a simple name under the default module. + Simple names will be prefixed with the default module, while module + aliases in qualified names - if any - will be resolved into actual + module names. + + .. py:method:: with_default_module(module=None) + + Returns a new :py:class:`State` copy with adjusted default module. + + .. note:: + This will not affect the globals that are already stored in this + state using simple names, because their names were resolved before + this call to ``with_default_module()``, which affects only the + future calls to the :py:meth:`with_globals` method. + + This is equivalent to using the ``set module`` command, or using the + ``reset module`` command when giving ``None``. + + :type module: str or None + :param module: + Adjust the *default module*. If ``module`` is ``None``, the + *default module* will be reset to default. + + .. py:method:: with_module_aliases(aliases_dict=None, /, **aliases) + + Returns a new :py:class:`State` copy with adjusted module aliases. + + .. note:: + This will not affect the globals that are already stored in this + state using module aliases, because their names were resolved + before this call to ``with_module_aliases()``, which affects only + the future calls to the :py:meth:`with_globals` method. + + This is equivalent to using the ``set alias`` command. + + :type aliases_dict: dict[str, str] or None + :param aliases_dict: + Adjust the module aliases by merging with the given alias -> target + module mapping. This is an optional positional-only argument. + + :param dict[str, str] aliases: + Adjust the module aliases by merging with the given alias -> target + module mapping, after applying ``aliases_dict`` if set. + + .. py:method:: without_module_aliases(*aliases) + + Returns a new :py:class:`State` copy without specified module aliases. + + .. note:: + This will not affect the globals that are already stored in this + state using module aliases, because their names were resolved + before this call to ``without_module_aliases()``, which affects + only the future calls to the :py:meth:`with_globals` method. + + This is equivalent to using the ``reset alias`` command. + + :param tuple[str] aliases: + Adjust the module aliases by dropping the specified aliases if they + were set, no errors will be raised if they weren't. + + If no aliases were given, all module aliases will be dropped. + + .. py:method:: with_config(config_dict=None, /, **config) + + Returns a new :py:class:`State` copy with adjusted session config. + + This is equivalent to using the ``configure session set`` command. + + :type config_dict: dict[str, object] or None + :param config_dict: + Adjust the config settings by merging with the given config name -> + config value mapping. This is an optional positional-only argument. + + :param dict[str, object] config: + Adjust the config settings by merging with the given config name -> + config value mapping, after applying ``config_dict`` if set. + + .. py:method:: without_config(*config_names) + + Returns a new :py:class:`State` copy without specified session config. + + This is equivalent to using the ``configure session reset`` command. + + :param tuple[str] config_names: + Adjust the config settings by resetting the specified config to + default if they were set, no errors will be raised if they weren't. + + If no names were given, all session config will be reset. + + .. py:method:: with_globals(globals_dict=None, /, **globals_) + + Returns a new :py:class:`State` copy with adjusted global values. + + .. note:: + The globals are stored with their names resolved into the actual + fully-qualified names using the current default module and module + aliases set on this state. + + This is equivalent to using the ``set global`` command. + + :type globals_dict: dict[str, object] or None + :param globals_dict: + Adjust the global values by merging with the given global name -> + global value mapping. This is an optional positional-only argument. + + :param dict[str, object] globals_: + Adjust the global values by merging with the given global name -> + global value mapping, after applying ``globals_dict`` if set. + + .. py:method:: without_globals(*global_names) + + Returns a new :py:class:`State` copy without specified globals. + + This is equivalent to using the ``reset global`` command. + + :param tuple[str] global_names: + Adjust the globals by resetting the specified globals to default if + they were set, no errors will be raised if they weren't. + + If no names were given, all globals will be reset. + +:py:class:`State` can be set on :py:class:`~edgedb.Client` or +:py:class:`~edgedb.AsyncIOClient` using one of these methods: + +* :py:meth:`edgedb.Client.with_state` +* :py:meth:`edgedb.AsyncIOClient.with_state` + +These methods return a "shallow copy" of the current client object with +modified state, affecting all future commands executed using the returned copy. +Both ``self`` and the returned object can be used, but different state will +applied respectively. + +Alternatively, shortcuts are available on client objects: + +* :py:meth:`edgedb.Client.with_default_module` +* :py:meth:`edgedb.Client.with_module_aliases` +* :py:meth:`edgedb.Client.without_module_aliases` +* :py:meth:`edgedb.Client.with_config` +* :py:meth:`edgedb.Client.without_config` +* :py:meth:`edgedb.Client.with_globals` +* :py:meth:`edgedb.Client.without_globals` +* :py:meth:`edgedb.AsyncIOClient.with_default_module` +* :py:meth:`edgedb.AsyncIOClient.with_module_aliases` +* :py:meth:`edgedb.AsyncIOClient.without_module_aliases` +* :py:meth:`edgedb.AsyncIOClient.with_config` +* :py:meth:`edgedb.AsyncIOClient.without_config` +* :py:meth:`edgedb.AsyncIOClient.with_globals` +* :py:meth:`edgedb.AsyncIOClient.without_globals` + +They work the same way as ``with_state``, and adjusts the corresponding state +values. diff --git a/docs/clients/python/api/asyncio_client.rst b/docs/clients/python/api/asyncio_client.rst new file mode 100644 index 00000000000..4ec117f290e --- /dev/null +++ b/docs/clients/python/api/asyncio_client.rst @@ -0,0 +1,683 @@ +.. _edgedb-python-asyncio-api-reference: + +=========== +AsyncIO API +=========== + +.. py:currentmodule:: edgedb + + +.. _edgedb-python-async-api-client: + +Client +====== + +.. py:function:: create_async_client(dsn=None, *, \ + host=None, port=None, \ + user=None, password=None, \ + secret_key=None, \ + database=None, \ + timeout=60, \ + concurrency=None) + + Create an asynchronous client with a lazy connection pool. + + The connection parameters may be specified either as a connection + URI in *dsn*, or as specific keyword arguments, or both. + If both *dsn* and keyword arguments are specified, the latter + override the corresponding values parsed from the connection URI. + + If no connection parameter is specified, the client will try to search in + environment variables and then the current project, see :ref:`Client + Library Connection ` docs for more information. + + Returns a new :py:class:`AsyncIOClient` object. + + :param str dsn: + If this parameter does not start with ``edgedb://`` then this is + interpreted as the :ref:`name of a local instance + `. + + Otherwise it specifies a single string in the following format: + ``edgedb://user:password@host:port/database?option=value``. + The following options are recognized: host, port, + user, database, password. For a complete reference on DSN, see + the :ref:`DSN Specification `. + + :param host: + Database host address as an IP address or a domain name; + + If not specified, the following will be tried, in order: + + - host address(es) parsed from the *dsn* argument, + - the value of the ``EDGEDB_HOST`` environment variable, + - ``"localhost"``. + + :param port: + Port number to connect to at the server host. If multiple host + addresses were specified, this parameter may specify a + sequence of port numbers of the same length as the host sequence, + or it may specify a single port number to be used for all host + addresses. + + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_PORT`` environment variable, or ``5656`` + if neither is specified. + + :param user: + The name of the database role used for authentication. + + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_USER`` environment variable, or the + operating system name of the user running the application. + + :param database: + The name of the database to connect to. + + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_DATABASE`` environment variable, or the + operating system name of the user running the application. + + :param password: + Password to be used for authentication, if the server requires + one. If not specified, the value parsed from the *dsn* argument + is used, or the value of the ``EDGEDB_PASSWORD`` environment variable. + Note that the use of the environment variable is discouraged as + other users and applications may be able to read it without needing + specific privileges. + + :param secret_key: + Secret key to be used for authentication, if the server requires one. + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_SECRET_KEY`` environment variable. + Note that the use of the environment variable is discouraged as + other users and applications may be able to read it without needing + specific privileges. + + :param float timeout: + Connection timeout in seconds. + + :param int concurrency: + Max number of connections in the pool. If not set, the suggested + concurrency value provided by the server is used. + + :return: An instance of :py:class:`AsyncIOClient`. + + The APIs on the returned client instance can be safely used by different + :py:class:`asyncio.Task`/coroutines, because under the hood they are + checking out different connections from the pool to run the queries: + + * :py:meth:`AsyncIOClient.query()` + * :py:meth:`AsyncIOClient.query_single()` + * :py:meth:`AsyncIOClient.query_required_single()` + * :py:meth:`AsyncIOClient.query_json()` + * :py:meth:`AsyncIOClient.query_single_json()` + * :py:meth:`AsyncIOClient.query_required_single_json()` + * :py:meth:`AsyncIOClient.execute()` + * :py:meth:`AsyncIOClient.transaction()` + + .. code-block:: python + + client = edgedb.create_async_client() + await client.query('SELECT {1, 2, 3}') + + The same for transactions: + + .. code-block:: python + + client = edgedb.create_async_client() + async for tx in client.transaction(): + async with tx: + await tx.query('SELECT {1, 2, 3}') + + + +.. py:class:: AsyncIOClient() + + An asynchronous client with a connection pool, safe for concurrent use. + + Async clients are created by calling + :py:func:`~edgedb.create_async_client`. + + .. py:coroutinemethod:: query(query, *args, **kwargs) + + Acquire a connection and use it to run a query and return the results + as an :py:class:`edgedb.Set` instance. The temporary + connection is automatically returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + An instance of :py:class:`edgedb.Set` containing + the query result. + + Note that positional and named query arguments cannot be mixed. + + + .. py:coroutinemethod:: query_single(query, *args, **kwargs) + + Acquire a connection and use it to run an optional singleton-returning + query and return its element. The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result. + + The *query* must return no more than one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, ``None`` is returned. + + Note, that positional and named query arguments cannot be mixed. + + + .. py:coroutinemethod:: query_required_single(query, *args, **kwargs) + + Acquire a connection and use it to run a singleton-returning query + and return its element. The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result. + + The *query* must return exactly one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``edgedb.NoDataError`` + is raised. + + Note, that positional and named query arguments cannot be mixed. + + + .. py:coroutinemethod:: query_json(query, *args, **kwargs) + + Acquire a connection and use it to run a query and + return the results as JSON. The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + A JSON string containing an array of query results. + + Note, that positional and named query arguments cannot be mixed. + + .. note:: + + Caution is advised when reading ``decimal`` values using + this method. The JSON specification does not have a limit + on significant digits, so a ``decimal`` number can be + losslessly represented in JSON. However, the default JSON + decoder in Python will read all such numbers as ``float`` + values, which may result in errors or precision loss. If + such loss is unacceptable, then consider casting the value + into ``str`` and decoding it on the client side into a + more appropriate type, such as ``Decimal``. + + + .. py:coroutinemethod:: query_single_json(query, *args, **kwargs) + + Acquire a connection and use it to run an optional singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result encoded in JSON. + + The *query* must return no more than one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, ``"null"`` is returned. + + Note, that positional and named query arguments cannot be mixed. + + .. note:: + + Caution is advised when reading ``decimal`` values using + this method. The JSON specification does not have a limit + on significant digits, so a ``decimal`` number can be + losslessly represented in JSON. However, the default JSON + decoder in Python will read all such numbers as ``float`` + values, which may result in errors or precision loss. If + such loss is unacceptable, then consider casting the value + into ``str`` and decoding it on the client side into a + more appropriate type, such as ``Decimal``. + + + .. py:coroutinemethod:: query_required_single_json(query, *args, **kwargs) + + Acquire a connection and use it to run a singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result encoded in JSON. + + The *query* must return exactly one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``edgedb.NoDataError`` + is raised. + + Note, that positional and named query arguments cannot be mixed. + + .. note:: + + Caution is advised when reading ``decimal`` values using + this method. The JSON specification does not have a limit + on significant digits, so a ``decimal`` number can be + losslessly represented in JSON. However, the default JSON + decoder in Python will read all such numbers as ``float`` + values, which may result in errors or precision loss. If + such loss is unacceptable, then consider casting the value + into ``str`` and decoding it on the client side into a + more appropriate type, such as ``Decimal``. + + + .. py:coroutinemethod:: execute(query) + + Acquire a connection and use it to execute an EdgeQL command + (or commands). The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + + The commands must take no arguments. + + Example: + + .. code-block:: pycon + + >>> await con.execute(''' + ... CREATE TYPE MyType { + ... CREATE PROPERTY a -> int64 + ... }; + ... FOR x IN {100, 200, 300} + ... UNION INSERT MyType { a := x }; + ... ''') + + .. note:: + If the results of *query* are desired, :py:meth:`query`, + :py:meth:`query_single` or :py:meth:`query_required_single` + should be used instead. + + .. py:method:: transaction() + + Open a retryable transaction loop. + + This is the preferred method of initiating and running a database + transaction in a robust fashion. The ``transaction()`` + transaction loop will attempt to re-execute the transaction loop body + if a transient error occurs, such as a network error or a transaction + serialization error. + + Returns an instance of :py:class:`AsyncIORetry`. + + See :ref:`edgedb-python-asyncio-api-transaction` for more details. + + Example: + + .. code-block:: python + + async for tx in con.transaction(): + async with tx: + value = await tx.query_single("SELECT Counter.value") + await tx.execute( + "UPDATE Counter SET { value := $value }", + value=value + 1, + ) + + Note that we are executing queries on the ``tx`` object rather + than on the original connection. + + .. note:: + The transaction starts lazily. A connection is only acquired from + the pool when the first query is issued on the transaction instance. + + + .. py:coroutinemethod:: aclose() + + Attempt to gracefully close all connections in the pool. + + Wait until all pool connections are released, close them and + shut down the pool. If any error (including cancellation) occurs + in ``aclose()`` the pool will terminate by calling + :py:meth:`~edgedb.AsyncIOClient.terminate`. + + It is advisable to use :py:func:`python:asyncio.wait_for` to set + a timeout. + + .. py:method:: terminate() + + Terminate all connections in the pool. + + + .. py:coroutinemethod:: ensure_connected() + + If the client does not yet have any open connections in its pool, + attempts to open a connection, else returns immediately. + + Since the client lazily creates new connections as needed (up to the + configured ``concurrency`` limit), the first connection attempt will + only occur when the first query is run on a client. ``ensureConnected`` + can be useful to catch any errors resulting from connection + mis-configuration by triggering the first connection attempt + explicitly. + + .. py:method:: with_transaction_options(options=None) + + Returns a shallow copy of the client with adjusted transaction options. + + :param TransactionOptions options: + Object that encapsulates transaction options. + + See :ref:`edgedb-python-transaction-options` for details. + + .. py:method:: with_retry_options(options=None) + + Returns a shallow copy of the client with adjusted retry options. + + :param RetryOptions options: Object that encapsulates retry options. + + See :ref:`edgedb-python-retry-options` for details. + + .. py:method:: with_state(state) + + Returns a shallow copy of the client with adjusted state. + + :param State state: Object that encapsulates state. + + See :ref:`edgedb-python-state` for details. + + .. py:method:: with_default_module(module=None) + + Returns a shallow copy of the client with adjusted default module. + + This is equivalent to using the ``set module`` command, or using the + ``reset module`` command when giving ``None``. + + :type module: str or None + :param module: Adjust the *default module*. + + See :py:meth:`State.with_default_module` for details. + + .. py:method:: with_module_aliases(aliases_dict=None, /, **aliases) + + Returns a shallow copy of the client with adjusted module aliases. + + This is equivalent to using the ``set alias`` command. + + :type aliases_dict: dict[str, str] or None + :param aliases_dict: This is an optional positional-only argument. + + :param dict[str, str] aliases: + Adjust the module aliases after applying ``aliases_dict`` if set. + + See :py:meth:`State.with_module_aliases` for details. + + .. py:method:: without_module_aliases(*aliases) + + Returns a shallow copy of the client without specified module aliases. + + This is equivalent to using the ``reset alias`` command. + + :param tuple[str] aliases: Module aliases to reset. + + See :py:meth:`State.without_module_aliases` for details. + + .. py:method:: with_config(config_dict=None, /, **config) + + Returns a shallow copy of the client with adjusted session config. + + This is equivalent to using the ``configure session set`` command. + + :type config_dict: dict[str, object] or None + :param config_dict: This is an optional positional-only argument. + + :param dict[str, object] config: + Adjust the config settings after applying ``config_dict`` if set. + + See :py:meth:`State.with_config` for details. + + .. py:method:: without_config(*config_names) + + Returns a shallow copy of the client without specified session config. + + This is equivalent to using the ``configure session reset`` command. + + :param tuple[str] config_names: Config to reset. + + See :py:meth:`State.without_config` for details. + + .. py:method:: with_globals(globals_dict=None, /, **globals_) + + Returns a shallow copy of the client with adjusted global values. + + This is equivalent to using the ``set global`` command. + + :type globals_dict: dict[str, object] or None + :param globals_dict: This is an optional positional-only argument. + + :param dict[str, object] globals_: + Adjust the global values after applying ``globals_dict`` if set. + + See :py:meth:`State.with_globals` for details. + + .. py:method:: without_globals(*global_names) + + Returns a shallow copy of the client without specified globals. + + This is equivalent to using the ``reset global`` command. + + :param tuple[str] global_names: Globals to reset. + + See :py:meth:`State.without_globals` for details. + + +.. _edgedb-python-asyncio-api-transaction: + +Transactions +============ + +The most robust way to execute transactional code is to use +the ``transaction()`` loop API: + +.. code-block:: python + + async for tx in client.transaction(): + async with tx: + await tx.execute("INSERT User { name := 'Don' }") + +Note that we execute queries on the ``tx`` object in the above +example, rather than on the original ``client`` object. + +The ``tx`` object stores a connection acquired from the pool, so that all +queries can be executed on the same connection in the same transaction. +Transaction start is lazy. ``async for tx`` or ``async with tx`` won't acquire +the connection and start the transaction. It's only done when executing the +first query on the ``tx`` object. That connection is pinned to the ``tx`` +object even when a reconnection is needed, until leaving the final +``async with`` transaction block. + +The ``transaction()`` API guarantees that: + +1. Transactions are executed atomically; +2. If a transaction is failed for any of the number of transient errors (i.e. + a network failure or a concurrent update error), the transaction would + be retried; +3. If any other, non-retryable exception occurs, the transaction is rolled + back, and the exception is propagated, immediately aborting the + ``transaction()`` block. + +The key implication of retrying transactions is that the entire +nested code block can be re-run, including any non-querying +Python code. Here is an example: + +.. code-block:: python + + async for tx in client.transaction(): + async with tx: + user = await tx.query_single( + "SELECT User { email } FILTER .login = $login", + login=login, + ) + data = await httpclient.get( + 'https://service.local/email_info', + params=dict(email=user.email), + ) + user = await tx.query_single(''' + UPDATE User FILTER .login = $login + SET { email_info := $data} + ''', + login=login, + data=data, + ) + +In the above example, the execution of the HTTP request would be retried +too. The core of the issue is that whenever a transaction is interrupted +the user's email might have been changed (as the result of a concurrent +transaction), so we have to redo all the work done. + +Generally it's recommended to not execute any long running +code within the transaction unless absolutely necessary. + +Transactions allocate expensive server resources, and having +too many concurrent long-running transactions will +negatively impact the performance of the DB server. + +To rollback a transaction that is in progress raise an exception. + +.. code-block:: python + + class RollBack(Exception): + "A user defined exception." + + try: + async for tx in client.transaction(): + async with tx: + raise RollBack + except RollBack: + pass + +See also: + +* RFC1004_ +* :py:meth:`AsyncIOClient.transaction()` + + + +.. py:class:: AsyncIORetry + + Represents a wrapper that yields :py:class:`AsyncIOTransaction` + object when iterating. + + See :py:meth:`AsyncIOClient.transaction()` + method for an example. + + .. py:coroutinemethod:: __anext__() + + Yields :py:class:`AsyncIOTransaction` object every time transaction + has to be repeated. + +.. py:class:: AsyncIOTransaction + + Represents a transaction. + + Instances of this type are yielded by a :py:class:`AsyncIORetry` iterator. + + .. describe:: async with c: + + Start and commit/rollback the transaction + automatically when entering and exiting the code inside the + context manager block. + + .. py:coroutinemethod:: query(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a query and return the results + as an :py:class:`edgedb.Set` instance. The temporary + connection is automatically returned back to the pool when exiting the + transaction block. + + See :py:meth:`AsyncIOClient.query() + ` for details. + + .. py:coroutinemethod:: query_single(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run an optional singleton-returning + query and return its element. The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`AsyncIOClient.query_single() + ` for details. + + .. py:coroutinemethod:: query_required_single(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a singleton-returning query + and return its element. The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`AsyncIOClient.query_required_single() + ` for details. + + .. py:coroutinemethod:: query_json(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a query and + return the results as JSON. The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`AsyncIOClient.query_json() + ` for details. + + .. py:coroutinemethod:: query_single_json(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run an optional singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool when exiting the transaction + block. + + See :py:meth:`AsyncIOClient.query_single_json() + ` for details. + + .. py:coroutinemethod:: query_required_single_json(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool when exiting the transaction + block. + + See :py:meth:`AsyncIOClient.query_requried_single_json() + ` for details. + + .. py:coroutinemethod:: execute(query) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to execute an EdgeQL command + (or commands). The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`AsyncIOClient.execute() + ` for details. + +.. _RFC1004: https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst diff --git a/docs/clients/python/api/blocking_client.rst b/docs/clients/python/api/blocking_client.rst new file mode 100644 index 00000000000..94f10dce132 --- /dev/null +++ b/docs/clients/python/api/blocking_client.rst @@ -0,0 +1,679 @@ +.. _edgedb-python-blocking-api-reference: + +============ +Blocking API +============ + +.. py:currentmodule:: edgedb + + +.. _edgedb-python-blocking-api-client: + +Client +====== + +.. py:function:: create_client(dsn=None, *, \ + host=None, port=None, \ + user=None, password=None, \ + secret_key=None, \ + database=None, \ + timeout=60, \ + concurrency=None) + + Create a blocking client with a lazy connection pool. + + The connection parameters may be specified either as a connection + URI in *dsn*, or as specific keyword arguments, or both. + If both *dsn* and keyword arguments are specified, the latter + override the corresponding values parsed from the connection URI. + + If no connection parameter is specified, the client will try to search in + environment variables and then the current project, see :ref:`Client + Library Connection ` docs for more information. + + Returns a new :py:class:`Client` object. + + :param dsn: + If this parameter does not start with ``edgedb://`` then this is + interpreted as the :ref:`name of a local instance + `. + + Otherwise it specifies a single string in the following format: + ``edgedb://user:password@host:port/database?option=value``. + The following options are recognized: host, port, + user, database, password. For a complete reference on DSN, see + the :ref:`DSN Specification `. + + :param host: + Database host address as an IP address or a domain name; + + If not specified, the following will be tried, in order: + + - host address(es) parsed from the *dsn* argument, + - the value of the ``EDGEDB_HOST`` environment variable, + - ``"localhost"``. + + :param port: + Port number to connect to at the server host. If multiple host + addresses were specified, this parameter may specify a + sequence of port numbers of the same length as the host sequence, + or it may specify a single port number to be used for all host + addresses. + + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_PORT`` environment variable, or ``5656`` + if neither is specified. + + :param user: + The name of the database role used for authentication. + + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_USER`` environment variable, or the + operating system name of the user running the application. + + :param database: + The name of the database to connect to. + + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_DATABASE`` environment variable, or the + operating system name of the user running the application. + + :param password: + Password to be used for authentication, if the server requires + one. If not specified, the value parsed from the *dsn* argument + is used, or the value of the ``EDGEDB_PASSWORD`` environment variable. + Note that the use of the environment variable is discouraged as + other users and applications may be able to read it without needing + specific privileges. + + :param secret_key: + Secret key to be used for authentication, if the server requires one. + If not specified, the value parsed from the *dsn* argument is used, + or the value of the ``EDGEDB_SECRET_KEY`` environment variable. + Note that the use of the environment variable is discouraged as + other users and applications may be able to read it without needing + specific privileges. + + :param float timeout: + Connection timeout in seconds. + + :return: An instance of :py:class:`Client`. + + The APIs on the returned client instance can be safely used by different + threads, because under the hood they are + checking out different connections from the pool to run the queries: + + * :py:meth:`Client.query()` + * :py:meth:`Client.query_single()` + * :py:meth:`Client.query_required_single()` + * :py:meth:`Client.query_json()` + * :py:meth:`Client.query_single_json()` + * :py:meth:`Client.query_required_single_json()` + * :py:meth:`Client.execute()` + * :py:meth:`Client.transaction()` + + .. code-block:: python + + client = edgedb.create_client() + client.query('SELECT {1, 2, 3}') + + The same for transactions: + + .. code-block:: python + + client = edgedb.create_client() + for tx in client.transaction(): + with tx: + tx.query('SELECT {1, 2, 3}') + + + +.. py:class:: Client + + A thread-safe blocking client with a connection pool. + + Blocking clients are created by calling :py:func:`create_client`. + + + .. py:method:: query(query, *args, **kwargs) + + Acquire a connection and use it to run a query and return the results + as an :py:class:`edgedb.Set` instance. The temporary + connection is automatically returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + An instance of :py:class:`edgedb.Set` containing + the query result. + + Note that positional and named query arguments cannot be mixed. + + + .. py:method:: query_single(query, *args, **kwargs) + + Acquire a connection and use it to run an optional singleton-returning + query and return its element. The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result. + + The *query* must return no more than one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, ``None`` is returned. + + Note, that positional and named query arguments cannot be mixed. + + + .. py:method:: query_required_single(query, *args, **kwargs) + + Acquire a connection and use it to run a singleton-returning query + and return its element. The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result. + + The *query* must return exactly one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``edgedb.NoDataError`` + is raised. + + Note, that positional and named query arguments cannot be mixed. + + + .. py:method:: query_json(query, *args, **kwargs) + + Acquire a connection and use it to run a query and + return the results as JSON. The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + A JSON string containing an array of query results. + + Note, that positional and named query arguments cannot be mixed. + + .. note:: + + Caution is advised when reading ``decimal`` values using + this method. The JSON specification does not have a limit + on significant digits, so a ``decimal`` number can be + losslessly represented in JSON. However, the default JSON + decoder in Python will read all such numbers as ``float`` + values, which may result in errors or precision loss. If + such loss is unacceptable, then consider casting the value + into ``str`` and decoding it on the client side into a + more appropriate type, such as ``Decimal``. + + + .. py:method:: query_single_json(query, *args, **kwargs) + + Acquire a connection and use it to run an optional singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result encoded in JSON. + + The *query* must return no more than one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, ``"null"`` is returned. + + Note, that positional and named query arguments cannot be mixed. + + .. note:: + + Caution is advised when reading ``decimal`` values using + this method. The JSON specification does not have a limit + on significant digits, so a ``decimal`` number can be + losslessly represented in JSON. However, the default JSON + decoder in Python will read all such numbers as ``float`` + values, which may result in errors or precision loss. If + such loss is unacceptable, then consider casting the value + into ``str`` and decoding it on the client side into a + more appropriate type, such as ``Decimal``. + + + .. py:method:: query_required_single_json(query, *args, **kwargs) + + Acquire a connection and use it to run a singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool. + + :param str query: Query text. + :param args: Positional query arguments. + :param kwargs: Named query arguments. + + :return: + Query result encoded in JSON. + + The *query* must return exactly one element. If the query returns + more than one element, an ``edgedb.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``edgedb.NoDataError`` + is raised. + + Note, that positional and named query arguments cannot be mixed. + + .. note:: + + Caution is advised when reading ``decimal`` values using + this method. The JSON specification does not have a limit + on significant digits, so a ``decimal`` number can be + losslessly represented in JSON. However, the default JSON + decoder in Python will read all such numbers as ``float`` + values, which may result in errors or precision loss. If + such loss is unacceptable, then consider casting the value + into ``str`` and decoding it on the client side into a + more appropriate type, such as ``Decimal``. + + + .. py:method:: execute(query) + + Acquire a connection and use it to execute an EdgeQL command + (or commands). The temporary connection is automatically + returned back to the pool. + + :param str query: Query text. + + The commands must take no arguments. + + Example: + + .. code-block:: pycon + + >>> client.execute(''' + ... CREATE TYPE MyType { + ... CREATE PROPERTY a -> int64 + ... }; + ... FOR x IN {100, 200, 300} + ... UNION INSERT MyType { a := x }; + ... ''') + + .. note:: + If the results of *query* are desired, :py:meth:`query`, + :py:meth:`query_single` or :py:meth:`query_required_single` + should be used instead. + + .. py:method:: transaction() + + Open a retryable transaction loop. + + This is the preferred method of initiating and running a database + transaction in a robust fashion. The ``transaction()`` + transaction loop will attempt to re-execute the transaction loop body + if a transient error occurs, such as a network error or a transaction + serialization error. + + Returns an instance of :py:class:`Retry`. + + See :ref:`edgedb-python-blocking-api-transaction` for more details. + + Example: + + .. code-block:: python + + for tx in client.transaction(): + with tx: + value = tx.query_single("SELECT Counter.value") + tx.execute( + "UPDATE Counter SET { value := $value }", + value=value + 1, + ) + + Note that we are executing queries on the ``tx`` object rather + than on the original connection. + + .. note:: + The transaction starts lazily. A connection is only acquired from + the pool when the first query is issued on the transaction instance. + + + .. py:method:: close(timeout=None) + + Attempt to gracefully close all connections in the pool. + + Wait until all pool connections are released, close them and + shut down the pool. If any error (including timeout) occurs + in ``close()`` the pool will terminate by calling + :py:meth:`~edgedb.Client.terminate`. + + :param float timeout: Seconds to wait, ``None`` for wait forever. + + + .. py:method:: terminate() + + Terminate all connections in the pool. + + + .. py:method:: ensure_connected() + + If the client does not yet have any open connections in its pool, + attempts to open a connection, else returns immediately. + + Since the client lazily creates new connections as needed (up to the + configured ``concurrency`` limit), the first connection attempt will + only occur when the first query is run on a client. ``ensureConnected`` + can be useful to catch any errors resulting from connection + mis-configuration by triggering the first connection attempt + explicitly. + + .. py:method:: with_transaction_options(options=None) + + Returns a shallow copy of the client with adjusted transaction options. + + :param TransactionOptions options: + Object that encapsulates transaction options. + + See :ref:`edgedb-python-transaction-options` for details. + + .. py:method:: with_retry_options(options=None) + + Returns a shallow copy of the client with adjusted retry options. + + :param RetryOptions options: Object that encapsulates retry options. + + See :ref:`edgedb-python-retry-options` for details. + + .. py:method:: with_state(state) + + Returns a shallow copy of the client with adjusted state. + + :param State state: Object that encapsulates state. + + See :ref:`edgedb-python-state` for details. + + .. py:method:: with_default_module(module=None) + + Returns a shallow copy of the client with adjusted default module. + + This is equivalent to using the ``set module`` command, or using the + ``reset module`` command when giving ``None``. + + :type module: str or None + :param module: Adjust the *default module*. + + See :py:meth:`State.with_default_module` for details. + + .. py:method:: with_module_aliases(aliases_dict=None, /, **aliases) + + Returns a shallow copy of the client with adjusted module aliases. + + This is equivalent to using the ``set alias`` command. + + :type aliases_dict: dict[str, str] or None + :param aliases_dict: This is an optional positional-only argument. + + :param dict[str, str] aliases: + Adjust the module aliases after applying ``aliases_dict`` if set. + + See :py:meth:`State.with_module_aliases` for details. + + .. py:method:: without_module_aliases(*aliases) + + Returns a shallow copy of the client without specified module aliases. + + This is equivalent to using the ``reset alias`` command. + + :param tuple[str] aliases: Module aliases to reset. + + See :py:meth:`State.without_module_aliases` for details. + + .. py:method:: with_config(config_dict=None, /, **config) + + Returns a shallow copy of the client with adjusted session config. + + This is equivalent to using the ``configure session set`` command. + + :type config_dict: dict[str, object] or None + :param config_dict: This is an optional positional-only argument. + + :param dict[str, object] config: + Adjust the config settings after applying ``config_dict`` if set. + + See :py:meth:`State.with_config` for details. + + .. py:method:: without_config(*config_names) + + Returns a shallow copy of the client without specified session config. + + This is equivalent to using the ``configure session reset`` command. + + :param tuple[str] config_names: Config to reset. + + See :py:meth:`State.without_config` for details. + + .. py:method:: with_globals(globals_dict=None, /, **globals_) + + Returns a shallow copy of the client with adjusted global values. + + This is equivalent to using the ``set global`` command. + + :type globals_dict: dict[str, object] or None + :param globals_dict: This is an optional positional-only argument. + + :param dict[str, object] globals_: + Adjust the global values after applying ``globals_dict`` if set. + + See :py:meth:`State.with_globals` for details. + + .. py:method:: without_globals(*global_names) + + Returns a shallow copy of the client without specified globals. + + This is equivalent to using the ``reset global`` command. + + :param tuple[str] global_names: Globals to reset. + + See :py:meth:`State.without_globals` for details. + + +.. _edgedb-python-blocking-api-transaction: + +Transactions +============ + +The most robust way to execute transactional code is to use the +``transaction()`` loop API: + +.. code-block:: python + + for tx in client.transaction(): + with tx: + tx.execute("INSERT User { name := 'Don' }") + +Note that we execute queries on the ``tx`` object in the above +example, rather than on the original ``client`` object. + +The ``tx`` object stores a connection acquired from the pool, so that all +queries can be executed on the same connection in the same transaction. +Transaction start is lazy. ``for tx`` or ``with tx`` won't acquire +the connection and start the transaction. It's only done when executing the +first query on the ``tx`` object. That connection is pinned to the ``tx`` +object even when a reconnection is needed, until leaving the final +``with`` transaction block. + +The ``transaction()`` API guarantees that: + +1. Transactions are executed atomically; +2. If a transaction is failed for any of the number of transient errors + (i.e. a network failure or a concurrent update error), the transaction + would be retried; +3. If any other, non-retryable exception occurs, the transaction is + rolled back, and the exception is propagated, immediately aborting the + ``transaction()`` block. + +The key implication of retrying transactions is that the entire +nested code block can be re-run, including any non-querying +Python code. Here is an example: + +.. code-block:: python + + for tx in client.transaction(): + with tx: + user = tx.query_single( + "SELECT User { email } FILTER .login = $login", + login=login, + ) + data = httpclient.get( + 'https://service.local/email_info', + params=dict(email=user.email), + ) + user = tx.query_single(''' + UPDATE User FILTER .login = $login + SET { email_info := $data} + ''', + login=login, + data=data, + ) + +In the above example, the execution of the HTTP request would be retried +too. The core of the issue is that whenever a transaction is interrupted +the user's email might have been changed (as the result of a concurrent +transaction), so we have to redo all the work done. + +Generally it's recommended to not execute any long running +code within the transaction unless absolutely necessary. + +Transactions allocate expensive server resources and having +too many concurrently running long-running transactions will +negatively impact the performance of the DB server. + +To rollback a transaction that is in progress raise an exception. + +.. code-block:: python + + class RollBack(Exception): + "A user defined exception." + + try: + for tx in client.transaction(): + with tx: + raise RollBack + except RollBack: + pass + +See also: + +* RFC1004_ +* :py:meth:`Client.transaction()` + + +.. py:class:: Transaction() + + Represents a transaction. + + Instances of this type are yielded by a :py:class:`Retry` iterator. + + .. describe:: with c: + + start and commit/rollback the transaction + automatically when entering and exiting the code inside the + context manager block. + + .. py:method:: query(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a query and return the results + as an :py:class:`edgedb.Set` instance. The temporary + connection is automatically returned back to the pool when exiting the + transaction block. + + See :py:meth:`Client.query() + ` for details. + + .. py:method:: query_single(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run an optional singleton-returning + query and return its element. The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`Client.query_single() + ` for details. + + .. py:method:: query_required_single(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a singleton-returning query + and return its element. The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`Client.query_required_single() + ` for details. + + .. py:method:: query_json(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a query and + return the results as JSON. The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`Client.query_json() + ` for details. + + .. py:method:: query_single_json(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run an optional singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool when exiting the transaction + block. + + See :py:meth:`Client.query_single_json() + ` for details. + + .. py:method:: query_required_single_json(query, *args, **kwargs) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to run a singleton-returning + query and return its element in JSON. The temporary connection is + automatically returned back to the pool when exiting the transaction + block. + + See :py:meth:`Client.query_requried_single_json() + ` for details. + + .. py:method:: execute(query) + + Acquire a connection if the current transaction doesn't have one yet, + and use it to execute an EdgeQL command + (or commands). The temporary connection is automatically + returned back to the pool when exiting the transaction block. + + See :py:meth:`Client.execute() + ` for details. + +.. py:class:: Retry + + Represents a wrapper that yields :py:class:`Transaction` + object when iterating. + + See :py:meth:`Client.transaction()` method for + an example. + + .. py:method:: __next__() + + Yields :py:class:`Transaction` object every time transaction has to + be repeated. + + +.. _RFC1004: https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst diff --git a/docs/clients/python/api/codegen.rst b/docs/clients/python/api/codegen.rst new file mode 100644 index 00000000000..c62abccffb6 --- /dev/null +++ b/docs/clients/python/api/codegen.rst @@ -0,0 +1,96 @@ +.. _edgedb-python-codegen: + +=============== +Code Generation +=============== + +.. py:currentmodule:: edgedb + +The ``edgedb-python`` package exposes a command-line tool to generate +typesafe functions from ``*.edgeql`` files, using :py:mod:`dataclasses` for +objects primarily. + +.. code-block:: bash + + $ edgedb-py + +Or alternatively: + +.. code-block:: bash + + $ python -m edgedb.codegen + +Consider a simple query that lives in a file called ``get_number.edgeql``: + +.. code-block:: edgeql + + select $arg; + +Running the code generator will generate a new file called +``get_number_async_edgeql.py`` containing the following code (roughly): + +.. code-block:: python + + from __future__ import annotations + import edgedb + + + async def get_number( + client: edgedb.AsyncIOClient, + *, + arg: int, + ) -> int: + return await client.query_single( + """\ + select $arg\ + """, + arg=arg, + ) + +Target +~~~~~~ + +By default, the generated code uses an ``async`` API. The generator supports +additional targets via the ``--target`` flag. + +.. code-block:: bash + + $ edgedb-py --target async # generate async function (default) + $ edgedb-py --target blocking # generate blocking code + +The names of the generated files will differ accordingly: +``{query_filename}_{target}_edgeql.py``. + +Single-file mode +~~~~~~~~~~~~~~~~ + +It may be preferable to generate a single file containing all the generated +functions. This can be done by passing the ``--file`` flag. + +.. code-block:: bash + + $ edgedb-py --file + +This generates a single file called ``generated_{target}_edgeql.py`` in the +root of your project. + +Connection +~~~~~~~~~~ + +The ``edgedb-py`` command supports the same set of :ref:`connection options +` as the ``edgedb`` CLI. + +.. code-block:: + + -I, --instance + --dsn + --credentials-file + -H, --host + -P, --port + -d, --database + -u, --user + --password + --password-from-stdin + --tls-ca-file + --tls-security + diff --git a/docs/clients/python/api/types.rst b/docs/clients/python/api/types.rst new file mode 100644 index 00000000000..c90710e5972 --- /dev/null +++ b/docs/clients/python/api/types.rst @@ -0,0 +1,296 @@ +.. _edgedb-python-datatypes: + +========= +Datatypes +========= + +.. py:currentmodule:: edgedb + + +edgedb-python automatically converts EdgeDB types to the corresponding Python +types and vice versa. + +The table below shows the correspondence between EdgeDB and Python types. + ++----------------------------+-----------------------------------------------------+ +| EdgeDB Type | Python Type | ++============================+=====================================================+ +| ``Set`` | :py:class:`edgedb.Set` | ++----------------------------+-----------------------------------------------------+ +| ``array`` | :py:class:`edgedb.Array` | ++----------------------------+-----------------------------------------------------+ +| ``anytuple`` | :py:class:`edgedb.Tuple` or | +| | :py:class:`edgedb.NamedTuple` | ++----------------------------+-----------------------------------------------------+ +| ``anyenum`` | :py:class:`edgedb.EnumValue` | ++----------------------------+-----------------------------------------------------+ +| ``Object`` | :py:class:`edgedb.Object` | ++----------------------------+-----------------------------------------------------+ +| ``bool`` | :py:class:`bool ` | ++----------------------------+-----------------------------------------------------+ +| ``bytes`` | :py:class:`bytes ` | ++----------------------------+-----------------------------------------------------+ +| ``str`` | :py:class:`str ` | ++----------------------------+-----------------------------------------------------+ +| ``cal::local_date`` | :py:class:`datetime.date ` | ++----------------------------+-----------------------------------------------------+ +| ``cal::local_time`` | offset-naive :py:class:`datetime.time \ | +| | ` | ++----------------------------+-----------------------------------------------------+ +| ``cal::local_datetime`` | offset-naive :py:class:`datetime.datetime \ | +| | ` | ++----------------------------+-----------------------------------------------------+ +| ``cal::relative_duration`` | :py:class:`edgedb.RelativeDuration` | ++----------------------------+-----------------------------------------------------+ +| ``cal::date_duration`` | :py:class:`edgedb.DateDuration` | ++----------------------------+-----------------------------------------------------+ +| ``datetime`` | offset-aware :py:class:`datetime.datetime \ | +| | ` | ++----------------------------+-----------------------------------------------------+ +| ``duration`` | :py:class:`datetime.timedelta \ | +| | ` | ++----------------------------+-----------------------------------------------------+ +| ``float32``, | :py:class:`float ` | +| ``float64`` | | ++----------------------------+-----------------------------------------------------+ +| ``int16``, | :py:class:`int ` | +| ``int32``, | | +| ``int64``, | | +| ``bigint`` | | ++----------------------------+-----------------------------------------------------+ +| ``decimal`` | :py:class:`Decimal ` | ++----------------------------+-----------------------------------------------------+ +| ``json`` | :py:class:`str ` | ++----------------------------+-----------------------------------------------------+ +| ``uuid`` | :py:class:`uuid.UUID ` | ++----------------------------+-----------------------------------------------------+ + +.. note:: + + Inexact single-precision ``float`` values may have a different + representation when decoded into a Python float. This is inherent + to the implementation of limited-precision floating point types. + If you need the decimal representation to match, cast the expression + to ``float64`` or ``decimal`` in your query. + + +.. _edgedb-python-types-set: + +Sets +==== + +.. py:class:: Set() + + This is :py:class:`list ` since version 1.0. + + +.. _edgedb-python-types-object: + +Objects +======= + +.. py:class:: Object() + + An immutable representation of an object instance returned from a query. + + .. versionchanged:: 1.0 + + ``edgedb.Object`` instances are dataclass-compatible since version 1.0, + for example, ``dataclasses.is_dataclass()`` will return ``True``, and + ``dataclasses.asdict()`` will work on ``edgedb.Object`` instances. + + .. versionchanged:: 1.0 + + ``edgedb.Object.__hash__`` is just ``object.__hash__`` in version 1.0. + Similarly, ``==`` is equivalent to the ``is`` operator comparing + ``edgedb.Object`` instances, and ``<``, ``<=``, ``>``, ``>=`` are not + allowed on ``edgedb.Object`` instances. + + The value of an object property or a link can be accessed through + a corresponding attribute: + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single(''' + ... SELECT schema::ObjectType {name} + ... FILTER .name = 'std::Object' + ... LIMIT 1''') + >>> r + Object{name := 'std::Object'} + >>> r.name + 'std::Object' + + .. describe:: obj[linkname] + + Return a :py:class:`edgedb.Link` or a :py:class:`edgedb.LinkSet` instance + representing the instance(s) of link *linkname* associated with + *obj*. + + Example: + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single(''' + ... SELECT schema::Property {name, annotations: {name, @value}} + ... FILTER .name = 'listen_port' + ... AND .source.name = 'cfg::Config' + ... LIMIT 1''') + >>> r + Object { + name: 'listen_port', + annotations: { + Object { + name: 'cfg::system', + @value: 'true' + } + } + } + >>> r['annotations'] + LinkSet(name='annotations') + >>> l = list(r['annotations])[0] + >>> l.value + 'true' + + +Links +===== + +.. py:class:: Link + + An immutable representation of an object link. + + Links are created when :py:class:`edgedb.Object` is accessed via + a ``[]`` operator. Using Link objects explicitly is useful for + accessing link properties. + + +.. py:class:: LinkSet + + An immutable representation of a set of Links. + + LinkSets are created when a multi link on :py:class:`edgedb.Object` + is accessed via a ``[]`` operator. + + +Tuples +====== + +.. py:class:: Tuple() + + This is :py:class:`tuple ` since version 1.0. + + +Named Tuples +============ + +.. py:class:: NamedTuple() + + An immutable value representing an EdgeDB named tuple value. + + .. versionchanged:: 1.0 + + ``edgedb.NamedTuple`` is a subclass of :py:class:`tuple ` + and is duck-type compatible with ``collections.namedtuple`` since + version 1.0. + + Instances of ``edgedb.NamedTuple`` generally behave similarly to + :py:func:`namedtuple `: + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single('''SELECT (a := 1, b := 'a', c := [3])''') + >>> r + (a := 1, b := 'a', c := [3]) + >>> r.b + 'a' + >>> r[0] + 1 + >>> r == (1, 'a', [3]) + True + >>> r._fields + ('a', 'b', 'c') + + +Arrays +====== + +.. py:class:: Array() + + This is :py:class:`list ` since version 1.0. + + +RelativeDuration +================ + +.. py:class:: RelativeDuration() + + An immutable value representing an EdgeDB ``cal::relative_duration`` value. + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single('''SELECT "1 year 2 days 3 seconds"''') + >>> r + + >>> r.months + 12 + >>> r.days + 2 + >>> r.microseconds + 3000000 + + +DateDuration +============ + +.. py:class:: DateDuration() + + An immutable value representing an EdgeDB ``cal::date_duration`` value. + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single('''SELECT "1 year 2 days"''') + >>> r + + >>> r.months + 12 + >>> r.days + 2 + + +EnumValue +========= + +.. py:class:: EnumValue() + + An immutable value representing an EdgeDB enum value. + + .. versionchanged:: 1.0 + + Since version 1.0, ``edgedb.EnumValue`` is a subclass of + :py:class:`enum.Enum `. Actual enum values are + instances of ad-hoc enum classes created by the codecs to represent + the actual members defined in your EdgeDB schema. + + .. code-block:: pycon + + >>> import edgedb + >>> client = edgedb.create_client() + >>> r = client.query_single("""SELECT 'red'""") + >>> r + + >>> str(r) + 'red' + >>> r.value # added in 1.0 + 'red' + >>> r.name # added in 1.0, simply str.upper() of r.value + 'RED' diff --git a/docs/clients/python/index.rst b/docs/clients/python/index.rst index 51537be37d0..1a647fb6fea 100644 --- a/docs/clients/python/index.rst +++ b/docs/clients/python/index.rst @@ -1,9 +1,54 @@ .. _edgedb-python-intro: -====== -Python -====== +==================== +EdgeDB Python Driver +==================== -The documentation for the Python client is automatically pulled -from https://github.com/edgedb/edgedb-python/tree/master/docs by the -build pipeline of the edgedb.com website. +**edgedb-python** is the official EdgeDB driver for Python. +It provides both :ref:`blocking IO ` +and :ref:`asyncio ` implementations. + +.. rubric:: Contents + +* :ref:`edgedb-python-installation` + + edgedb-python is installable via ``$ pip install edgedb``. Read + the section for more information on how to install the library. + +* :ref:`edgedb-python-examples` + + High-level examples on how to use blocking and asyncio connections, + as well as on how to work with transactions. + +* :ref:`edgedb-python-asyncio-api-reference` + + Asynchronous API reference. + +* :ref:`edgedb-python-blocking-api-reference` + + Synchronous API reference. + +* :ref:`edgedb-python-datatypes` + + EdgeDB Python types documentation. + +* :ref:`edgedb-python-codegen` + + Python code generation command-line tool documentation. + +* :ref:`edgedb-python-advanced` + + Advanced usages of the state and optional customization. + + +.. toctree:: + :maxdepth: 3 + :hidden: + + installation + usage + api/asyncio_client + api/blocking_client + api/types + api/codegen + api/advanced diff --git a/docs/clients/python/installation.rst b/docs/clients/python/installation.rst new file mode 100644 index 00000000000..b22c81ff0bb --- /dev/null +++ b/docs/clients/python/installation.rst @@ -0,0 +1,56 @@ +.. _edgedb-python-installation: + + +Installation +============ + +The recommended way to install the EdgeDB driver is to use **pip**: + +.. code-block:: bash + + $ pip install edgedb + + +.. note:: + + It is recommended to use **pip** version **8.1** or later to take + advantage of the precompiled wheel packages. Older versions of pip + will ignore the wheel packages and install from the source + package. In that case a working C compiler is required. + + +Building from source +-------------------- + +If you want to build the EdgeDB driver from a Git checkout you will need: + +* A working C compiler. +* CPython header files. These can usually be obtained by installing + the relevant Python development package: **python3-dev** on Debian/Ubuntu, + **python3-devel** on RHEL/Fedora. + +Once the above requirements are satisfied, run the following command +in the root of the source checkout: + +.. code-block:: bash + + $ pip install -e . + +A debug build containing more runtime checks at the expense of performance +can be created by setting the ``EDGEDB_DEBUG`` environment variable when +building: + +.. code-block:: bash + + $ env EDGEDB_DEBUG=1 pip install -e . + + +Running tests +------------- + +The testsuite requires a working local installation of the EdgeDB server. +To execute the testsuite run: + +.. code-block:: bash + + $ python setup.py test diff --git a/docs/clients/python/requirements.txt b/docs/clients/python/requirements.txt new file mode 100644 index 00000000000..910cf81fc97 --- /dev/null +++ b/docs/clients/python/requirements.txt @@ -0,0 +1,2 @@ +sphinxcontrib-asyncio +sphinx_rtd_theme diff --git a/docs/clients/python/usage.rst b/docs/clients/python/usage.rst new file mode 100644 index 00000000000..e222ecad3b8 --- /dev/null +++ b/docs/clients/python/usage.rst @@ -0,0 +1,176 @@ +.. _edgedb-python-examples: + +Basic Usage +=========== + +To start using EdgeDB in Python, create an :py:class:`edgedb.Client` instance +using :py:func:`edgedb.create_client`: + +.. code-block:: python + + import datetime + import edgedb + + client = edgedb.create_client() + + client.query(""" + INSERT User { + name := $name, + dob := $dob + } + """, name="Bob", dob=datetime.date(1984, 3, 1)) + + user_set = client.query( + "SELECT User {name, dob} FILTER .name = $name", name="Bob") + # *user_set* now contains + # Set{Object{name := 'Bob', dob := datetime.date(1984, 3, 1)}} + + client.close() + +When used with asyncio, this should be replaced with +:py:func:`edgedb.create_async_client` which creates an instance of the +:py:class:`~edgedb.AsyncIOClient`: + +.. code-block:: python + + import asyncio + import datetime + import edgedb + + client = edgedb.create_async_client() + + async def main(): + await client.query(""" + INSERT User { + name := $name, + dob := $dob + } + """, name="Bob", dob=datetime.date(1984, 3, 1)) + + user_set = await client.query( + "SELECT User {name, dob} FILTER .name = $name", name="Bob") + # *user_set* now contains + # Set{Object{name := 'Bob', dob := datetime.date(1984, 3, 1)}} + + await client.aclose() + + asyncio.run(main()) + + +Connect to EdgeDB +----------------- + +The examples above only work under an :ref:`EdgeDB project +`. You could also provide your own connection +parameters, refer to the :ref:`Client Library Connection +` docs for details. + + +Type conversion +--------------- + +edgedb-python automatically converts EdgeDB types to the corresponding Python +types and vice versa. See :ref:`edgedb-python-datatypes` for details. + + +.. _edgedb-python-connection-pool: + +Client connection pools +----------------------- + +For server-type applications that handle frequent requests and need +the database connection for a short period of time while handling a request, +the use of a connection pool is recommended. Both :py:class:`edgedb.Client` +and :py:class:`edgedb.AsyncIOClient` come with such a pool. + +For :py:class:`edgedb.Client`, all methods are thread-safe. You can share the +same client instance safely across multiple threads, and run queries +concurrently. Likewise, :py:class:`~edgedb.AsyncIOClient` is designed to be +shared among different :py:class:`asyncio.Task`/coroutines for concurrency. + +Below is an example of a web API server running `aiohttp +`_: + +.. code-block:: python + + import asyncio + import edgedb + from aiohttp import web + + + async def handle(request): + """Handle incoming requests.""" + client = request.app['client'] + username = request.match_info.get('name') + + # Execute the query on any pool connection + result = await client.query_single_json( + ''' + SELECT User {first_name, email, bio} + FILTER .name = $username + ''', username=username) + return web.Response( + text=result, + content_type='application/json') + + + def init_app(): + """Initialize the application server.""" + app = web.Application() + # Create a database client + app['client'] = edgedb.create_async_client( + database='my_service', + user='my_service') + # Configure service routes + app.router.add_route('GET', '/user/{name}', handle) + return app + + + loop = asyncio.get_event_loop() + app = init_app() + web.run_app(app) + +Note that the client is created synchronously. Pool connections are created +lazily as they are needed. If you want to explicitly connect to the +database in ``init_app()``, use the ``ensure_connected()`` method on the client. + +For more information, see API documentation of :ref:`the blocking client +` and :ref:`the asynchronous client +`. + + +Transactions +------------ + +The most robust way to create a +:ref:`transaction ` is the +``transaction()`` method: + +* :py:meth:`AsyncIOClient.transaction() ` +* :py:meth:`Client.transaction() ` + + +Example: + +.. code-block:: python + + for tx in client.transaction(): + with tx: + tx.execute("INSERT User {name := 'Don'}") + +or, if using the async API: + +.. code-block:: python + + async for tx in client.transaction(): + async with tx: + await tx.execute("INSERT User {name := 'Don'}") + +.. note:: + + When not in an explicit transaction block, any changes to the database + will be applied immediately. + +For more information, see API documentation of transactions for :ref:`the +blocking client ` and :ref:`the +asynchronous client `. diff --git a/docs/clients/rust/arguments.rst b/docs/clients/rust/arguments.rst deleted file mode 100644 index 529e809f175..00000000000 --- a/docs/clients/rust/arguments.rst +++ /dev/null @@ -1,81 +0,0 @@ -.. _ref_rust_arguments: - -Passing in arguments --------------------- - -A regular EdgeQL query without arguments looks like this: - -.. code-block:: edgeql - - with - message1 := 'Hello there', - message2 := 'General Kenobi', - select message1 ++ ' ' ++ message2; - -And the same query with arguments: - -.. code-block:: edgeql - - with - message1 := $0, - message2 := $1, - select message1 ++ ' ' ++ message2; - -In the EdgeQL REPL you are prompted to enter arguments: - -.. code-block:: edgeql-repl - - db> with - ... message1 := $0, - ... message2 := $1, - ... select message1 ++ ' ' ++ message2; - Parameter $0: Hello there - Parameter $1: General Kenobi - {'Hello there General Kenobi'} - -But when using the Rust client, there is no prompt to do so. At present, -arguments also have to be in the order ``$0``, ``$1``, and so on, while in -the REPL they can be named (e.g. ``$message`` and ``$person`` instead of -``$0`` and ``$1``). The arguments in the client are then passed to the -appropriate query method as a tuple: - -.. code-block:: rust - - let args = ("Nice movie", 2023); - let query = "with - movie := (insert Movie { - title := $0, - release_year := $1 - }) - select { - title, - release_year, - id - }"; - let query_res: Value = client.query_required_single(query, &(args)).await?; - -A note on the casting syntax: EdgeDB requires arguments to have a cast in the -same way that Rust requires a type declaration in function signatures. -As such, arguments in queries are used as type specification for the EdgeDB -compiler, not to cast from queries from the Rust side. Take this query -as an example: - -.. code-block:: rust - - let query = "select $0"; - -This simply means "select an argument that must be an ``int32``", not -"take the received argument and cast it into an ``int32``". - -As such, this will return an error: - -.. code-block:: rust - - let query = "select $0"; - let arg = 9i16; // Rust client will expect an int16 - let query_res: Result = - client.query_required_single(query, &(arg,)).await; - assert!(query_res - .unwrap_err() - .to_string() - .contains("expected std::int16")); \ No newline at end of file diff --git a/docs/clients/rust/client.rst b/docs/clients/rust/client.rst deleted file mode 100644 index 98c2f52c4ec..00000000000 --- a/docs/clients/rust/client.rst +++ /dev/null @@ -1,113 +0,0 @@ -.. _ref_rust_client: - -Using the client ----------------- - -Creating a new EdgeDB client can be done in a single line: - -.. code-block:: rust - - let client = edgedb_tokio::create_client().await?; - -Under the hood, this will create a ``Builder``, look for environment variables -and/or an ``edgedb.toml`` file, and return an ``Ok(Self)`` if successful. -This ``Builder`` can be used on its own instead of ``create_client()`` -if you need a more customized setup. - -Queries with the client ------------------------ - -Here are the simplified signatures of the client methods used for querying: - -.. note:: - ``R`` here means a type that implements ``QueryResult``. - (See more on ``QueryResult`` and ``QueryArgs`` on the - `edgedb-protocol documentation`_.) - -.. code-block:: rust - - fn query -> Result, Error> - fn query_json -> Result - - fn query_single -> Result, Error> - fn query_single_json -> Result> - - fn query_required_single -> Result - fn query_required_single_json -> Result - - fn execute -> Result<(), Error> - -Note the difference between the ``_single`` and the -``_required_single`` methods: - -- The ``_required_single`` methods return empty results as a ``NoDataError`` - which allows propagating errors normally through an application. -- The ``_single`` methods will simply give you an ``Ok(None)`` in this case. - -These methods all take a *query* (a ``&str``) and *arguments* (something that -implements the ``QueryArgs`` trait). - -The ``()`` unit type implements ``QueryArgs`` and is used when no arguments -are present so ``&()`` is a pretty common sight when using the Rust client. - -.. code-block:: rust - - // Without arguments: just add &() after the query - let query_res: String = - client.query_required_single("select 'Just a string'", &()).await?; - - // With arguments, same output as the previous example - let a = " a "; - let b = "string"; - let query_res: String = client - .query_required_single("select 'Just' ++ $0 ++ $1", &(a, b)) - .await?; - -For more, see the section on :ref:`passing in arguments `. - -These methods take two generic parameters which can be specified with the -turbofish syntax: - -.. code-block:: rust - - let query_res = client - .query_required_single::("select 'Just a string'", &()) - .await?; - // or - let query_res = client - .query_required_single::("select 'Just a string'", &()) - .await?; - -But declaring the final expected type upfront tends to look neater. - -.. code-block:: rust - - let query_res: String = client - .query_required_single("select 'Just a string'", &()) - .await?; - -When cardinality is guaranteed to be 1 --------------------------------------- - -Using the ``.query()`` method works fine for any cardinality, but returns a -``Vec`` of results. This query with a cardinality of 1 returns a -``Result>`` which becomes a ``Vec`` after the error -is handled: - -.. code-block:: rust - - let query = "select 'Just a string'"; - let query_res: Vec = client.query(query, &()).await?; - -But if you know that only a single result will be returned, using -``.query_required_single()`` or ``.query_single()`` will be more ergonomic: - -.. code-block:: rust - - let query = "select 'Just a string'"; - let query_res: String = client - .query_required_single(query, &()).await?; - let query_res_opt: Option = client - .query_single(query, &()).await?; - -.. _`edgedb-protocol documentation`: https://docs.rs/edgedb-protocol/ \ No newline at end of file diff --git a/docs/clients/rust/client_config.rst b/docs/clients/rust/client_config.rst deleted file mode 100644 index d31d5a7cbab..00000000000 --- a/docs/clients/rust/client_config.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. _ref_rust_client_config: - -Client configuration --------------------- - -The client can be configured after initialization via the ``with_*`` methods -(``with_retry_options``, ``with_transaction_options``, etc.) that create a -shallow copy of the client with adjusted options. - -.. code-block:: rust - - // Take a schema with matching Rust structs: - // - // module default { - // type User { - // required name: str; - // } - // } - - // module test { - // type User { - // required name: str; - // } - // }; - - // The regular client will query from module 'default' by default - let client = edgedb_tokio::create_client().await?; - - // This client will query from module 'test' by default - // The original client is unaffected - let test_client = client.with_default_module(Some("test")); - - // Each client queries separately with different behavior - let query = "select User {name};"; - let users: Vec = client.query(query, &()).await?; - let test_users: Vec = test_client.query(query, &()).await?; - - // Many other clients can be created with different options, - // all independent of the main client: - let transaction_opts = TransactionOptions::default().read_only(true); - let _read_only_client = client - .with_transaction_options(transaction_opts); - - let retry_opts = RetryOptions::default().with_rule( - RetryCondition::TransactionConflict, - // No. of retries - 1, - // Retry immediately, instead of default with increasing backoff - |_| std::time::Duration::from_millis(0), - ); - let _one_immediate_retry_client = client.with_retry_options(retry_opts); diff --git a/docs/clients/rust/execute.rst b/docs/clients/rust/execute.rst deleted file mode 100644 index ed685cf6c30..00000000000 --- a/docs/clients/rust/execute.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _ref_rust_execute: - -Execute -------- - -The ``execute`` method doesn't return anything — a successful execute returns -an ``Ok(())`` — which is convenient for things like updates or commands where -we don't care about getting output if it works: - -.. code-block:: rust - - client.execute("update Account set {username := .username ++ '!'};", &()) - .await?; - client.execute("create superuser role project;", &()) - .await?; - client.execute("alter role project set password := 'GOODpassword';", &()) - .await?; - - // Returns Ok(()) upon success but error info will be returned - let command = client.execute("create type MyType {};", &()).await; - let command_string = command.unwrap_err().to_string(); - assert!(command_string.contains("bare DDL statements are not allowed")); \ No newline at end of file diff --git a/docs/clients/rust/getting_started.rst b/docs/clients/rust/getting_started.rst deleted file mode 100644 index b73a1f78476..00000000000 --- a/docs/clients/rust/getting_started.rst +++ /dev/null @@ -1,141 +0,0 @@ -.. _ref_rust_getting_started: - -=============== -Getting started -=============== - -From examples repo -================== - -If you just want a working repo to get started, clone the Rust client -`examples repo`_, type ``edgedb project init`` to start an EdgeDB -project, and then ``cargo run`` to run the samples. - -This tutorial contains a lot of similar examples to those found in the -``main.rs`` file inside that repo. - -From scratch -============ - -The minimum to add to your Cargo.toml to use the client is `edgedb-tokio`_: - -.. code-block:: toml - - edgedb-tokio = "0.5.0" - -The next most common dependency is `edgedb-protocol`_, which includes the -EdgeDB types used for data modeling: - -.. code-block:: toml - - edgedb-protocol = "0.6.0" - -A third crate called `edgedb-derive`_ contains the ``#[derive(Queryable)]`` -derive macro which is the main way to unpack EdgeDB output into Rust types: - -.. code-block:: toml - - edgedb-derive = "0.5.1" - -The Rust client uses tokio so add this to Cargo.toml as well: - -.. code-block:: toml - - tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] } - -If you are avoiding async code and want to emulate a blocking client, you will -still need to use tokio as a dependency but can bridge with async using one of -the `bridging methods`_ recommended by tokio. This won't require any -added features: - -.. code-block:: toml - - tokio = "1.29.1" - -Then you can start a runtime. Block and wait for futures to resolve by calling -the runtime's ``.block_on()`` method: - -.. code-block:: rust - - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build()?; - let just_a_string: String = - rt.block_on(client.query_required_single("select 'A string'", &()))?; - -Edgedb project setup -==================== - -The EdgeDB CLI initializes an EdgeDB project with a single command in the same -way that Cargo initializes a Rust project, except it does not create a -new directory. So to start a project: - -- Use ``cargo new `` as usual, then: -- Go into the directory and type ``edgedb project init``. - -The CLI will prompt you for the instance name and version of EdgeDB to use. -It will look something like this: - -.. code-block:: powershell - - PS> edgedb project init - No `edgedb.toml` found in `\\?\C:\rust\my_db` or above - Do you want to initialize a new project? [Y/n] - > Y - Specify the name of EdgeDB instance to use - with this project [default: my_db]: - > my_db - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: 3.0]: - > 3.0 - ┌─────────────────────┬─────────────────────────────────┐ - │ Project directory │ \\?\C:\rust\my_db │ - │ Project config │ \\?\C:\rust\my_db\edgedb.toml │ - │ Schema dir (empty) │ \\?\C:\rust\my_db\dbschema │ - │ Installation method │ WSL │ - │ Version │ 3.0+e7d38e9 │ - │ Instance name │ my_db │ - └─────────────────────┴─────────────────────────────────┘ - Version 3.0+e7d38e9 is already installed - Initializing EdgeDB instance... - Applying migrations... - Everything is up to date. Revision initial - Project initialized. - To connect to my_db, run `edgedb` - -Inside your project directory you'll notice some new items: - -- ``edgedb.toml``, which is used to mark the directory as an EdgeDB project. - -The file itself doesn't contain much — just the version of EdgeDB being -used — but is used by the CLI to run commands without connection flags. -(E.g., ``edgedb -I my_project migrate`` becomes simply ``edgedb migrate``). -See more in our :ref:`edgedb.toml reference ` or on -the `blog post introducing the EdgeDB projects CLI`_. - -- A ``/dbschema`` folder, inside which you'll see: - - - a ``default.esdl`` file which holds your schema. You can change the schema - by directly modifying this file followed by ``edgedb migration create`` - and ``edgedb migrate``. - - - a ``/migrations`` folder with ``.edgeql`` files named starting at - ``00001``. These hold the :ref:`ddl ` commands that were used - to migrate your schema. A new file shows up in this directory every time - your schema is migrated. - -If you are running EdgeDB 3.0 and above, you also have the option of using -the :ref:`edgedb watch ` command. Doing so starts a -long-running process that keeps an eye on changes in ``/dbschema``, -automatically applying these changes in real time. - -Now that you have the right dependencies and an EdgeDB instance, -you can create a client. - -.. _`blog post introducing the EdgeDB projects CLI`: - https://www.edgedb.com/blog/introducing-edgedb-projects -.. _`bridging methods`: https://tokio.rs/tokio/topics/bridging -.. _`edgedb-derive`: https://docs.rs/edgedb-derive/latest/edgedb_derive/ -.. _`edgedb-protocol`: https://docs.rs/edgedb-protocol/latest/edgedb_protocol -.. _`edgedb-tokio`: https://docs.rs/edgedb-tokio/latest/edgedb_tokio -.. _`examples repo`: https://github.com/Dhghomon/edgedb_rust_client_examples \ No newline at end of file diff --git a/docs/clients/rust/index.rst b/docs/clients/rust/index.rst deleted file mode 100644 index b88a4486628..00000000000 --- a/docs/clients/rust/index.rst +++ /dev/null @@ -1,41 +0,0 @@ -.. _ref_rust_index: - -==== -Rust -==== - -:edb-alt-title: EdgeDB Rust Client - -EdgeDB maintains a client library for Rust. View the `full documentation`_. - -The "hello world" of the Rust EdgeDB client is as follows: - -.. code-block:: rust - - #[tokio::main] - async fn main() { - let conn = edgedb_tokio::create_client() - .await - .expect("Client should have initiated"); - let val: i64 = conn - .query_required_single("select 7*8", &()) - .await - .expect("Query should have worked"); - println!("7*8 is: {val}"); - } - -.. _`full documentation`: https://docs.rs/edgedb-tokio/latest/edgedb_tokio/ - -.. toctree:: - :maxdepth: 2 - :hidden: - - getting_started - client - queryable - arguments - queryable_alternatives - execute - transactions - client_config - diff --git a/docs/clients/rust/queryable.rst b/docs/clients/rust/queryable.rst deleted file mode 100644 index f58c543a28c..00000000000 --- a/docs/clients/rust/queryable.rst +++ /dev/null @@ -1,150 +0,0 @@ -.. _ref_rust_queryable: - -Using the ``Queryable`` macro ------------------------------ - -The easiest way to unpack an EdgeDB query result is the built-in -``Queryable`` macro from the ``edgedb-derive`` crate. This turns queries -directly into Rust types without having to match on a ``Value`` (more in -the section on the ``Value`` enum), cast to JSON, etc. - -.. code-block:: rust - - #[derive(Debug, Deserialize, Queryable)] - pub struct QueryableAccount { - pub username: String, - pub id: Uuid, - } - - let query = "select account { - username, - id - };"; - let as_queryable_account: QueryableAccount = client - .query_required_single(query, &()) - .await?; - -.. note:: - - Field order within the shape of the query matters when using the - ``Queryable`` macro. In the example below, we run a query with the order - ``id, username`` instead of ``username, id`` as defined in the struct: - -.. code-block:: rust - - let query = "select account { - id, - username - };"; - let wrong_order: Result = client - .query_required_single(query, &()) - .await; - assert!( - format!("{wrong_order:?}") - .contains(r#"WrongField { unexpected: "id", expected: "username""#); - ); - -You can use `cargo expand`_ with the nightly compiler to see the code -generated by the ``Queryable`` macro, but the minimal example repo also -contains a somewhat cleaned up version of the generated ``Queryable`` code: - -.. code-block:: rust - - use edgedb_protocol::{ - descriptors::{Descriptor, TypePos}, - errors::DecodeError, - queryable::{Decoder, DescriptorContext, DescriptorMismatch, Queryable}, - serialization::decode::DecodeTupleLike, - }; - - // The code below shows the code generated from the Queryable macro in a - // more readable form (with macro-generated qualified paths replaced with - // use statements). - - #[derive(Debug)] - pub struct IsAStruct { - pub name: String, - pub number: i16, - pub is_ok: bool, - } - - impl Queryable for IsAStruct { - fn decode(decoder: &Decoder, buf: &[u8]) -> Result { - let nfields = 3usize - + if decoder.has_implicit_id { 1 } else { 0 } - + if decoder.has_implicit_tid { 1 } else { 0 } - + if decoder.has_implicit_tname { 1 } else { 0 }; - let mut elements = DecodeTupleLike::new_object(buf, nfields)?; - if decoder.has_implicit_tid { - elements.skip_element()?; - } - if decoder.has_implicit_tname { - elements.skip_element()?; - } - if decoder.has_implicit_id { - elements.skip_element()?; - } - let name = Queryable::decode_optional(decoder, elements.read()?)?; - let number = Queryable::decode_optional(decoder, elements.read()?)?; - let is_ok = Queryable::decode_optional(decoder, elements.read()?)?; - Ok(IsAStruct { - name, - number, - is_ok, - }) - } - - fn check_descriptor( - ctx: &DescriptorContext, - type_pos: TypePos, - ) -> Result<(), DescriptorMismatch> { - let desc = ctx.get(type_pos)?; - let shape = match desc { - Descriptor::ObjectShape(shape) => shape, - _ => return Err(ctx.wrong_type(desc, "str")), - }; - let mut idx = 0; - if ctx.has_implicit_tid { - if !shape.elements[idx].flag_implicit { - return Err(ctx.expected("implicit __tid__")); - } - idx += 1; - } - if ctx.has_implicit_tname { - if !shape.elements[idx].flag_implicit { - return Err(ctx.expected("implicit __tname__")); - } - idx += 1; - } - if ctx.has_implicit_id { - if !shape.elements[idx].flag_implicit { - return Err(ctx.expected("implicit id")); - } - idx += 1; - } - let el = &shape.elements[idx]; - if el.name != "name" { - return Err(ctx.wrong_field("name", &el.name)); - } - idx += 1; - ::check_descriptor(ctx, el.type_pos)?; - let el = &shape.elements[idx]; - if el.name != "number" { - return Err(ctx.wrong_field("number", &el.name)); - } - idx += 1; - ::check_descriptor(ctx, el.type_pos)?; - let el = &shape.elements[idx]; - if el.name != "is_ok" { - return Err(ctx.wrong_field("is_ok", &el.name)); - } - idx += 1; - ::check_descriptor(ctx, el.type_pos)?; - if shape.elements.len() != idx { - return Err(ctx.field_number(shape.elements.len(), idx)); - } - Ok(()) - } - } - -.. _`cargo expand`: https://github.com/dtolnay/cargo-expand \ No newline at end of file diff --git a/docs/clients/rust/queryable_alternatives.rst b/docs/clients/rust/queryable_alternatives.rst deleted file mode 100644 index f2a66f1da3b..00000000000 --- a/docs/clients/rust/queryable_alternatives.rst +++ /dev/null @@ -1,101 +0,0 @@ -.. _ref_rust_queryable_alternatives: - -Alternatives to the Queryable macro ------------------------------------ - -The ``Queryable`` macro is the recommended way to make EdgeDB queries in -Rust, but some alternatives exist. - -The ``Value`` enum ------------------- - -The ``Value`` enum can be found in the `edgedb-protocol`_ crate. A ``Value`` -represents anything returned from EdgeDB. This means you can always return -a ``Value`` from any of the query methods without needing to deserialize -into a Rust type, and the enum can be instructive in getting to know -the protocol. On the other hand, returning a ``Value`` leads to -pattern matching to get to the inner value and is not the most ergonomic way -to work with results from EdgeDB. - -.. code-block:: rust - - pub enum Value { - Nothing, - Uuid(Uuid), - Str(String), - Bytes(Vec), - Int16(i16), - Int32(i32), - Int64(i64), - Float32(f32), - Float64(f64), - BigInt(BigInt), - // ... and so on - } - -Most variants of the ``Value`` enum correspond to a Rust type from the Rust -standard library, while some are from the ``edgedb-protocol`` crate and must -be constructed. For example, this query expecting an EdgeDB ``bigint`` will -return an error as it receives a ``20``, which is an ``i32``: - -.. code-block:: rust - - let query = "select $0"; - let arg = 20; - let query_res: Result = - client.query_required_single(query, &(arg,)).await; - assert!(format!("{query_res:?}").contains("expected std::int32")); - -Instead, first construct a ``BigInt`` from the ``i32`` and pass that in -as an argument: - -.. code-block:: rust - - use edgedb_protocol::model::BigInt; - - let query = "select $0"; - let arg = BigInt::from(20); - let query_res: Result = - client.query_required_single(query, &(arg,)).await; - assert_eq!( - format!("{query_res:?}"), - "Ok(BigInt(BigInt { negative: false, weight: 0, digits: [20] }))" - ); - -Using JSON ----------- - -EdgeDB can cast any type to JSON with ````, but the ``*_json`` methods -don't require this cast in the query. This result can be turned into a -``String`` and used to respond to some JSON API request directly, unpacked -into a struct using ``serde`` and ``serde_json``, etc. - -.. code-block:: rust - - #[derive(Debug, Deserialize)] - pub struct Account { - pub username: String, - pub id: Uuid, - } - - // No need for cast here - let query = "select Account { - username, - id - } filter .username = $0;"; - - // Can use query_single_json if we know there will only be one result; - // otherwise query_json which returns a map of json - let json_res = client - .query_single_json(query, &("SomeUserName",)) - .await? - .unwrap(); - - // Format: - // {"username" : "SomeUser1", - // "id" : "7093944a-fd3a-11ed-a013-c7de12ffe7a9"} - let as_string = json_res.to_string(); - let as_account: Account = serde_json::from_str(&json_res)?; - - -.. _`edgedb-protocol`: https://docs.rs/edgedb-protocol \ No newline at end of file diff --git a/docs/clients/rust/transactions.rst b/docs/clients/rust/transactions.rst deleted file mode 100644 index 6924b761f60..00000000000 --- a/docs/clients/rust/transactions.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. _ref_rust_transactions: - -Transactions ------------- - -The client also has a ``.transaction()`` method that -allows for atomic :ref:`transactions `. - -Wikipedia has a good example of a scenario requiring a transaction which we -can then implement: - -*An example of an atomic transaction is a monetary transfer from bank account A -to account B. It consists of two operations, withdrawing the money from account -A and saving it to account B. Performing these operations in an atomic -transaction ensures that the database remains in a consistent state, that is, -money is neither lost nor created if either of those two operations fails.* - -A transaction removing 10 cents from one customer's account and placing it in -another's would look like this: - -.. code-block:: rust - - #[derive(Debug, Deserialize, Queryable)] - pub struct BankCustomer { - pub name: String, - pub bank_balance: i32, - } - // Customer1 has an account with 110 cents in it. - // Customer2 has an account with 90 cents in it. - // Customer1 is going to send 10 cents to Customer 2. This will be a - // transaction as we don't want the case to ever occur - even for a - // split second - where one account has sent money while the other - // has not received it yet. - - // After the transaction is over, each customer should have 100 cents. - - let sender_name = "Customer1"; - let receiver_name = "Customer2"; - let balance_check = "select BankCustomer { name, bank_balance } - filter .name = $0"; - let balance_change = "update BankCustomer - filter .name = $0 - set { bank_balance := .bank_balance + $1 }"; - let send_amount = 10; - - client - .transaction(|mut conn| async move { - let sender: BankCustomer = conn - .query_required_single(balance_check, &(sender_name,)) - .await?; - if sender.bank_balance < send_amount { - println!("Not enough money, bailing from transaction"); - return Ok(()); - }; - conn.execute(balance_change, &(sender_name, send_amount.neg())) - .await?; - conn.execute(balance_change, &(receiver_name, send_amount)) - .await?; - Ok(()) - }) - .await?; - -.. note:: - - What often may seem to require an atomic transaction can instead be - achieved with links and :ref:`backlinks ` which - are both idiomatic and easy to use in EdgeDB. - For example, if one object holds a ``required link`` to two - other objects and each of these two objects has a single backlink to the - first one, simply updating the first object will effectively change the - state of the other two instantaneously. diff --git a/docs/edgeql/transactions.rst b/docs/edgeql/transactions.rst index 2dd52e814dd..25c4bd197c0 100644 --- a/docs/edgeql/transactions.rst +++ b/docs/edgeql/transactions.rst @@ -165,4 +165,5 @@ Rust .await .expect("Transaction should have worked"); -Full documentation at :ref:`Client Libraries > Rust `. +.. XXX: Add Rust docs +.. Full documentation at :ref:`Client Libraries > Rust `. diff --git a/docs/guides/tutorials/phoenix_github_oauth.rst b/docs/guides/tutorials/phoenix_github_oauth.rst index 5c09f020771..ae17abeb3a7 100644 --- a/docs/guides/tutorials/phoenix_github_oauth.rst +++ b/docs/guides/tutorials/phoenix_github_oauth.rst @@ -9,8 +9,8 @@ Phoenix In this tutorial, we'll look at how you can create an application with authorization through GitHub using -`Phoenix `_ and :ref:`the official EdgeDB Elixir -driver `. +`Phoenix `_ and `the official EdgeDB Elixir +driver `_. This tutorial is a simplified version of the `LiveBeats `_ application from diff --git a/docs/intro/clients.rst b/docs/intro/clients.rst index 3f56d57b254..95f6f19f8af 100644 --- a/docs/intro/clients.rst +++ b/docs/intro/clients.rst @@ -37,11 +37,13 @@ libraries* for the following languages. - :ref:`JavaScript/TypeScript ` - :ref:`Go ` - :ref:`Python ` -- :ref:`Rust ` -- :ref:`C# and F# ` -- :ref:`Java ` -- :ref:`Dart ` -- :ref:`Elixir ` + +.. XXX: link to third-party doc websites +.. - :ref:`Rust ` +.. - :ref:`C# and F# ` +.. - :ref:`Java ` +.. - :ref:`Dart ` +.. - :ref:`Elixir ` Usage ===== diff --git a/docs/intro/quickstart.rst b/docs/intro/quickstart.rst index d6121295bfd..b2c9a7e1789 100644 --- a/docs/intro/quickstart.rst +++ b/docs/intro/quickstart.rst @@ -191,7 +191,7 @@ This did a couple things. Let's connect to our new instance! Run ``edgedb`` in your terminal to open an interactive REPL to your instance. You're now connected to a live EdgeDB -instance running on your computer! Try executing a simple query (``select 1 + 1;``) after the +instance running on your computer! Try executing a simple query (``select 1 + 1;``) after the REPL prompt (``quickstart:main>``): .. code-block:: edgeql-repl @@ -511,11 +511,13 @@ provides official libraries for many langauges: - :ref:`JavaScript/TypeScript ` - :ref:`Go ` - :ref:`Python ` -- :ref:`Rust ` -- :ref:`C# and F# ` -- :ref:`Java ` -- :ref:`Dart ` -- :ref:`Elixir ` + +.. XXX: link to third-party doc websites +.. - :ref:`Rust ` +.. - :ref:`C# and F# ` +.. - :ref:`Java ` +.. - :ref:`Dart ` +.. - :ref:`Elixir ` Check out the :ref:`Clients ` guide to get started with the language of your choice. @@ -554,8 +556,10 @@ and used a client library. - :ref:`JavaScript/TypeScript ` - :ref:`Go ` - :ref:`Python ` - - :ref:`Rust ` - - :ref:`C# and F# ` - - :ref:`Java ` - - :ref:`Dart ` - - :ref:`Elixir ` + +.. XXX: link to third-party doc websites +.. - :ref:`Rust ` +.. - :ref:`C# and F# ` +.. - :ref:`Java ` +.. - :ref:`Dart ` +.. - :ref:`Elixir ` From df9985bc46fe7f0e6ef0c115afd4a7e5f572347e Mon Sep 17 00:00:00 2001 From: James Clarke Date: Sat, 8 Feb 2025 12:11:47 +0000 Subject: [PATCH 061/154] Update docs site deployment branch --- .github/scripts/docs/preview-deploy.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/scripts/docs/preview-deploy.js b/.github/scripts/docs/preview-deploy.js index 4906c25f97b..b23c02128fa 100644 --- a/.github/scripts/docs/preview-deploy.js +++ b/.github/scripts/docs/preview-deploy.js @@ -1,3 +1,9 @@ +const DOCS_SITE_REPO = { + org: "edgedb", + repo: "edgedb.com", + ref: "new-new-docs", +}; + module.exports = async ({ github, context }) => { const { VERCEL_TOKEN, VERCEL_TEAM_ID } = process.env; @@ -35,9 +41,7 @@ module.exports = async ({ github, context }) => { name: "edgedb-docs", gitSource: { type: "github", - org: "edgedb", - repo: "edgedb.com", - ref: "docs-preview", + ...DOCS_SITE_REPO, }, projectSettings: { buildCommand: `EDGEDB_REPO_BRANCH=${prBranch} EDGEDB_REPO_SHA=${commitSHA} yarn vercel-build`, From 381feec12cfbb08a515e5728e550eab5536a0ed7 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Sat, 8 Feb 2025 12:14:18 +0000 Subject: [PATCH 062/154] Return errors from script if deployment fails --- .github/scripts/docs/preview-deploy.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/scripts/docs/preview-deploy.js b/.github/scripts/docs/preview-deploy.js index b23c02128fa..27e8be7d29f 100644 --- a/.github/scripts/docs/preview-deploy.js +++ b/.github/scripts/docs/preview-deploy.js @@ -35,6 +35,7 @@ module.exports = async ({ github, context }) => { c.body?.startsWith(commentHeader) ); + let deploymentError = null; let deployment; try { deployment = await vercelFetch("https://api.vercel.com/v13/deployments", { @@ -50,6 +51,7 @@ module.exports = async ({ github, context }) => { commentMessage += `\n🔄 Deploying docs preview for commit ${shortCommitSHA}:\n\n`; } catch (e) { + deploymentError = e; commentMessage += `\n❌ Failed to deploy docs preview for commit ${shortCommitSHA}:\n\n\`\`\`\n${e.message}\n\`\`\``; } @@ -73,6 +75,10 @@ module.exports = async ({ github, context }) => { ).data; } + if (deploymentError) { + throw new Error(`Docs preview deployment failed: ${e.message}`); + } + let i = 0; while (i < 40) { await sleep(15_000); @@ -110,10 +116,25 @@ module.exports = async ({ github, context }) => { new Date() )})`, }); + if (status !== "READY") { + throw new Error( + `Docs preview deployment failed with status ${status}: https://${deployment.url}` + ); + } return; } } - throw new Error("timed out waiting for deployment status to succeed or fail"); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: updateComment.id, + body: `${commentHeader} +❌ Timed out waiting for deployment status to succeed or fail for commit ${shortCommitSHA}:\n\n\n\n(Last updated: ${formatDatetime(new Date())})`, + }); + throw new Error("Timed out waiting for deployment status to succeed or fail"); }; async function vercelFetch(url, body) { From 7ec911380fd05861eff3b2555d381064b67e36af Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 15:34:31 -0800 Subject: [PATCH 063/154] docs: Move guides/cloud -> cloud --- docs/{guides => }/cloud/cli.rst | 0 docs/{guides => }/cloud/deploy/fly.rst | 0 .../cloud/deploy/images/cloud-netlify-config.png | Bin .../cloud/deploy/images/cloud-railway-config.png | Bin .../cloud/deploy/images/cloud-render-config.png | Bin .../cloud/deploy/images/cloud-vercel-config.png | Bin docs/{guides => }/cloud/deploy/index.rst | 0 docs/{guides => }/cloud/deploy/netlify.rst | 0 docs/{guides => }/cloud/deploy/railway.rst | 0 docs/{guides => }/cloud/deploy/render.rst | 0 docs/{guides => }/cloud/deploy/vercel.rst | 0 docs/{guides => }/cloud/http_gql.rst | 0 docs/{guides => }/cloud/index.rst | 0 docs/{guides => }/cloud/web.rst | 0 docs/guides/index.rst | 1 - docs/index.rst | 1 + 16 files changed, 1 insertion(+), 1 deletion(-) rename docs/{guides => }/cloud/cli.rst (100%) rename docs/{guides => }/cloud/deploy/fly.rst (100%) rename docs/{guides => }/cloud/deploy/images/cloud-netlify-config.png (100%) rename docs/{guides => }/cloud/deploy/images/cloud-railway-config.png (100%) rename docs/{guides => }/cloud/deploy/images/cloud-render-config.png (100%) rename docs/{guides => }/cloud/deploy/images/cloud-vercel-config.png (100%) rename docs/{guides => }/cloud/deploy/index.rst (100%) rename docs/{guides => }/cloud/deploy/netlify.rst (100%) rename docs/{guides => }/cloud/deploy/railway.rst (100%) rename docs/{guides => }/cloud/deploy/render.rst (100%) rename docs/{guides => }/cloud/deploy/vercel.rst (100%) rename docs/{guides => }/cloud/http_gql.rst (100%) rename docs/{guides => }/cloud/index.rst (100%) rename docs/{guides => }/cloud/web.rst (100%) diff --git a/docs/guides/cloud/cli.rst b/docs/cloud/cli.rst similarity index 100% rename from docs/guides/cloud/cli.rst rename to docs/cloud/cli.rst diff --git a/docs/guides/cloud/deploy/fly.rst b/docs/cloud/deploy/fly.rst similarity index 100% rename from docs/guides/cloud/deploy/fly.rst rename to docs/cloud/deploy/fly.rst diff --git a/docs/guides/cloud/deploy/images/cloud-netlify-config.png b/docs/cloud/deploy/images/cloud-netlify-config.png similarity index 100% rename from docs/guides/cloud/deploy/images/cloud-netlify-config.png rename to docs/cloud/deploy/images/cloud-netlify-config.png diff --git a/docs/guides/cloud/deploy/images/cloud-railway-config.png b/docs/cloud/deploy/images/cloud-railway-config.png similarity index 100% rename from docs/guides/cloud/deploy/images/cloud-railway-config.png rename to docs/cloud/deploy/images/cloud-railway-config.png diff --git a/docs/guides/cloud/deploy/images/cloud-render-config.png b/docs/cloud/deploy/images/cloud-render-config.png similarity index 100% rename from docs/guides/cloud/deploy/images/cloud-render-config.png rename to docs/cloud/deploy/images/cloud-render-config.png diff --git a/docs/guides/cloud/deploy/images/cloud-vercel-config.png b/docs/cloud/deploy/images/cloud-vercel-config.png similarity index 100% rename from docs/guides/cloud/deploy/images/cloud-vercel-config.png rename to docs/cloud/deploy/images/cloud-vercel-config.png diff --git a/docs/guides/cloud/deploy/index.rst b/docs/cloud/deploy/index.rst similarity index 100% rename from docs/guides/cloud/deploy/index.rst rename to docs/cloud/deploy/index.rst diff --git a/docs/guides/cloud/deploy/netlify.rst b/docs/cloud/deploy/netlify.rst similarity index 100% rename from docs/guides/cloud/deploy/netlify.rst rename to docs/cloud/deploy/netlify.rst diff --git a/docs/guides/cloud/deploy/railway.rst b/docs/cloud/deploy/railway.rst similarity index 100% rename from docs/guides/cloud/deploy/railway.rst rename to docs/cloud/deploy/railway.rst diff --git a/docs/guides/cloud/deploy/render.rst b/docs/cloud/deploy/render.rst similarity index 100% rename from docs/guides/cloud/deploy/render.rst rename to docs/cloud/deploy/render.rst diff --git a/docs/guides/cloud/deploy/vercel.rst b/docs/cloud/deploy/vercel.rst similarity index 100% rename from docs/guides/cloud/deploy/vercel.rst rename to docs/cloud/deploy/vercel.rst diff --git a/docs/guides/cloud/http_gql.rst b/docs/cloud/http_gql.rst similarity index 100% rename from docs/guides/cloud/http_gql.rst rename to docs/cloud/http_gql.rst diff --git a/docs/guides/cloud/index.rst b/docs/cloud/index.rst similarity index 100% rename from docs/guides/cloud/index.rst rename to docs/cloud/index.rst diff --git a/docs/guides/cloud/web.rst b/docs/cloud/web.rst similarity index 100% rename from docs/guides/cloud/web.rst rename to docs/cloud/web.rst diff --git a/docs/guides/index.rst b/docs/guides/index.rst index 4ff9475a11d..f1f8f642cc1 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -17,7 +17,6 @@ guide! .. toctree:: :maxdepth: 1 - cloud/index deployment/index datamigrations/index tutorials/index diff --git a/docs/index.rst b/docs/index.rst index b9a8f914ab1..4da5f27d27e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,3 +24,4 @@ Welcome to the EdgeDB |version| documentation. reference/index cheatsheets/index changelog/index + cloud/index From 57da0af73cae5ad3df58fdb05fd45cd8229dafd9 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 17:17:05 -0800 Subject: [PATCH 064/154] Implement basic Sphinx subsitution |Gel| -> node to custom render --- docs/datamodel/index.rst | 25 ++++++++----------------- edb/tools/docs/edb.py | 25 ++++++++++++++++++++++++- tests/test_docs_sphinx_ext.py | 22 ++++++++++++++++++++++ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/docs/datamodel/index.rst b/docs/datamodel/index.rst index ea582e88919..e7de44af61a 100644 --- a/docs/datamodel/index.rst +++ b/docs/datamodel/index.rst @@ -45,20 +45,6 @@ across multiple files if you wish. By convention, your schema files should live in a directory called ``dbschema`` in the root of your project. -.. code-block:: sdl - :version-lt: 3.0 - - # dbschema/default.esdl - - type Movie { - required property title -> str; - required link director -> Person; - } - - type Person { - required property name -> str; - } - .. code-block:: sdl # dbschema/default.esdl @@ -120,17 +106,22 @@ An EdgeDB **instance** is a running EdgeDB process. Instances can be created, started, stopped, and destroyed locally with the :ref:`EdgeDB CLI `. +.. _ref_datamodel_databases: .. _ref_datamodel_branches: -Branches -^^^^^^^^ +Branch +^^^^^^ .. versionadded:: 5.0 +.. versionchanged:: 5.0 + + Prior to |Gel| 5, *branches* were called "databases" + (and "databases" is what Gel branches map to in PostgreSQL). + Instances can be branched when working on new features, similar to branches in your VCS. Each branch has its own schema and data. -.. _ref_datamodel_databases: Database ^^^^^^^^ diff --git a/edb/tools/docs/edb.py b/edb/tools/docs/edb.py index f78a0a958a5..fe52fc8f2b6 100644 --- a/edb/tools/docs/edb.py +++ b/edb/tools/docs/edb.py @@ -24,7 +24,7 @@ from docutils.parsers import rst as d_rst from docutils.parsers.rst import directives as d_directives # type: ignore -from sphinx_code_tabs import TabsNode +from sphinx import transforms class EDBYoutubeEmbed(d_rst.Directive): @@ -135,8 +135,31 @@ class GelDomain(s_domains.Domain): } +class GelSubstitutionTransform(transforms.SphinxTransform): + default_priority = 0 + + def apply(self): + builder_name = "html" + if hasattr(self.document.settings, 'env'): + env = self.document.settings.env + if env and hasattr(env, "app"): + builder_name = env.app.builder.name + + # Traverse all substitution_reference nodes. + for node in self.document.traverse(d_nodes.substitution_reference): + if node.astext() == "Gel": + if builder_name in {"xml", "edge-xml"}: + sub = d_nodes.inline( + "Gel", "Gel", **{"edb-substitution": "true"} + ) + node.replace_self(sub) + else: + node.replace_self(d_nodes.Text("Gel")) + + def setup_domain(app): app.add_domain(GelDomain) + app.add_transform(GelSubstitutionTransform) def setup(app): diff --git a/tests/test_docs_sphinx_ext.py b/tests/test_docs_sphinx_ext.py index 938d7553576..24099f827c3 100644 --- a/tests/test_docs_sphinx_ext.py +++ b/tests/test_docs_sphinx_ext.py @@ -945,3 +945,25 @@ def test_sphinx_edb_collapsed_01(self): //container[@collapsed_block="True"]/paragraph/text() '''), ['spam', 'ham']) + + +@unittest.skipIf(requests_xml is None, 'requests-xml package is not installed') +class TestOthers(unittest.TestCase, BaseDomainTest): + + def test_sphinx_edb_brand_name_01(self): + src = ''' + blah |Gel| + ''' + + out = self.build(src, format='xml') + print(out, '\n') + x = requests_xml.XML(xml=out) + + self.assertEqual( + x.xpath(''' + //paragraph/inline[@edb-substitution="true"]/text() + '''), + ['Gel']) + + + print(x) From 90b0bb33ec405f20d39226b7b12ce5b7517ab83f Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 18:39:11 -0800 Subject: [PATCH 065/154] Replace EdgeDB->Gel in all :edb-alt-title: --- docs/ai/index.rst | 2 +- docs/ai/javascript.rst | 8 +-- docs/ai/python.rst | 2 +- docs/changelog/1_x.rst | 2 +- docs/changelog/2_x.rst | 2 +- docs/changelog/3_x.rst | 2 +- docs/changelog/4_x.rst | 2 +- docs/changelog/5_x.rst | 2 +- docs/changelog/6_x.rst | 2 +- docs/cheatsheets/index.rst | 2 +- docs/cli/index.rst | 2 +- docs/cloud/cli.rst | 2 +- docs/cloud/deploy/fly.rst | 20 +++---- docs/cloud/deploy/index.rst | 2 +- docs/cloud/deploy/netlify.rst | 2 +- docs/cloud/deploy/railway.rst | 8 +-- docs/cloud/deploy/render.rst | 10 ++-- docs/cloud/deploy/vercel.rst | 2 +- docs/cloud/http_gql.rst | 2 +- docs/cloud/index.rst | 2 +- docs/cloud/web.rst | 2 +- docs/guides/auth/built_in_ui.rst | 2 +- docs/guides/auth/email_password.rst | 2 +- docs/guides/auth/index.rst | 2 +- docs/guides/auth/magic_link.rst | 2 +- docs/guides/auth/oauth.rst | 2 +- docs/guides/auth/webauthn.rst | 2 +- docs/guides/contributing/code.rst | 2 +- docs/guides/contributing/documentation.rst | 2 +- docs/guides/contributing/index.rst | 2 +- docs/guides/deployment/aws_aurora_ecs.rst | 2 +- .../deployment/azure_flexibleserver.rst | 2 +- docs/guides/deployment/bare_metal.rst | 2 +- docs/guides/deployment/digitalocean.rst | 2 +- docs/guides/deployment/docker.rst | 2 +- docs/guides/deployment/fly_io.rst | 2 +- docs/guides/deployment/gcp.rst | 2 +- docs/guides/deployment/heroku.rst | 2 +- docs/guides/tutorials/chatgpt_bot.rst | 2 +- docs/guides/tutorials/cloudflare_workers.rst | 56 ++++++++--------- .../graphql_apis_with_strawberry.rst | 2 +- docs/guides/tutorials/jupyter_notebook.rst | 2 +- docs/guides/tutorials/nextjs_pages_router.rst | 10 ++-- .../tutorials/rest_apis_with_fastapi.rst | 58 +++++++++--------- .../guides/tutorials/rest_apis_with_flask.rst | 2 +- docs/guides/tutorials/trpc.rst | 60 +++++++++---------- docs/index.rst | 4 +- 47 files changed, 155 insertions(+), 155 deletions(-) diff --git a/docs/ai/index.rst b/docs/ai/index.rst index 64d186fba28..d8b08b6a2d0 100644 --- a/docs/ai/index.rst +++ b/docs/ai/index.rst @@ -12,7 +12,7 @@ AI python reference -:edb-alt-title: Using EdgeDB AI +:edb-alt-title: Using Gel AI EdgeDB AI allows you to ship AI-enabled apps with practically no effort. It automatically generates embeddings for your data. Works with OpenAI, Mistral diff --git a/docs/ai/javascript.rst b/docs/ai/javascript.rst index 12a4c5ab0e3..d29540136da 100644 --- a/docs/ai/javascript.rst +++ b/docs/ai/javascript.rst @@ -4,7 +4,7 @@ JavaScript ========== -:edb-alt-title: EdgeDB AI's JavaScript package +:edb-alt-title: Gel AI's JavaScript package ``@edgedb/ai`` offers a convenient wrapper around ``ext::ai``. Install it with npm or via your package manager of choice: @@ -179,11 +179,11 @@ Public methods Can be used in two ways: - - as **an async iterator** - if you want to process streaming data in + - as **an async iterator** - if you want to process streaming data in real-time as it arrives, ideal for handling long-running streams. - - as **a Promise that resolves to a full Response object** - you have - complete control over how you want to handle the stream, this might be + - as **a Promise that resolves to a full Response object** - you have + complete control over how you want to handle the stream, this might be useful when you want to manipulate the raw stream or parse it in a custom way. :param string message: diff --git a/docs/ai/python.rst b/docs/ai/python.rst index 87ffa2f6048..2bd6033cc5b 100644 --- a/docs/ai/python.rst +++ b/docs/ai/python.rst @@ -4,7 +4,7 @@ Python ====== -:edb-alt-title: EdgeDB AI's Python package +:edb-alt-title: Gel AI's Python package The ``edgedb.ai`` package is an optional binding of the AI extension in EdgeDB. To use the AI binding, you need to install ``edgedb-python`` with the ``ai`` diff --git a/docs/changelog/1_x.rst b/docs/changelog/1_x.rst index f0d3328ea72..41de57062a7 100644 --- a/docs/changelog/1_x.rst +++ b/docs/changelog/1_x.rst @@ -2,7 +2,7 @@ v1.0 ==== -:edb-alt-title: EdgeDB v1 (Nova) +:edb-alt-title: Gel v1 (Nova) .. image:: images/v1_nova.jpg :width: 100% diff --git a/docs/changelog/2_x.rst b/docs/changelog/2_x.rst index 9cd0230e33f..7e0fc4adb94 100644 --- a/docs/changelog/2_x.rst +++ b/docs/changelog/2_x.rst @@ -2,7 +2,7 @@ v2.0 ==== -:edb-alt-title: EdgeDB v2 (Sagittarius) +:edb-alt-title: Gel v2 (Sagittarius) .. image:: images/v2_sagittarius.jpg :width: 100% diff --git a/docs/changelog/3_x.rst b/docs/changelog/3_x.rst index 361b50e896f..94d2036893b 100644 --- a/docs/changelog/3_x.rst +++ b/docs/changelog/3_x.rst @@ -2,7 +2,7 @@ v3.0 ==== -:edb-alt-title: EdgeDB v3 +:edb-alt-title: Gel v3 .. image:: images/v3_betelgeuse.jpg :width: 100% diff --git a/docs/changelog/4_x.rst b/docs/changelog/4_x.rst index aaea6b0777e..e50b4552045 100644 --- a/docs/changelog/4_x.rst +++ b/docs/changelog/4_x.rst @@ -2,7 +2,7 @@ v4.0 ==== -:edb-alt-title: EdgeDB v4 +:edb-alt-title: Gel v4 This release cycle is much shorter than the previous ones. It reflects our new approach at EdgeDB where the goal is to provide improvements at a steady diff --git a/docs/changelog/5_x.rst b/docs/changelog/5_x.rst index abfbbf5ce6d..2d0ebd0de08 100644 --- a/docs/changelog/5_x.rst +++ b/docs/changelog/5_x.rst @@ -2,7 +2,7 @@ v5.0 ==== -:edb-alt-title: EdgeDB v5 +:edb-alt-title: Gel v5 To play with the new features, make sure to specify version 5.0 when initializing the project as pre-release versions are not considered stable diff --git a/docs/changelog/6_x.rst b/docs/changelog/6_x.rst index 79bab33309a..27a0e9caeb5 100644 --- a/docs/changelog/6_x.rst +++ b/docs/changelog/6_x.rst @@ -2,7 +2,7 @@ v6.0 ==== -:edb-alt-title: EdgeDB v6 +:edb-alt-title: Gel v6 To explore the new features, ensure you specify version 6.0 when initializing your project. Pre-release versions are not considered stable and will not be diff --git a/docs/cheatsheets/index.rst b/docs/cheatsheets/index.rst index 8ea09b617e0..c1e80e60169 100644 --- a/docs/cheatsheets/index.rst +++ b/docs/cheatsheets/index.rst @@ -4,7 +4,7 @@ Cheatsheets =========== -:edb-alt-title: Cheatsheets: EdgeDB by example +:edb-alt-title: Cheatsheets: Gel by example .. toctree:: :maxdepth: 3 diff --git a/docs/cli/index.rst b/docs/cli/index.rst index e442bbdda9b..288af98be98 100644 --- a/docs/cli/index.rst +++ b/docs/cli/index.rst @@ -6,7 +6,7 @@ CLI === -:edb-alt-title: The EdgeDB CLI +:edb-alt-title: The Gel CLI The ``edgedb`` command-line interface (CLI) provides an idiomatic way to install EdgeDB, spin up local instances, open a REPL, execute queries, diff --git a/docs/cloud/cli.rst b/docs/cloud/cli.rst index 7bac7f359b2..62ff9f31b6a 100644 --- a/docs/cloud/cli.rst +++ b/docs/cloud/cli.rst @@ -4,7 +4,7 @@ CLI === -:edb-alt-title: Using EdgeDB Cloud via the CLI +:edb-alt-title: Using Gel Cloud via the CLI To use EdgeDB Cloud via the CLI, first log in using :ref:`ref_cli_edgedb_cloud_login`. diff --git a/docs/cloud/deploy/fly.rst b/docs/cloud/deploy/fly.rst index 99efd0bd827..83c26e119ea 100644 --- a/docs/cloud/deploy/fly.rst +++ b/docs/cloud/deploy/fly.rst @@ -4,16 +4,16 @@ Fly.io ====== -:edb-alt-title: Deploying applications built on EdgeDB Cloud to Fly.io +:edb-alt-title: Deploying applications built on Gel Cloud to Fly.io 1. Install the `Fly.io CLI `_ 2. Log in to Fly.io with ``flyctl auth login`` 3. Run ``flyctl launch`` to create a new app on Fly.io and configure it. - It will ask you to select a region and a name for your app. When done it will + It will ask you to select a region and a name for your app. When done it will create a ``fly.toml`` file and a ``Dockerfile`` in your project directory. -4. Set ``EDGEDB_INSTANCE`` and ``EDGEDB_SECRET_KEY`` as secrets in your Fly.io - app. - +4. Set ``EDGEDB_INSTANCE`` and ``EDGEDB_SECRET_KEY`` as secrets in your Fly.io + app. + For **runtime secrets**, you can do this by running the following commands: .. code-block:: bash @@ -21,15 +21,15 @@ Fly.io $ flyctl secrets set EDGEDB_INSTANCE $ flyctl secrets set EDGEDB_SECRET_KEY - `Read more about Fly.io runtime secrets + `Read more about Fly.io runtime secrets `_. - For **build secrets**, you can do this by modifying the ``Dockerfile`` to + For **build secrets**, you can do this by modifying the ``Dockerfile`` to mount the secrets as environment variables. .. code-block:: dockerfile-diff :caption: Dockerfile - + # Build application - RUN pnpm run build + RUN --mount=type=secret,id=EDGEDB_INSTANCE \ @@ -38,7 +38,7 @@ Fly.io + EDGEDB_SECRET_KEY="$(cat /run/secrets/EDGEDB_SECRET_KEY)" \ + pnpm run build - `Read more about Fly.io build secrets + `Read more about Fly.io build secrets `_. 5. Deploy your app to Fly.io @@ -47,7 +47,7 @@ Fly.io $ flyctl deploy - If your app requires build secrets, you can pass them as arguments + If your app requires build secrets, you can pass them as arguments to the ``deploy`` command: .. code-block:: bash diff --git a/docs/cloud/deploy/index.rst b/docs/cloud/deploy/index.rst index b3c0b29bc80..582c5329973 100644 --- a/docs/cloud/deploy/index.rst +++ b/docs/cloud/deploy/index.rst @@ -4,7 +4,7 @@ Deploy an app ============= -:edb-alt-title: Deploying applications built on EdgeDB Cloud +:edb-alt-title: Deploying applications built on Gel Cloud For your production deployment, generate a dedicated secret key for your instance with :ref:`ref_cli_edgedb_cloud_secretkey_create` or via the web UI's diff --git a/docs/cloud/deploy/netlify.rst b/docs/cloud/deploy/netlify.rst index 1c5a5951ea6..19e96d2be61 100644 --- a/docs/cloud/deploy/netlify.rst +++ b/docs/cloud/deploy/netlify.rst @@ -4,7 +4,7 @@ Netlify ======= -:edb-alt-title: Deploying applications built on EdgeDB Cloud to Netlify +:edb-alt-title: Deploying applications built on Gel Cloud to Netlify .. note:: diff --git a/docs/cloud/deploy/railway.rst b/docs/cloud/deploy/railway.rst index 13f17055f68..1ffef2fdd4f 100644 --- a/docs/cloud/deploy/railway.rst +++ b/docs/cloud/deploy/railway.rst @@ -4,7 +4,7 @@ Railway ======= -:edb-alt-title: Deploying applications built on EdgeDB Cloud to Railway +:edb-alt-title: Deploying applications built on Gel Cloud to Railway 1. Push project to GitHub or some other Git remote repository 2. Create and make note of a secret key for your EdgeDB Cloud instance @@ -21,7 +21,7 @@ Railway .. image:: images/cloud-railway-config.png :width: 100% - :alt: A screenshot of the Railway deployment configuration view - highlighting the environment variables section where a user will - need to set the necessary variables for EdgeDB Cloud instance + :alt: A screenshot of the Railway deployment configuration view + highlighting the environment variables section where a user will + need to set the necessary variables for EdgeDB Cloud instance connection. diff --git a/docs/cloud/deploy/render.rst b/docs/cloud/deploy/render.rst index 016c82a9e32..9fadd22923b 100644 --- a/docs/cloud/deploy/render.rst +++ b/docs/cloud/deploy/render.rst @@ -4,13 +4,13 @@ Render ====== -:edb-alt-title: Deploying applications built on EdgeDB Cloud to Render +:edb-alt-title: Deploying applications built on Gel Cloud to Render 1. Push project to GitHub or some other Git remote repository 2. Create and make note of a secret key for your EdgeDB Cloud instance 3. From Render's dashboard, click "New > Web Service" 4. Import your project's repository -5. In the setup page, scroll down to the "Environment Variables" section and +5. In the setup page, scroll down to the "Environment Variables" section and add the following environment variables: - ``EDGEDB_INSTANCE`` containing your EdgeDB Cloud instance name (in @@ -22,7 +22,7 @@ Render .. image:: images/cloud-render-config.png :width: 100% - :alt: A screenshot of the Render deployment configuration view - highlighting the environment variables section where a user - will need to set the necessary variables for EdgeDB Cloud instance + :alt: A screenshot of the Render deployment configuration view + highlighting the environment variables section where a user + will need to set the necessary variables for EdgeDB Cloud instance connection. diff --git a/docs/cloud/deploy/vercel.rst b/docs/cloud/deploy/vercel.rst index bb81a667d0f..29f076a5288 100644 --- a/docs/cloud/deploy/vercel.rst +++ b/docs/cloud/deploy/vercel.rst @@ -4,7 +4,7 @@ Vercel ====== -:edb-alt-title: Deploying applications built on EdgeDB Cloud to Vercel +:edb-alt-title: Deploying applications built on Gel Cloud to Vercel 1. Push project to GitHub or some other Git remote repository 2. Create and make note of a secret key for your EdgeDB Cloud instance diff --git a/docs/cloud/http_gql.rst b/docs/cloud/http_gql.rst index 389cd25c74f..dc87114de44 100644 --- a/docs/cloud/http_gql.rst +++ b/docs/cloud/http_gql.rst @@ -4,7 +4,7 @@ HTTP & GraphQL APIs =================== -:edb-alt-title: Querying EdgeDB Cloud over HTTP and GraphQL +:edb-alt-title: Querying Gel Cloud over HTTP and GraphQL Using EdgeDB Cloud via HTTP and GraphQL works the same as :ref:`using any other EdgeDB instance `. The two differences are in **how to diff --git a/docs/cloud/index.rst b/docs/cloud/index.rst index 3fad39fcc8c..073e9489f12 100644 --- a/docs/cloud/index.rst +++ b/docs/cloud/index.rst @@ -4,7 +4,7 @@ Cloud ===== -:edb-alt-title: Using EdgeDB Cloud +:edb-alt-title: Using Gel Cloud EdgeDB Cloud is the easiest way to host your EdgeDB instance. We offer two ways to interact with EdgeDB Cloud: via our CLI or through a graphical web diff --git a/docs/cloud/web.rst b/docs/cloud/web.rst index 59e3a95f8c1..7a98158e80d 100644 --- a/docs/cloud/web.rst +++ b/docs/cloud/web.rst @@ -4,7 +4,7 @@ Web GUI ======= -:edb-alt-title: Using EdgeDB Cloud via the web GUI +:edb-alt-title: Using Gel Cloud via the web GUI If you'd prefer, you can also manage your account via `the EdgeDB Cloud web-based GUI `_. diff --git a/docs/guides/auth/built_in_ui.rst b/docs/guides/auth/built_in_ui.rst index d1d8eebb88a..dd8798e2311 100644 --- a/docs/guides/auth/built_in_ui.rst +++ b/docs/guides/auth/built_in_ui.rst @@ -4,7 +4,7 @@ Built-in UI =========== -:edb-alt-title: Integrating EdgeDB Auth's built-in UI +:edb-alt-title: Integrating Gel Auth's built-in UI To use the built-in UI for EdgeDB Auth, enable the built-in Auth UI by clicking the "Enable UI" button under "Login UI" in the configuration section of the diff --git a/docs/guides/auth/email_password.rst b/docs/guides/auth/email_password.rst index d0c7852d0f8..105372caadb 100644 --- a/docs/guides/auth/email_password.rst +++ b/docs/guides/auth/email_password.rst @@ -4,7 +4,7 @@ Email and password ================== -:edb-alt-title: Integrating EdgeDB Auth's email and password provider +:edb-alt-title: Integrating Gel Auth's email and password provider Along with using the :ref:`built-in UI `, you can also create your own UI that calls to your own web application backend. diff --git a/docs/guides/auth/index.rst b/docs/guides/auth/index.rst index 7b160b44ff4..5a0654a4e65 100644 --- a/docs/guides/auth/index.rst +++ b/docs/guides/auth/index.rst @@ -14,7 +14,7 @@ Auth magic_link webauthn -:edb-alt-title: Using EdgeDB Auth +:edb-alt-title: Using Gel Auth EdgeDB Auth is a batteries-included authentication solution for your app built into the EdgeDB server. Here's how you can integrate it with your app. diff --git a/docs/guides/auth/magic_link.rst b/docs/guides/auth/magic_link.rst index c31334e98c9..1af2fca9a42 100644 --- a/docs/guides/auth/magic_link.rst +++ b/docs/guides/auth/magic_link.rst @@ -4,7 +4,7 @@ Magic Link Auth ================ -:edb-alt-title: Integrating EdgeDB Auth's Magic Link provider +:edb-alt-title: Integrating Gel Auth's Magic Link provider Magic Link is a passwordless authentication method that allows users to log in via a unique, time-sensitive link sent to their email. This guide will walk you through integrating Magic Link authentication with your application using EdgeDB Auth. diff --git a/docs/guides/auth/oauth.rst b/docs/guides/auth/oauth.rst index 0253c3ad777..5b201c8719e 100644 --- a/docs/guides/auth/oauth.rst +++ b/docs/guides/auth/oauth.rst @@ -4,7 +4,7 @@ OAuth ===== -:edb-alt-title: Integrating EdgeDB Auth's OAuth provider +:edb-alt-title: Integrating Gel Auth's OAuth provider Along with using the :ref:`built-in UI `, you can also create your own UI that calls to your own web application backend. diff --git a/docs/guides/auth/webauthn.rst b/docs/guides/auth/webauthn.rst index 5dfca7dc86c..f020d91db6f 100644 --- a/docs/guides/auth/webauthn.rst +++ b/docs/guides/auth/webauthn.rst @@ -4,7 +4,7 @@ WebAuthn ======== -:edb-alt-title: Integrating EdgeDB Auth's WebAuthn provider +:edb-alt-title: Integrating Gel Auth's WebAuthn provider WebAuthn, short for Web Authentication, is a web standard published by the World Wide Web Consortium (W3C) for secure and passwordless authentication on diff --git a/docs/guides/contributing/code.rst b/docs/guides/contributing/code.rst index b252b607e09..3ed656dfeae 100644 --- a/docs/guides/contributing/code.rst +++ b/docs/guides/contributing/code.rst @@ -4,7 +4,7 @@ Code ==== -:edb-alt-title: Developing EdgeDB +:edb-alt-title: Developing Gel This section describes how to build EdgeDB locally, how to use its internal tools, and how to contribute to it. diff --git a/docs/guides/contributing/documentation.rst b/docs/guides/contributing/documentation.rst index e10dc3f1294..8230cd582b5 100644 --- a/docs/guides/contributing/documentation.rst +++ b/docs/guides/contributing/documentation.rst @@ -4,7 +4,7 @@ Documentation ============= -:edb-alt-title: Writing EdgeDB Documentation +:edb-alt-title: Writing Gel Documentation We pride ourselves on having some of the best documentation around, but we want you to help us make it even better. Documentation is a great way to get started diff --git a/docs/guides/contributing/index.rst b/docs/guides/contributing/index.rst index 0d1f9bcc34b..a75faee6867 100644 --- a/docs/guides/contributing/index.rst +++ b/docs/guides/contributing/index.rst @@ -4,7 +4,7 @@ Contributing ============ -:edb-alt-title: Contributing to EdgeDB +:edb-alt-title: Contributing to Gel EdgeDB is an open-source project, and we welcome contributions from our community. You can contribute by writing code or by helping us improve our diff --git a/docs/guides/deployment/aws_aurora_ecs.rst b/docs/guides/deployment/aws_aurora_ecs.rst index a6d8561835c..d30cd29d441 100644 --- a/docs/guides/deployment/aws_aurora_ecs.rst +++ b/docs/guides/deployment/aws_aurora_ecs.rst @@ -4,7 +4,7 @@ AWS === -:edb-alt-title: Deploying EdgeDB to AWS +:edb-alt-title: Deploying Gel to AWS In this guide we show how to deploy EdgeDB on AWS using Amazon Aurora and Elastic Container Service. diff --git a/docs/guides/deployment/azure_flexibleserver.rst b/docs/guides/deployment/azure_flexibleserver.rst index ecd48d7647c..2ef651f1d40 100644 --- a/docs/guides/deployment/azure_flexibleserver.rst +++ b/docs/guides/deployment/azure_flexibleserver.rst @@ -4,7 +4,7 @@ Azure ===== -:edb-alt-title: Deploying EdgeDB to Azure +:edb-alt-title: Deploying Gel to Azure In this guide we show how to deploy EdgeDB using Azure's `Postgres Flexible Server diff --git a/docs/guides/deployment/bare_metal.rst b/docs/guides/deployment/bare_metal.rst index f4312afd299..2fa9af1da62 100644 --- a/docs/guides/deployment/bare_metal.rst +++ b/docs/guides/deployment/bare_metal.rst @@ -4,7 +4,7 @@ Bare Metal ========== -:edb-alt-title: Deploying EdgeDB to a Bare Metal Server +:edb-alt-title: Deploying Gel to a Bare Metal Server In this guide we show how to deploy EdgeDB to bare metal using your system's package manager and systemd. diff --git a/docs/guides/deployment/digitalocean.rst b/docs/guides/deployment/digitalocean.rst index d96c345e098..dcf34cfd414 100644 --- a/docs/guides/deployment/digitalocean.rst +++ b/docs/guides/deployment/digitalocean.rst @@ -4,7 +4,7 @@ DigitalOcean ============ -:edb-alt-title: Deploying EdgeDB to DigitalOcean +:edb-alt-title: Deploying Gel to DigitalOcean In this guide we show how to deploy EdgeDB to DigitalOcean either with a One-click Deploy option or a diff --git a/docs/guides/deployment/docker.rst b/docs/guides/deployment/docker.rst index 46dab8fe5a2..87f7cb2687b 100644 --- a/docs/guides/deployment/docker.rst +++ b/docs/guides/deployment/docker.rst @@ -4,7 +4,7 @@ Docker ====== -:edb-alt-title: Deploying EdgeDB with Docker +:edb-alt-title: Deploying Gel with Docker When to use the `edgedb/edgedb`_ Docker image ============================================= diff --git a/docs/guides/deployment/fly_io.rst b/docs/guides/deployment/fly_io.rst index 6d75eec713a..a94859ee5e0 100644 --- a/docs/guides/deployment/fly_io.rst +++ b/docs/guides/deployment/fly_io.rst @@ -4,7 +4,7 @@ Fly.io ====== -:edb-alt-title: Deploying EdgeDB to Fly.io +:edb-alt-title: Deploying Gel to Fly.io In this guide we show how to deploy EdgeDB using a `Fly.io `_ PostgreSQL cluster as the backend. The deployment consists of two apps: one diff --git a/docs/guides/deployment/gcp.rst b/docs/guides/deployment/gcp.rst index b9a5325136f..7f6e8a9829b 100644 --- a/docs/guides/deployment/gcp.rst +++ b/docs/guides/deployment/gcp.rst @@ -4,7 +4,7 @@ Google Cloud ============ -:edb-alt-title: Deploying EdgeDB to Google Cloud +:edb-alt-title: Deploying Gel to Google Cloud In this guide we show how to deploy EdgeDB on GCP using Cloud SQL and Kubernetes. diff --git a/docs/guides/deployment/heroku.rst b/docs/guides/deployment/heroku.rst index 7358c4e6312..82ca9bebd1b 100644 --- a/docs/guides/deployment/heroku.rst +++ b/docs/guides/deployment/heroku.rst @@ -4,7 +4,7 @@ Heroku ====== -:edb-alt-title: Deploying EdgeDB to Heroku +:edb-alt-title: Deploying Gel to Heroku In this guide we show how to deploy EdgeDB to Heroku using a Heroku PostgreSQL add-on as the backend. diff --git a/docs/guides/tutorials/chatgpt_bot.rst b/docs/guides/tutorials/chatgpt_bot.rst index 03bbdf08856..06cac65cb6b 100644 --- a/docs/guides/tutorials/chatgpt_bot.rst +++ b/docs/guides/tutorials/chatgpt_bot.rst @@ -4,7 +4,7 @@ ChatGPT ======= -:edb-alt-title: Build your own documentation chatbot with ChatGPT and EdgeDB +:edb-alt-title: Build your own documentation chatbot with ChatGPT and Gel *For additional context, check out* `our blog post about why and how we use ChatGPT via embeddings`_ *to create our “Ask AI” bot which answers questions diff --git a/docs/guides/tutorials/cloudflare_workers.rst b/docs/guides/tutorials/cloudflare_workers.rst index 0c920b4484b..e3ed10df315 100644 --- a/docs/guides/tutorials/cloudflare_workers.rst +++ b/docs/guides/tutorials/cloudflare_workers.rst @@ -4,11 +4,11 @@ Cloudflare Workers ================== -:edb-alt-title: Using EdgeDB in Cloudflare Workers +:edb-alt-title: Using Gel in Cloudflare Workers -This guide demonstrates how to integrate EdgeDB with Cloudflare Workers to -build serverless applications that can interact with EdgeDB. +This guide demonstrates how to integrate EdgeDB with Cloudflare Workers to +build serverless applications that can interact with EdgeDB. It covers the following: @@ -51,16 +51,16 @@ Use the `create-cloudflare`_ package to create a new Cloudflare Worker project. Answer the prompts to create a new project. Pick the *"Hello World" Worker* template to get started. - + You'll be asked if you want to put your project on Cloudflare. -If you say yes, you'll need to sign in (if you haven't already). -If you don't want to deploy right away, switch to the project folder -you just made to start writing your code. When you're ready to deploy your +If you say yes, you'll need to sign in (if you haven't already). +If you don't want to deploy right away, switch to the project folder +you just made to start writing your code. When you're ready to deploy your project on Cloudflare, you can run ``npx wrangler deploy`` to push it. .. note:: Using Wrangler CLI - If you prefer using `Wrangler`_ to set up your worker, you can use the + If you prefer using `Wrangler`_ to set up your worker, you can use the :code:`wrangler generate` command to create a new project. .. _Wrangler: https://developers.cloudflare.com/workers/cli-wrangler @@ -75,23 +75,23 @@ You can use `EdgeDB Cloud`_ for a managed service or run EdgeDB locally. **Local EdgeDB Setup (Optional for EdgeDB Cloud Users)** -If you're running EdgeDB locally, you can use the following command +If you're running EdgeDB locally, you can use the following command to create a new instance: .. code-block:: bash $ edgedb project init -It creates an :code:`edgedb.toml` config file and a schema file +It creates an :code:`edgedb.toml` config file and a schema file :code:`dbschema/default.esdl`. It also spins up an EdgeDB instance and associates it with the current directory. -As long as you’re inside the project directory, all CLI commands will +As long as you’re inside the project directory, all CLI commands will be executed against this instance. -You can run :code:`edgedb` in your terminal to open an -interactive REPL to your instance. +You can run :code:`edgedb` in your terminal to open an +interactive REPL to your instance. .. code-block:: bash @@ -105,7 +105,7 @@ interactive REPL to your instance. **Extend The Default Schema (Optional)** -You can extend the default schema, :code:`dbschema/default.esdl`, to define +You can extend the default schema, :code:`dbschema/default.esdl`, to define your data model, and then try it out in the Cloudflare Worker code. Add new types to the schema file: @@ -135,7 +135,7 @@ Then apply the schema schema to your EdgeDB instance: Using EdgeDB in a Cloudflare Worker ==================================== -Open the :code:`index.ts` file from the :code:`src` directory in your project, +Open the :code:`index.ts` file from the :code:`src` directory in your project, and remove the default code. To interact with your **local EdgeDB instance**, use the following code: @@ -175,13 +175,13 @@ To interact with your **local EdgeDB instance**, use the following code: .. note:: tlsSecurity - The :code:`tlsSecurity` option is set to :code:`insecure` to allow + The :code:`tlsSecurity` option is set to :code:`insecure` to allow connections to a local EdgeDB instance. This lets you test your Cloudflare Worker locally. **Don't use this option in production.** **Client Setup with EdgeDB Cloud** -If you're using EdgeDB Cloud, you can instead use the following code to +If you're using EdgeDB Cloud, you can instead use the following code to set up the client: .. code-block:: typescript @@ -193,10 +193,10 @@ set up the client: .. note:: Environment variables - You can obtain :code:`EDGEDB_INSTANCE` and :code:`EDGEDB_SECRET_KEY` + You can obtain :code:`EDGEDB_INSTANCE` and :code:`EDGEDB_SECRET_KEY` values from the EdgeDB Cloud dashboard. -You will need to set the :code:`EDGEDB_INSTANCE` and :code:`EDGEDB_SECRET` +You will need to set the :code:`EDGEDB_INSTANCE` and :code:`EDGEDB_SECRET` environment variables in your Cloudflare Worker project. Add the following to your :code:`wrangler.toml` file: @@ -212,11 +212,11 @@ environment variables. **Running the Worker** -.. note:: Adding polyfills for Node.js +.. note:: Adding polyfills for Node.js - The :code:`edgedb` package currently uses Node.js built-in modules - that are not available in the Cloudflare Worker environment. - You have to add the following line to your :code:`wrangler.toml` file + The :code:`edgedb` package currently uses Node.js built-in modules + that are not available in the Cloudflare Worker environment. + You have to add the following line to your :code:`wrangler.toml` file to include the polyfills: .. code-block:: toml @@ -229,7 +229,7 @@ To run the worker locally, use the following command: $ npm run dev # or pnpm, yarn, bun -This will start a local server at :code:`http://localhost:8787`. +This will start a local server at :code:`http://localhost:8787`. Run :code:`curl http://localhost:8787` to see the response. **Deploying the Worker to Cloudflare** @@ -240,19 +240,19 @@ To deploy the worker to Cloudflare, use the following command: $ npm run deploy # or pnpm, yarn, bun -This will deploy the worker to Cloudflare and provide you with a URL +This will deploy the worker to Cloudflare and provide you with a URL to access your worker. Wrapping up =========== -Congratulations! You have successfully integrated EdgeDB with +Congratulations! You have successfully integrated EdgeDB with Cloudflare Workers. -Here's a minimal starter project that you can use as a +Here's a minimal starter project that you can use as a reference: `EdgeDB Cloudflare Workers Example`_. -Check out the `Cloudflare Workers documentation`_ for more information and +Check out the `Cloudflare Workers documentation`_ for more information and to learn about the various features and capabilities of Cloudflare Workers. .. _`EdgeDB Cloudflare Workers Example`: diff --git a/docs/guides/tutorials/graphql_apis_with_strawberry.rst b/docs/guides/tutorials/graphql_apis_with_strawberry.rst index 572bfceb75b..1a2145d3e9a 100644 --- a/docs/guides/tutorials/graphql_apis_with_strawberry.rst +++ b/docs/guides/tutorials/graphql_apis_with_strawberry.rst @@ -2,7 +2,7 @@ Strawberry ========== -:edb-alt-title: Building a GraphQL API with EdgeDB and Strawberry +:edb-alt-title: Building a GraphQL API with Gel and Strawberry EdgeDB allows you to query your database with GraphQL via the built-in GraphQL extension. It enables you to expose GraphQL-driven CRUD APIs for all object diff --git a/docs/guides/tutorials/jupyter_notebook.rst b/docs/guides/tutorials/jupyter_notebook.rst index 493e099b8aa..c7bbeb64010 100644 --- a/docs/guides/tutorials/jupyter_notebook.rst +++ b/docs/guides/tutorials/jupyter_notebook.rst @@ -4,7 +4,7 @@ Jupyter Notebook ================ -:edb-alt-title: Using EdgeDB with Jupyter Notebook +:edb-alt-title: Using Gel with Jupyter Notebook 1. `Install Jupyter Notebook `__ diff --git a/docs/guides/tutorials/nextjs_pages_router.rst b/docs/guides/tutorials/nextjs_pages_router.rst index 8503b1eb6ac..5a94ef49b1d 100644 --- a/docs/guides/tutorials/nextjs_pages_router.rst +++ b/docs/guides/tutorials/nextjs_pages_router.rst @@ -4,7 +4,7 @@ Next.js (Pages Router) ====================== -:edb-alt-title: Building a simple blog application with EdgeDB and +:edb-alt-title: Building a simple blog application with Gel and Next.js (Pages Router) We're going to build a simple blog application with @@ -125,11 +125,11 @@ something like this. Initializing EdgeDB ------------------- -Now let's spin up a database for the app. You have two options to initialize -an EdgeDB project: using ``npx edgedb`` without installing the CLI, or +Now let's spin up a database for the app. You have two options to initialize +an EdgeDB project: using ``npx edgedb`` without installing the CLI, or installing the edgedb CLI directly. In this tutorial, we'll use the first -option. If you prefer to install the CLI, see the -`EdgeDB CLI installation guide `_ +option. If you prefer to install the CLI, see the +`EdgeDB CLI installation guide `_ for more information. From the application's root directory, run the following command: diff --git a/docs/guides/tutorials/rest_apis_with_fastapi.rst b/docs/guides/tutorials/rest_apis_with_fastapi.rst index 62ab990cf9c..704cda74ba7 100644 --- a/docs/guides/tutorials/rest_apis_with_fastapi.rst +++ b/docs/guides/tutorials/rest_apis_with_fastapi.rst @@ -4,7 +4,7 @@ FastAPI ======= -:edb-alt-title: Building a REST API with EdgeDB and FastAPI +:edb-alt-title: Building a REST API with Gel and FastAPI Because FastAPI encourages and facilitates strong typing, it's a natural pairing with EdgeDB. Our Python code generation generates not only typed @@ -29,10 +29,10 @@ Prerequisites ============= Before we start, make sure you've :ref:`installed ` the -``edgedb`` command line tool. For this tutorial, we'll use Python 3.10 to -take advantage of the asynchronous I/O paradigm to communicate with the -database more efficiently. You can use newer versions of Python if you prefer, -but you may need to adjust the code accordingly. If you want to skip ahead, +``edgedb`` command line tool. For this tutorial, we'll use Python 3.10 to +take advantage of the asynchronous I/O paradigm to communicate with the +database more efficiently. You can use newer versions of Python if you prefer, +but you may need to adjust the code accordingly. If you want to skip ahead, the completed source code for this API can be found `in our examples repo `_. If you want to check out an example with EdgeDB Auth, you can find that in the same @@ -1186,7 +1186,7 @@ following to your schema definition: .. code-block:: sdl - using extension auth; + using extension auth; Once added, make sure to apply the schema changes by migrating your database schema. @@ -1202,8 +1202,8 @@ Configuring EdgeDB Auth The configuration of EdgeDB Auth involves setting various parameters to secure and tailor authentication to your needs. For now, we'll focus on the essential -parameters to get started. You can configure these settings through a Python -script, which is recommended for scalability, or you can use the EdgeDB UI for +parameters to get started. You can configure these settings through a Python +script, which is recommended for scalability, or you can use the EdgeDB UI for a more user-friendly approach. **Auth Signing Key** @@ -1256,7 +1256,7 @@ Enabling authentication providers You need to configure at least one authentication provider to use EdgeDB Auth. This can be done via the EdgeDB UI or directly through queries. -In this example, we'll configure a email and password provider. You can add +In this example, we'll configure a email and password provider. You can add it with the following query: .. code-block:: edgeql @@ -1268,9 +1268,9 @@ it with the following query: .. note:: - ``require_verification`` defaults to ``true``. In this example, we're - setting it to ``false`` to simplify the setup. In a production environment, - you should set it to ``true`` to ensure that users verify their email + ``require_verification`` defaults to ``true``. In this example, we're + setting it to ``false`` to simplify the setup. In a production environment, + you should set it to ``true`` to ensure that users verify their email addresses before they can log in. If you use the Email and Password provider, in addition to the @@ -1298,7 +1298,7 @@ great for testing in development: CONFIGURE CURRENT BRANCH SET ext::auth::SMTPConfig::validate_certs := false; -You can query the database configuration to discover which providers are +You can query the database configuration to discover which providers are configured with the following query: .. code-block:: edgeql @@ -1356,7 +1356,7 @@ Next, we're going to create endpoints in FastAPI to handle user registration import httpx router = APIRouter() - + # Value should be: # {protocol}://${host}:${port}/branch/${branch}/ext/auth/ EDGEDB_AUTH_BASE_URL = os.getenv('EDGEDB_AUTH_BASE_URL') @@ -1439,7 +1439,7 @@ a new user. It also sets the auth token as an HttpOnly cookie in the response. return response The sign-in endpoint sends a POST request to the EdgeDB Auth server to authenticate -a user. It then retrieves the code from the response and exchanges it for an auth +a user. It then retrieves the code from the response and exchanges it for an auth token. The token is set as an HttpOnly cookie in the response. **Add the auth endpoints to the FastAPI application** @@ -1455,7 +1455,7 @@ Creating a new user in the sign-up endpoint ------------------------------------------- Now, let's automatically create a new user in the database when a user signs up. -We'll use the ``create_user_async_edgeql`` query we generated earlier +We'll use the ``create_user_async_edgeql`` query we generated earlier to achieve this, but we'll need to modify it slightly to link it to the EdgeDB Auth identity. @@ -1464,14 +1464,14 @@ to store the EdgeDB Auth identity and a new ``current_user`` type. .. code-block:: sdl-diff :caption: dbschema/default.esdl - + + global current_user := assert_single( + (( + select User + filter .identity = global ext::auth::ClientTokenIdentity + )) + ); - + type User extending Auditable { + required identity: ext::auth::Identity; required name: str { @@ -1530,7 +1530,7 @@ endpoint to create a new user in the database. We need to do a few things: + if not email or not password or not name: - raise HTTPException(status_code=400, detail="Missing email or password.") + raise HTTPException(status_code=400, detail="Missing email, password, or name.") - + verifier, challenge = generate_pkce() register_url = f"{EDGEDB_AUTH_BASE_URL}/register" register_response = httpx.post(register_url, json={ @@ -1540,14 +1540,14 @@ endpoint to create a new user in the database. We need to do a few things: "provider": "builtin::local_emailpassword", "verify_url": "http://localhost:8000/auth/verify", }) - + if register_response.status_code != 200 and register_response.status_code != 201: return JSONResponse(status_code=400, content={"message": "Registration failed"}) - + code = register_response.json().get("code") token_url = f"{EDGEDB_AUTH_BASE_URL}/token" token_response = httpx.get(token_url, params={"code": code, "verifier": verifier}) - + if token_response.status_code != 200: return JSONResponse(status_code=400, content={"message": "Token exchange failed"}) @@ -1560,7 +1560,7 @@ endpoint to create a new user in the database. We need to do a few things: + status_code=400, + detail={"error": f"User with email '{email}' already exists."}, + ) - + response = JSONResponse(content={"message": "User registered"}) response.set_cookie(key="edgedb-auth-token", value=auth_token, httponly=True, secure=True, samesite='strict') return response @@ -1578,7 +1578,7 @@ You can now test the sign-up endpoint by sending a POST request to If the request is successful, you should see a response with the message ``User registered``. - + Wrapping up =========== @@ -1586,13 +1586,13 @@ Wrapping up Now you have a fully functioning events API in FastAPI backed by EdgeDB. If you want to see all the source code for the completed project, you'll find it in `our examples repo -`_. We also +`_. We also have a separate example that demonstrates how to integrate EdgeDB Auth with -FastAPI in the same repo. Check it out +FastAPI in the same repo. Check it out `here `_. -If you're stuck or if you just want to show off what you've built, come talk -to us `on Discord `_. It's a great community of -helpful folks, all passionate about being part of the next generation of +If you're stuck or if you just want to show off what you've built, come talk +to us `on Discord `_. It's a great community of +helpful folks, all passionate about being part of the next generation of databases. If you like what you see and want to dive deeper into EdgeDB and what it can diff --git a/docs/guides/tutorials/rest_apis_with_flask.rst b/docs/guides/tutorials/rest_apis_with_flask.rst index d6adf4d3e7a..5cc48b6ec73 100644 --- a/docs/guides/tutorials/rest_apis_with_flask.rst +++ b/docs/guides/tutorials/rest_apis_with_flask.rst @@ -4,7 +4,7 @@ Flask ===== -:edb-alt-title: Building a REST API with EdgeDB and Flask +:edb-alt-title: Building a REST API with Gel and Flask The EdgeDB Python client makes it easy to integrate EdgeDB into your preferred web development stack. In this tutorial, we'll see how you can quickly start diff --git a/docs/guides/tutorials/trpc.rst b/docs/guides/tutorials/trpc.rst index 4dab003f33a..54c060112df 100644 --- a/docs/guides/tutorials/trpc.rst +++ b/docs/guides/tutorials/trpc.rst @@ -4,19 +4,19 @@ tRPC ==== -:edb-alt-title: Integrating EdgeDB with tRPC +:edb-alt-title: Integrating Gel with tRPC -This guide explains how to integrate **EdgeDB** with **tRPC** for a modern, -type-safe API. We'll cover setting up database interactions, API routing, -and implementing authentication, all while ensuring type safety across the +This guide explains how to integrate **EdgeDB** with **tRPC** for a modern, +type-safe API. We'll cover setting up database interactions, API routing, +and implementing authentication, all while ensuring type safety across the client and server. You can reference the following repositories for more context: -- `create-t3-turbo-edgedb `_ - - A monorepo template using the `T3 stack `_, +- `create-t3-turbo-edgedb `_ - + A monorepo template using the `T3 stack `_, `Turborepo `_, and EdgeDB. -- `LookFeel Project `_ - A real-world +- `LookFeel Project `_ - A real-world example using **EdgeDB** and **tRPC**. Step 1: EdgeDB setup @@ -27,7 +27,7 @@ EdgeDB will serve as the database layer for your application. Install and initialize EdgeDB ----------------------------- -To initialize **EdgeDB**, run the following command using your preferred +To initialize **EdgeDB**, run the following command using your preferred package manager: .. code-block:: bash @@ -66,7 +66,7 @@ Once schema changes are made, apply migrations with: Step 2: Configure EdgeDB Client =============================== -To interact with **EdgeDB** from your application, you need to configure the +To interact with **EdgeDB** from your application, you need to configure the client. Install EdgeDB Client @@ -96,7 +96,7 @@ This client will be used to interact with the database and execute queries. Step 3: tRPC setup ================== -**tRPC** enables type-safe communication between the frontend and +**tRPC** enables type-safe communication between the frontend and backend. Install tRPC dependencies @@ -111,7 +111,7 @@ Install the required tRPC dependencies: $ # or npm install @trpc/server @trpc/client $ # or bun add @trpc/server @trpc/client -If you're using React and would like to use React Query with tRPC, also +If you're using React and would like to use React Query with tRPC, also install a wrapper around the `@tanstack/react-query `_. .. code-block:: bash @@ -143,7 +143,7 @@ Here’s how to define a simple tRPC query that interacts with **EdgeDB**: export type AppRouter = typeof appRouter; -This example defines a query that fetches user data from EdgeDB, ensuring +This example defines a query that fetches user data from EdgeDB, ensuring type safety in both the query and response. Step 4: Use tRPC Client @@ -161,7 +161,7 @@ If you're working with **Next.js**, here’s how to integrate **tRPC**: Create a tRPC API Handler ~~~~~~~~~~~~~~~~~~~~~~~~~ -Inside ``api/trpc/[trpc].ts``, create the following handler to connect +Inside ``api/trpc/[trpc].ts``, create the following handler to connect **tRPC** with Next.js: .. code-block:: typescript @@ -269,15 +269,15 @@ In non-Next.js apps, use the tRPC client to interact with the server: Step 5: Set up authentication with EdgeDB Auth ============================================== -In this section, we will cover how to integrate **EdgeDB Auth** with **tRPC** -and context in both **Next.js** and **Express** environments. This will ensure -that user authentication is handled securely and that both server-side and +In this section, we will cover how to integrate **EdgeDB Auth** with **tRPC** +and context in both **Next.js** and **Express** environments. This will ensure +that user authentication is handled securely and that both server-side and client-side tRPC calls can access the user’s session. EdgeDB Auth with tRPC and tRPC context in Next.js ------------------------------------------------- -In **Next.js**, integrating **EdgeDB Auth** with **tRPC** involves creating a +In **Next.js**, integrating **EdgeDB Auth** with **tRPC** involves creating a context that provides the user session and EdgeDB client to the tRPC API. 1. **Initialize EdgeDB Client and Auth** @@ -301,7 +301,7 @@ context that provides the user session and EdgeDB client to the tRPC API. 2. **Create tRPC Context** - The **tRPC** context provides the EdgeDB Auth session to the tRPC + The **tRPC** context provides the EdgeDB Auth session to the tRPC procedures: .. code-block:: typescript @@ -325,7 +325,7 @@ context that provides the user session and EdgeDB client to the tRPC API. 3. **Use tRPC Context in API Handler** - In **Next.js**, set up an API handler to connect your **tRPC router** with + In **Next.js**, set up an API handler to connect your **tRPC router** with the context: .. code-block:: typescript @@ -342,7 +342,7 @@ context that provides the user session and EdgeDB client to the tRPC API. 4. **Example tRPC Procedure** - You can now write procedures in your tRPC router, making use of the + You can now write procedures in your tRPC router, making use of the **EdgeDB Auth** session and the **EdgeDB** client: .. code-block:: typescript @@ -364,12 +364,12 @@ context that provides the user session and EdgeDB client to the tRPC API. EdgeDB Auth with tRPC and Context in Express -------------------------------------------- -In **Express**, the process involves setting up middleware to manage the +In **Express**, the process involves setting up middleware to manage the authentication and context for tRPC procedures. 1. **Initialize EdgeDB Client and Auth for Express** - Just like in **Next.js**, you first initialize the **EdgeDB** client and + Just like in **Next.js**, you first initialize the **EdgeDB** client and **EdgeDB Auth**: .. code-block:: typescript @@ -387,7 +387,7 @@ authentication and context for tRPC procedures. 2. **Create tRPC Context Middleware for Express** - In **Express**, create middleware to pass the authenticated session and + In **Express**, create middleware to pass the authenticated session and EdgeDB client to the tRPC context: .. code-block:: typescript @@ -410,7 +410,7 @@ authentication and context for tRPC procedures. 3. **Set up tRPC Router in Express** - Use the **tRPC router** in **Express** by including the context middleware + Use the **tRPC router** in **Express** by including the context middleware and **EdgeDB Auth** middleware: .. code-block:: typescript @@ -444,7 +444,7 @@ authentication and context for tRPC procedures. 4. **Example tRPC Procedure in Express** - Once the context is set, you can define tRPC procedures that use both the + Once the context is set, you can define tRPC procedures that use both the session and EdgeDB client: .. code-block:: typescript @@ -466,14 +466,14 @@ authentication and context for tRPC procedures. Conclusion ---------- -By integrating **EdgeDB Auth** into the tRPC context, you ensure that -authenticated sessions are securely passed to API procedures, enabling +By integrating **EdgeDB Auth** into the tRPC context, you ensure that +authenticated sessions are securely passed to API procedures, enabling user authentication and protecting routes. You can also reference these projects for further examples: -- `create-t3-turbo-edgedb `_ - - A monorepo template using the `T3 stack `_, +- `create-t3-turbo-edgedb `_ - + A monorepo template using the `T3 stack `_, `Turborepo `_, and EdgeDB. -- `LookFeel Project `_ - A real-world +- `LookFeel Project `_ - A real-world example using **EdgeDB** and **tRPC**. diff --git a/docs/index.rst b/docs/index.rst index 4da5f27d27e..4fc6781909e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,10 +2,10 @@ ==================== -EdgeDB documentation +Gel documentation ==================== -Welcome to the EdgeDB |version| documentation. +Welcome to the |Gel| |version| documentation. .. !!! DO NOT CHANGE :maxdepth: egdedb.com/docs depends on it !!! From d779fa909f7744e43c6c6bcc5092ab3bd6a05b4d Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 18:45:50 -0800 Subject: [PATCH 066/154] Fix docs/tests to go green --- docs/clients/js/driver.rst | 30 +++++++++++++++--------------- docs/clients/js/for.rst | 2 +- docs/clients/js/reference.rst | 26 +++++++++++++------------- tests/test_docs.py | 1 + 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/docs/clients/js/driver.rst b/docs/clients/js/driver.rst index 0dc62a325b6..4455b59b60c 100644 --- a/docs/clients/js/driver.rst +++ b/docs/clients/js/driver.rst @@ -23,7 +23,7 @@ executing queries. To create a client: -.. code-block:: js +.. code-block:: javascript const edgedb = require("edgedb"); @@ -33,7 +33,7 @@ To create a client: If you're using TypeScript or have ES modules enabled, you can use ``import`` syntax instead: -.. code-block:: js +.. code-block:: javascript import * as edgedb from "edgedb"; @@ -102,7 +102,7 @@ Running queries To execute a basic query: -.. code-block:: js +.. code-block:: javascript const edgedb = require("edgedb"); @@ -118,7 +118,7 @@ To execute a basic query: In TypeScript, you can supply a type hint to receive a strongly typed result. -.. code-block:: js +.. code-block:: javascript const result = await client.query(`select 2 + 2;`); // number[] @@ -129,7 +129,7 @@ In TypeScript, you can supply a type hint to receive a strongly typed result. The ``.query`` method always returns an array of results. It places no constraints on cardinality. -.. code-block:: js +.. code-block:: javascript await client.query(`select 2 + 2;`); // [4] await client.query(`select [1, 2, 3];`); // [[1, 2, 3]] @@ -149,7 +149,7 @@ a set with more than one elements, the ``Client`` will throw a runtime error. Note that if you're selecting an array or tuple, the returned value may still be an array. -.. code-block:: js +.. code-block:: javascript await client.querySingle(`select 2 + 2;`); // 4 await client.querySingle(`select [1, 2, 3];`); // [1, 2, 3] @@ -163,7 +163,7 @@ Use ``queryRequiredSingle`` for queries that return *exactly one* element. If the query returns an empty set or a set with multiple elements, the ``Client`` will throw a runtime error. -.. code-block:: js +.. code-block:: javascript await client.queryRequiredSingle(`select 2 + 2;`); // 4 await client.queryRequiredSingle(`select [1, 2, 3];`); // [1, 2, 3] @@ -274,7 +274,7 @@ Client provide additional methods for running queries and retrieving results as a *serialized JSON string*. This serialization happens inside the database and is typically more performant than running ``JSON.stringify`` yourself. -.. code-block:: js +.. code-block:: javascript await client.queryJSON(`select {1, 2, 3};`); // "[1, 2, 3]" @@ -292,7 +292,7 @@ To execute a query without retrieving a result, use the ``.execute`` method. This is especially useful for mutations, where there's often no need for the query to return a value. -.. code-block:: js +.. code-block:: javascript await client.execute(`insert Movie { title := "Avengers: Endgame" @@ -301,7 +301,7 @@ query to return a value. With EdgeDB 2.0 or later, you can execute a "script" consisting of multiple semicolon-separated statements in a single ``.execute`` call. -.. code-block:: js +.. code-block:: javascript await client.execute(` insert Person { name := "Robert Downey Jr." }; @@ -323,7 +323,7 @@ Parameters If your query contains parameters (e.g. ``$foo``), you can pass in values as the second argument. This is true for all ``query*`` methods and ``execute``. -.. code-block:: js +.. code-block:: javascript const INSERT_MOVIE = `insert Movie { title := $title @@ -346,7 +346,7 @@ transaction (unless already in an explicit transaction), so the whole script remains atomic. For the ``query*`` methods only the result of the final statement in the script will be returned. -.. code-block:: js +.. code-block:: javascript const result = await client.query(` insert Movie { @@ -374,7 +374,7 @@ established until the first time you execute a query. If you want to explicitly ensure that the client is connected without running a query, use the ``.ensureConnected()`` method. -.. code-block:: js +.. code-block:: javascript const edgedb = require("edgedb"); @@ -392,7 +392,7 @@ Transactions The most robust way to execute transactional code is to use the ``transaction()`` API: -.. code-block:: js +.. code-block:: javascript await client.transaction(tx => { await tx.execute("insert User {name := 'Don'}"); @@ -419,7 +419,7 @@ The key implication of retrying transactions is that the entire nested code block can be re-run, including any non-querying JavaScript code. Here is an example: -.. code-block:: js +.. code-block:: javascript const email = "timmy@edgedb.com" diff --git a/docs/clients/js/for.rst b/docs/clients/js/for.rst index da1c7702ff3..70c100a2edb 100644 --- a/docs/clients/js/for.rst +++ b/docs/clients/js/for.rst @@ -79,7 +79,7 @@ Note that the ``Movie`` type's ``title`` property has an exclusive constraint. Here's the data we want to bulk insert: -.. code-block:: js +.. code-block:: javascript [ { diff --git a/docs/clients/js/reference.rst b/docs/clients/js/reference.rst index 3f2895bd561..3ff9b8d8d87 100644 --- a/docs/clients/js/reference.rst +++ b/docs/clients/js/reference.rst @@ -95,7 +95,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); @@ -156,7 +156,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript await client.execute(` CREATE TYPE MyType { @@ -332,7 +332,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript await client.executeSQL(` INSERT INTO "MyType"(prop) VALUES ("value"); @@ -353,7 +353,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript let vals = await client.querySQL(`SELECT 1 as foo`) console.log(vals); // [{'foo': 1}] @@ -384,7 +384,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript await client.transaction(async tx => { const value = await tx.querySingle("select Counter.value") @@ -411,7 +411,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript import {createClient} from 'edgedb'; @@ -439,7 +439,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript const user = await client.withGlobals({ userId: '...' @@ -459,7 +459,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript const user = await client.withModuleAliases({ module: 'sys' @@ -501,7 +501,7 @@ Client Example: - .. code-block:: js + .. code-block:: javascript import {createClient} from 'edgedb'; @@ -633,7 +633,7 @@ Arrays EdgeDB ``array`` maps onto the JavaScript ``Array``. -.. code-block:: js +.. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); @@ -661,7 +661,7 @@ Objects ``Object`` represents an object instance returned from a query. The value of an object property or a link can be accessed through a corresponding object key: -.. code-block:: js +.. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); @@ -698,7 +698,7 @@ Tuples A regular EdgeDB ``tuple`` becomes an ``Array`` in JavaScript. -.. code-block:: js +.. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); @@ -727,7 +727,7 @@ Named Tuples A named EdgeDB ``tuple`` becomes an ``Array``-like ``object`` in JavaScript, where the elements are accessible either by their names or indexes. -.. code-block:: js +.. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); diff --git a/tests/test_docs.py b/tests/test_docs.py index 00250b3c58b..966dd08ef98 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -359,6 +359,7 @@ def run_block_test(self, block): 'typescript', 'go', 'yaml', + 'text', 'jsx', 'rust', 'tsx', From 83efcee97e952fc992dd8a77b19e13db63056c17 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 19:03:47 -0800 Subject: [PATCH 067/154] Get rid of trailing whitespace for good --- docs/clients/go/index.rst | 60 +++++++-------- docs/clients/python/index.rst | 2 +- docs/datamodel/extensions.rst | 2 +- docs/edgeql/paths.rst | 14 ++-- docs/edgeql/select.rst | 12 +-- docs/guides/tutorials/nextjs_app_router.rst | 82 ++++++++++----------- docs/reference/sdl/globals.rst | 2 +- docs/stdlib/bytes.rst | 4 +- docs/stdlib/numbers.rst | 2 +- docs/stdlib/set.rst | 16 ++-- tests/test_docs.py | 23 ++++++ 11 files changed, 121 insertions(+), 98 deletions(-) diff --git a/docs/clients/go/index.rst b/docs/clients/go/index.rst index 89c77ecbb5a..49eee7a35df 100644 --- a/docs/clients/go/index.rst +++ b/docs/clients/go/index.rst @@ -24,14 +24,14 @@ Typical client usage looks like this: .. code-block:: go package main - + import ( "context" "log" - + "github.com/edgedb/edgedb-go" ) - + func main() { ctx := context.Background() client, err := edgedb.CreateClient(ctx, edgedb.Options{}) @@ -39,7 +39,7 @@ Typical client usage looks like this: log.Fatal(err) } defer client.Close() - + var ( age int64 = 21 users []struct { @@ -47,12 +47,12 @@ Typical client usage looks like this: Name string `edgedb:"name"` } ) - + query := "SELECT User{name} FILTER .age = $0" err = client.Query(ctx, query, &users, age) ... } - + We recommend using environment variables for connection parameters. See the `client connection docs `_ for more information. @@ -62,7 +62,7 @@ You may also connect to a database using a DSN: url := "edgedb://edgedb@localhost/edgedb" client, err := edgedb.CreateClientDSN(ctx, url, opts) - + Or you can use Option fields. .. code-block:: go @@ -72,9 +72,9 @@ Or you can use Option fields. User: "edgedb", Concurrency: 4, } - + client, err := edgedb.CreateClient(ctx, opts) - + Errors ------ @@ -87,19 +87,19 @@ use errors.Is() or errors.As(). err := client.Query(...) if errors.Is(err, context.Canceled) { ... } - + Most errors returned by the edgedb package will satisfy the edgedb.Error interface which has methods for introspecting. .. code-block:: go err := client.Query(...) - + var edbErr edgedb.Error if errors.As(err, &edbErr) && edbErr.Category(edgedb.NoDataError){ ... } - + Datatypes --------- @@ -136,9 +136,9 @@ mapping between EdgeDB types and go types: uuid edgedb.UUID, edgedb.OptionalUUID json []byte, edgedb.OptionalBytes bigint *big.Int, edgedb.OptionalBigInt - + decimal user defined (see Custom Marshalers) - + Note that EdgeDB's std::duration type is represented in int64 microseconds while go's time.Duration type is int64 nanoseconds. It is incorrect to cast one directly to the other. @@ -153,16 +153,16 @@ optional. edgedb.Optional Email string `edgedb:"email"` } - + var result User err := client.QuerySingle(ctx, `SELECT User { email } LIMIT 0`, $result) fmt.Println(result.Missing()) // Output: true - + err := client.QuerySingle(ctx, `SELECT User { email } LIMIT 1`, $result) fmt.Println(result.Missing()) // Output: false - + Not all types listed above are valid query parameters. To pass a slice of scalar values use array in your query. EdgeDB doesn't currently support using sets as parameters. @@ -171,7 +171,7 @@ using sets as parameters. query := `select User filter .id in array_unpack(>$1)` client.QuerySingle(ctx, query, $user, []edgedb.UUID{...}) - + Nested structures are also not directly allowed but you can use `json `_ instead. @@ -184,12 +184,12 @@ tag the embedded struct with \`edgedb:"$inline"\`. type Object struct { ID edgedb.UUID } - + type User struct { Object `edgedb:"$inline"` Name string } - + Custom Marshalers ----------------- @@ -203,24 +203,24 @@ Usage Example ------------- .. code-block:: go - + package edgedb_test - + import ( "context" "fmt" "log" "time" - + edgedb "github.com/edgedb/edgedb-go" ) - + type User struct { ID edgedb.UUID `edgedb:"id"` Name string `edgedb:"name"` DOB time.Time `edgedb:"dob"` } - + func Example() { opts := edgedb.Options{Concurrency: 4} ctx := context.Background() @@ -229,7 +229,7 @@ Usage Example log.Fatal(err) } defer db.Close() - + // create a user object type. err = db.Execute(ctx, ` CREATE TYPE User { @@ -240,7 +240,7 @@ Usage Example if err != nil { log.Fatal(err) } - + // Insert a new user. var inserted struct{ id edgedb.UUID } err = db.QuerySingle(ctx, ` @@ -252,7 +252,7 @@ Usage Example if err != nil { log.Fatal(err) } - + // Select users. var users []User args := map[string]interface{}{"name": "Bob"} @@ -261,7 +261,7 @@ Usage Example if err != nil { log.Fatal(err) } - + fmt.Println(users) } - + diff --git a/docs/clients/python/index.rst b/docs/clients/python/index.rst index 1a647fb6fea..7fb093b2c18 100644 --- a/docs/clients/python/index.rst +++ b/docs/clients/python/index.rst @@ -35,7 +35,7 @@ and :ref:`asyncio ` implementations. * :ref:`edgedb-python-codegen` Python code generation command-line tool documentation. - + * :ref:`edgedb-python-advanced` Advanced usages of the state and optional customization. diff --git a/docs/datamodel/extensions.rst b/docs/datamodel/extensions.rst index b2b96124017..0a10d2e00e4 100644 --- a/docs/datamodel/extensions.rst +++ b/docs/datamodel/extensions.rst @@ -42,7 +42,7 @@ To enable these extensions, add a ``using`` statement at the top level of your s .. code-block:: sdl using extension auth; - + Standalone extensions diff --git a/docs/edgeql/paths.rst b/docs/edgeql/paths.rst index d73e05c4d9c..2ee1b8de913 100644 --- a/docs/edgeql/paths.rst +++ b/docs/edgeql/paths.rst @@ -70,8 +70,8 @@ The first user reciprocates, adding the new user as a friend: .. code-block:: edgeql-repl - db> update User filter .email = "user1@me.com" - ... set { + db> update User filter .email = "user1@me.com" + ... set { ... friends += (select detached User filter .email = "user2@me.com") ... }; @@ -153,7 +153,7 @@ database. However, we can't impose a shape on it: As written, EdgeDB infers the *type* of this expression to be :eql:type:`BaseObject`. Why? Because in theory, there may be several links named ``author`` from different object types -that point to ``User``. And there is no guarantee that each +that point to ``User``. And there is no guarantee that each of these types will have a property called ``text``. .. note:: @@ -161,14 +161,14 @@ of these types will have a property called ``text``. a single property, ``id``. As such, commonly you'll want to narrow the results to a particular type. -To do so, use the :eql:op:`type intersection ` operator: +To do so, use the :eql:op:`type intersection ` operator: ``[is Foo]``: .. code-block:: edgeql - + # BlogPost objects that link to the user via a link named author select User. str; multi link friends -> User; diff --git a/docs/edgeql/select.rst b/docs/edgeql/select.rst index 855a44a305e..06a4132a4d7 100644 --- a/docs/edgeql/select.rst +++ b/docs/edgeql/select.rst @@ -652,10 +652,10 @@ starred in. (Note the ability to :ref:`cast from a uuid ` to an object type, which was added in EdgeDB 3.0!) .. code-block:: edgeql-repl - - db> select Villain filter .'6c60c28a-5c03-11ee-99ff-dfa425012a05' { - ... name := .name ++ ', who got to see Spider-Man!' + + db> select Villain filter .'6c60c28a-5c03-11ee-99ff-dfa425012a05' { + ... name := .name ++ ', who got to see Spider-Man!' ... }; { 'Obadiah Stane', @@ -673,7 +673,7 @@ traversing a backlink would look like this: .. code-block:: edgeql-repl - db> with movie := + db> with movie := ... '6c60c28a-5c03-11ee-99ff-dfa425012a05', ... select movie.characters[is Villain] { ... name := .name ++ ', who got to see Spider-Man!' @@ -1218,7 +1218,7 @@ internals of EdgeDB. Free objects ------------ -.. index:: ad hoc type +.. index:: ad hoc type To select several values simultaneously, you can "bundle" them into a "free object". Free objects are a set of key-value pairs that can contain any diff --git a/docs/guides/tutorials/nextjs_app_router.rst b/docs/guides/tutorials/nextjs_app_router.rst index df876746eb5..0cdb309ab30 100644 --- a/docs/guides/tutorials/nextjs_app_router.rst +++ b/docs/guides/tutorials/nextjs_app_router.rst @@ -11,9 +11,9 @@ We're going to build a simple blog application with `Next.js `_ and EdgeDB. Let's start by scaffolding our app with Next.js's ``create-next-app`` tool. -You'll be prompted to provide a name (we'll use ``nextjs-blog``) for your -app and choose project options. For this tutorial, we'll go with the -recommended settings including TypeScript, App Router, and +You'll be prompted to provide a name (we'll use ``nextjs-blog``) for your +app and choose project options. For this tutorial, we'll go with the +recommended settings including TypeScript, App Router, and **opt-ing out** of the ``src/`` directory. .. code-block:: bash @@ -26,8 +26,8 @@ recommended settings including TypeScript, App Router, and ✔ Would you like to use App Router? (recommended) Yes ✔ Would you like to customize the default import alias (@/*) Yes -The scaffolding tool will create a simple Next.js app and install its -dependencies. Once it's done, you can navigate to the app's directory and +The scaffolding tool will create a simple Next.js app and install its +dependencies. Once it's done, you can navigate to the app's directory and start the development server. .. code-block:: bash @@ -35,8 +35,8 @@ start the development server. $ cd nextjs-blog $ npm dev # or yarn dev or pnpm dev or bun run dev -When the dev server starts, it will log out a local URL. -Visit that URL to see the default Next.js homepage. At this +When the dev server starts, it will log out a local URL. +Visit that URL to see the default Next.js homepage. At this point the app's file structure looks like this: .. code-block:: @@ -57,13 +57,13 @@ point the app's file structure looks like this: ├── next.tsx └── vercel.svg -There's an async function ``Home`` defined in ``app/page.tsx`` that renders -the homepage. It's a +There's an async function ``Home`` defined in ``app/page.tsx`` that renders +the homepage. It's a `Server Component `_ -which lets you integrate server-side logic directly -into your React components. Server Components are executed on the server and -can fetch data from a database or an API. We'll use this feature to load blog +rendering/server-components>`_ +which lets you integrate server-side logic directly +into your React components. Server Components are executed on the server and +can fetch data from a database or an API. We'll use this feature to load blog posts from an EdgeDB database. Updating the homepage @@ -127,11 +127,11 @@ page later in the tutorial. Initializing EdgeDB ------------------- -Now let's spin up a database for the app. You have two options to initialize -an EdgeDB project: using ``npx edgedb`` without installing the CLI, or +Now let's spin up a database for the app. You have two options to initialize +an EdgeDB project: using ``npx edgedb`` without installing the CLI, or installing the edgedb CLI directly. In this tutorial, we'll use the first -option. If you prefer to install the CLI, see the -`EdgeDB CLI installation guide `_ +option. If you prefer to install the CLI, see the +`EdgeDB CLI installation guide `_ for more information. From the application's root directory, run the following command: @@ -262,21 +262,21 @@ Then execute the following ``insert`` statements. Loading posts with React Server Components ------------------------------------------ -Now that we have a couple posts in the database, let's load them into our +Now that we have a couple posts in the database, let's load them into our Next.js app. To do that, we'll need the ``edgedb`` client library. Let's install that from NPM: .. code-block:: bash - $ npm install edgedb + $ npm install edgedb # or yarn add edgedb or pnpm add edgedb or bun add edgedb Then go to the ``app/page.tsx`` file to replace the static data with the blogposts fetched from the database. To fetch these from the homepage, we'll create an EdgeDB client and use the -``.query()`` method to fetch all the posts in the database with a +``.query()`` method to fetch all the posts in the database with a ``select`` statement. .. code-block:: tsx-diff @@ -411,7 +411,7 @@ instead. + content: true, + })); + const posts = await selectPosts.run(client); - + return (

Posts

@@ -458,7 +458,7 @@ Add the following code in ``app/post/[id]/page.tsx``: .. code-block:: tsx :caption: app/post/[id]/page.tsx - + import { createClient } from 'edgedb' import e from '@/dbschema/edgeql-js' import Link from 'next/link' @@ -496,9 +496,9 @@ Add the following code in ``app/post/[id]/page.tsx``: ) } -We are again using a Server Component to fetch the post from the database. -This time, we're using the ``filter_single`` method to filter the -``BlogPost`` type by its ``id``. We're also using the ``uuid`` function +We are again using a Server Component to fetch the post from the database. +This time, we're using the ``filter_single`` method to filter the +``BlogPost`` type by its ``id``. We're also using the ``uuid`` function from the query builder to convert the ``id`` parameter to a UUID. Now, click on one of the blog post links on the homepage. This should bring @@ -507,7 +507,7 @@ you to ``/post/``. Deploying to Vercel ------------------- -You can deploy an EdgeDB instance on the EdgeDB Cloud or +You can deploy an EdgeDB instance on the EdgeDB Cloud or on your preferred cloud provider. We'll cover both options here. With EdgeDB Cloud @@ -515,10 +515,10 @@ With EdgeDB Cloud **#1 Deploy EdgeDB** -First, sign up for an account at -`cloud.edgedb.com `_ and create a new instance. -Create and make note of a secret key for your EdgeDB Cloud instance. You -can create a new secret key from the "Secret Keys" tab in the EdgeDB Cloud +First, sign up for an account at +`cloud.edgedb.com `_ and create a new instance. +Create and make note of a secret key for your EdgeDB Cloud instance. You +can create a new secret key from the "Secret Keys" tab in the EdgeDB Cloud console. We'll need this later to connect to the database from Vercel. Run the following command to migrate the project to the EdgeDB Cloud: @@ -529,7 +529,7 @@ Run the following command to migrate the project to the EdgeDB Cloud: .. note:: - Alternatively, if you want to restore your data from a local instance to + Alternatively, if you want to restore your data from a local instance to the cloud, you can use the ``edgedb dump`` and ``edgedb restore`` commands. .. code-block:: bash @@ -537,7 +537,7 @@ Run the following command to migrate the project to the EdgeDB Cloud: $ npx edgedb dump $ npx edgedb restore -I / -The migrations and schema will be automatically applied to the +The migrations and schema will be automatically applied to the cloud instance. **#2 Set up a `prebuild` script** @@ -545,8 +545,8 @@ cloud instance. Add the following ``prebuild`` script to your ``package.json``. When Vercel initializes the build, it will trigger this script which will generate the query builder. The ``npx @edgedb/generate edgeql-js`` command will read the -value of the ``EDGEDB_SECRET_KEY`` and ``EDGEDB_INSTANCE`` variables, -connect to the database, and generate the query builder before Vercel +value of the ``EDGEDB_SECRET_KEY`` and ``EDGEDB_INSTANCE`` variables, +connect to the database, and generate the query builder before Vercel starts building the project. .. code-block:: javascript-diff @@ -562,7 +562,7 @@ starts building the project. **#3 Deploy to Vercel** -Push your project to GitHub or some other Git remote repository. Then deploy +Push your project to GitHub or some other Git remote repository. Then deploy this app to Vercel with the button below. .. lint-off @@ -590,7 +590,7 @@ With other cloud providers **#1 Deploy EdgeDB** -Check out the following guides for deploying EdgeDB to your preferred cloud +Check out the following guides for deploying EdgeDB to your preferred cloud provider: - `AWS `_ @@ -638,7 +638,7 @@ database. Open a REPL and ``insert`` some blog posts: Add the following ``prebuild`` script to your ``package.json``. When Vercel initializes the build, it will trigger this script which will generate the query builder. The ``npx @edgedb/generate edgeql-js`` command will read the -value of the ``EDGEDB_DSN`` variable, connect to the database, and generate +value of the ``EDGEDB_DSN`` variable, connect to the database, and generate the query builder before Vercel starts building the project. .. code-block:: javascript-diff @@ -686,15 +686,15 @@ Wrapping up ----------- This tutorial demonstrates how to work with EdgeDB in a -Next.js app, using the App Router. We've created a simple blog application -that loads posts from a database and displays them on the homepage. -We've also created a dynamic route that fetches a single post from the +Next.js app, using the App Router. We've created a simple blog application +that loads posts from a database and displays them on the homepage. +We've also created a dynamic route that fetches a single post from the database and displays it on a separate page. The next step is to add a ``/newpost`` page with a form for writing new blog posts and saving them into EdgeDB. That's left as an exercise for the reader. -To see the final code for this tutorial, refer to +To see the final code for this tutorial, refer to `github.com/edgedb/edgedb-examples/tree/main/nextjs-blog `_. diff --git a/docs/reference/sdl/globals.rst b/docs/reference/sdl/globals.rst index 1587c6e4ac7..f1a7e08385e 100644 --- a/docs/reference/sdl/globals.rst +++ b/docs/reference/sdl/globals.rst @@ -109,7 +109,7 @@ The following options are available: :eql:synopsis:` := ` Defines a *computed* global variable. - + The provided expression must be a :ref:`Stable ` EdgeQL expression. It can refer to other global variables. diff --git a/docs/stdlib/bytes.rst b/docs/stdlib/bytes.rst index 81b87461a7a..d358f033243 100644 --- a/docs/stdlib/bytes.rst +++ b/docs/stdlib/bytes.rst @@ -175,8 +175,8 @@ Bytes --------- -.. eql:operator:: bytesplus: bytes ++ bytes -> - +.. eql:operator:: bytesplus: bytes ++ bytes -> + :index: ++, bytes, concatenate, join, add Concatenates two bytes values into one. diff --git a/docs/stdlib/numbers.rst b/docs/stdlib/numbers.rst index 8912fb6ceb3..829ebe262c9 100644 --- a/docs/stdlib/numbers.rst +++ b/docs/stdlib/numbers.rst @@ -778,7 +778,7 @@ Definitions std::bit_count(bytes: bytes) -> int64 Return the number of bits set in the :eql:type:`bytes` value. - + This is also known as the population count. .. code-block:: edgeql-repl diff --git a/docs/stdlib/set.rst b/docs/stdlib/set.rst index 4c5af12ef10..bd41eb5b622 100644 --- a/docs/stdlib/set.rst +++ b/docs/stdlib/set.rst @@ -244,7 +244,7 @@ Sets some condition: .. code-block:: edgeql - + with name := $0, admin := $1 @@ -256,7 +256,7 @@ Sets DML (i.e., :ref:`insert `, :ref:`update `, :ref:`delete `) was not supported in ``if...else`` prior to EdgeDB 4.0. If you need to do one of these - on an older version of EdgeDB, you can use a + on an older version of EdgeDB, you can use a :ref:`for loop conditional ` as a workaround. @@ -272,7 +272,7 @@ Sets .. versionadded:: 4.0 Produces one of two possible results based on a given condition. - + Uses ``then`` for an alternative syntax order to ``if..else`` above. .. eql:synopsis:: @@ -309,7 +309,7 @@ Sets some condition: .. code-block:: edgeql - + with name := $0, admin := $1 @@ -346,12 +346,12 @@ Sets As of EdgeDB 4.0, the coalescing operator can be used to express things like "select or insert if missing": - + .. code-block:: edgeql - select + select (select User filter .name = 'Alice') ?? - (insert User { name := 'Alice' }); + (insert User { name := 'Alice' }); ---------- @@ -774,4 +774,4 @@ Sets .. code-block:: edgeql-repl db> select max({-1, 100}); - {100} \ No newline at end of file + {100} diff --git a/tests/test_docs.py b/tests/test_docs.py index 966dd08ef98..532b2093a1a 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -402,6 +402,29 @@ def test_cqa_doc_snippets(self): for block in blocks: self.run_block_test(block) + def test_cqa_doc_trailing_whitespace(self): + edgepath = find_edgedb_root() + docspath = os.path.join(edgepath, 'docs') + + ws_errors = collections.defaultdict(set) + + for filename in self.find_rest_files(docspath): + with open(filename, 'rt') as f: + source = f.readlines() + + for lineno, line in enumerate(source): + if re.match(r'\s+\n$', line): + ws_errors[filename].add(lineno) + + if ws_errors: + raise AssertionError( + 'trailing whitespace:\n\n' + + '\n'.join( + f'{filename}:{linenos!r}' + for filename, linenos in ws_errors.items() + ) + ) + @unittest.skipIf(docutils is None, 'docutils is missing') def test_doc_test_broken_code_block_01(self): source = ''' From aa6a6a708c282ad53941cfda41c5b692db47b3e3 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 19:06:16 -0800 Subject: [PATCH 068/154] Get rid of weird unicode ' symbols --- docs/ai/index.rst | 2 +- docs/changelog/3_x.rst | 8 ++++---- docs/clients/js/index.rst | 2 +- docs/guides/auth/built_in_ui.rst | 8 ++++---- docs/guides/auth/index.rst | 18 +++++++++--------- docs/guides/tutorials/cloudflare_workers.rst | 2 +- .../tutorials/graphql_apis_with_strawberry.rst | 2 +- .../tutorials/rest_apis_with_fastapi.rst | 2 +- docs/guides/tutorials/trpc.rst | 10 +++++----- docs/intro/migrations.rst | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/ai/index.rst b/docs/ai/index.rst index d8b08b6a2d0..3cc202e4a6c 100644 --- a/docs/ai/index.rst +++ b/docs/ai/index.rst @@ -23,7 +23,7 @@ Enable extension in your schema =============================== AI is an EdgeDB extension. To enable it, you will need to add the extension -to your app’s schema: +to your app's schema: .. code-block:: sdl diff --git a/docs/changelog/3_x.rst b/docs/changelog/3_x.rst index 94d2036893b..b2cfcd98065 100644 --- a/docs/changelog/3_x.rst +++ b/docs/changelog/3_x.rst @@ -984,15 +984,15 @@ New release schedule Unfortunately, the 3.0 release will not include full-text search. We have many requirements for this new API (see `the FTS RFC `_ -for details), and, while we’ve made significant progress, we have unfortunately +for details), and, while we've made significant progress, we have unfortunately run out of time to be 100% sure that it is ready for prime time. -We don’t want this delay to hold back the release of EdgeDB 3.0, which includes +We don't want this delay to hold back the release of EdgeDB 3.0, which includes many other exciting features that are ready for you to start using right now. -That’s why we’ve decided to delay only the FTS feature rather than delaying the +That's why we've decided to delay only the FTS feature rather than delaying the entire 3.0 release. -That said we’re working hard to get FTS ready as soon as possible. After the +That said we're working hard to get FTS ready as soon as possible. After the release of 3.0, we'll be moving to a much more frequent release cycle so that features like FTS can be in your hands as soon as they're ready. diff --git a/docs/clients/js/index.rst b/docs/clients/js/index.rst index be5b75017d3..ea45a11dcf4 100644 --- a/docs/clients/js/index.rst +++ b/docs/clients/js/index.rst @@ -27,7 +27,7 @@ EdgeDB TypeScript/JS Client group reference -This is the official EdgeDB client library for JavaScript and TypeScript. It’s +This is the official EdgeDB client library for JavaScript and TypeScript. It's the easiest way to connect to your database and execute queries from a Node.js or Deno backend. diff --git a/docs/guides/auth/built_in_ui.rst b/docs/guides/auth/built_in_ui.rst index dd8798e2311..7cf1596f915 100644 --- a/docs/guides/auth/built_in_ui.rst +++ b/docs/guides/auth/built_in_ui.rst @@ -11,15 +11,15 @@ the "Enable UI" button under "Login UI" in the configuration section of the EdgeDB UI. Set these configuration values: - ``redirect_to``: Once the authentication flow is complete, EdgeDB will - redirect the user’s browser back to this URL in your application’s + redirect the user's browser back to this URL in your application's backend. - ``redirect_to_on_signup``: If this is a new user, EdgeDB will redirect - the user’s browser back to this URL in your application’s backend. + the user's browser back to this URL in your application's backend. - ``app_name``: Used in the built-in UI to show the user the - application’s name in a few important places. + application's name in a few important places. - ``logo_url``: If provided, will show in the built-in UI as part of the page design. -- ``dark_logo_url``: If provided and the user’s system has indicated +- ``dark_logo_url``: If provided and the user's system has indicated that they prefer a dark UI, this will show instead of ``logo_url`` in the built-in UI as part of the page design. - ``brand_color``: If provided, used in the built-in UI as part of the diff --git a/docs/guides/auth/index.rst b/docs/guides/auth/index.rst index 5a0654a4e65..d7f3d50e268 100644 --- a/docs/guides/auth/index.rst +++ b/docs/guides/auth/index.rst @@ -24,7 +24,7 @@ Enable extension in your schema =============================== Auth is an EdgeDB extension. To enable it, you will need to add the extension -to your app’s schema: +to your app's schema: .. code-block:: sdl @@ -99,7 +99,7 @@ To configure via query or script: token_time_to_live ------------------ -This value controls the expiration time on the authentication token’s +This value controls the expiration time on the authentication token's JSON Web Token. This is effectively the “session” time. To configure via query or script: @@ -277,7 +277,7 @@ like `Mailpit `__. Enabling authentication providers ================================= -In order to use the auth extension, you’ll need to enable at least one of these +In order to use the auth extension, you'll need to enable at least one of these authentication providers. Providers can be added from the "Providers" section of the admin auth UI by clicking "Add Provider." This will add a form to the UI allowing for selection of the provider and configuration of the values @@ -315,7 +315,7 @@ To enable via query or script: ``require_verification`` defaults to ``true``. If you use the Email and Password provider, in addition to the -``require_verification`` configuration, you’ll need to configure SMTP to allow +``require_verification`` configuration, you'll need to configure SMTP to allow EdgeDB to send email verification and password reset emails on your behalf or set up webhooks for the relevant events: @@ -368,8 +368,8 @@ We currently support six different OAuth providers: .. lint-on The instructions for creating an app for each provider can be found on -each provider’s developer documentation website, which is linked above. -The important things you’ll need to find and make note of for your +each provider's developer documentation website, which is linked above. +The important things you'll need to find and make note of for your configuration are the **client ID** and **secret**. Once you select the OAuth provider in the configuration UI, you will need to @@ -390,7 +390,7 @@ provide those values and the ``additional_scope``: We return this authentication token with this scope from the Identity Provider when we return our own authentication token. -You’ll also need to set a callback URL in each provider’s interface. To build +You'll also need to set a callback URL in each provider's interface. To build this callback URL, you will need the hostname, port, and branch name of your database. The branch name is ``main`` by default. The hostname and port can be found running this CLI command: @@ -595,7 +595,7 @@ Select your method for detailed configuration: Example usage ============= -Here’s an example schema that we can use to show how you would use the +Here's an example schema that we can use to show how you would use the ``auth_token`` you get back from EdgeDB to make queries against a protected resource, in this case being able to insert a ``Post``. @@ -629,7 +629,7 @@ protected resource, in this case being able to insert a ``Post``. } } -Let’s now insert a ``Post``. +Let's now insert a ``Post``. .. lint-off diff --git a/docs/guides/tutorials/cloudflare_workers.rst b/docs/guides/tutorials/cloudflare_workers.rst index e3ed10df315..705ae3ab71a 100644 --- a/docs/guides/tutorials/cloudflare_workers.rst +++ b/docs/guides/tutorials/cloudflare_workers.rst @@ -87,7 +87,7 @@ It creates an :code:`edgedb.toml` config file and a schema file It also spins up an EdgeDB instance and associates it with the current directory. -As long as you’re inside the project directory, all CLI commands will +As long as you're inside the project directory, all CLI commands will be executed against this instance. You can run :code:`edgedb` in your terminal to open an diff --git a/docs/guides/tutorials/graphql_apis_with_strawberry.rst b/docs/guides/tutorials/graphql_apis_with_strawberry.rst index 1a2145d3e9a..aa32a608cbb 100644 --- a/docs/guides/tutorials/graphql_apis_with_strawberry.rst +++ b/docs/guides/tutorials/graphql_apis_with_strawberry.rst @@ -12,7 +12,7 @@ with the database. You can learn more about that in the :ref:`GraphQL ` section of the docs. However, as of now, EdgeDB is not ready to be used as a standalone backend. You -shouldn't expose your EdgeDB instance directly to the application’s frontend; +shouldn't expose your EdgeDB instance directly to the application's frontend; this is insecure and will give all users full read/write access to your database. So, in this tutorial, we'll see how you can quickly create a simple GraphQL API without using the built-in extension, which will give the users diff --git a/docs/guides/tutorials/rest_apis_with_fastapi.rst b/docs/guides/tutorials/rest_apis_with_fastapi.rst index 704cda74ba7..d1a12ea41a5 100644 --- a/docs/guides/tutorials/rest_apis_with_fastapi.rst +++ b/docs/guides/tutorials/rest_apis_with_fastapi.rst @@ -1274,7 +1274,7 @@ it with the following query: addresses before they can log in. If you use the Email and Password provider, in addition to the -``require_verification`` configuration, you’ll need to configure SMTP to allow +``require_verification`` configuration, you'll need to configure SMTP to allow EdgeDB to send email verification and password reset emails on your behalf. Here is an example of setting a local SMTP server, in this case using a diff --git a/docs/guides/tutorials/trpc.rst b/docs/guides/tutorials/trpc.rst index 54c060112df..4014defb623 100644 --- a/docs/guides/tutorials/trpc.rst +++ b/docs/guides/tutorials/trpc.rst @@ -41,7 +41,7 @@ Define the EdgeDB Schema The previous command generated a schema file in the ``dbschema`` directory. -Here’s an example schema that defines a ``User`` model: +Here's an example schema that defines a ``User`` model: .. code-block:: sdl :caption: dbschema/default.esdl @@ -124,7 +124,7 @@ install a wrapper around the `@tanstack/react-query ` or the `Beta 1 blog post `_, -which describes the design of the migration system. \ No newline at end of file +which describes the design of the migration system. From cd8076d55e7c1577b94f85c047ac8616dc3cb83c Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Thu, 13 Feb 2025 23:27:46 -0800 Subject: [PATCH 069/154] Massive (yet careful) rename EdgeDB -> Gel --- docs/ai/index.rst | 34 ++--- docs/ai/javascript.rst | 46 +++---- docs/ai/python.rst | 64 ++++----- docs/ai/reference.rst | 20 +-- docs/changelog/1_0_a2.rst | 10 +- docs/changelog/1_0_a4.rst | 2 +- docs/changelog/1_0_a6.rst | 2 +- docs/changelog/1_0_a7.rst | 4 +- docs/changelog/1_0_b1.rst | 2 +- docs/changelog/1_0_b2.rst | 6 +- docs/changelog/1_0_b3.rst | 2 +- docs/changelog/1_0_rc1.rst | 2 +- docs/changelog/1_0_rc3.rst | 2 +- docs/changelog/1_0_rc4.rst | 2 +- docs/changelog/1_0_rc5.rst | 2 +- docs/changelog/1_x.rst | 2 +- docs/changelog/2_x.rst | 4 +- docs/changelog/3_x.rst | 4 +- docs/changelog/4_x.rst | 4 +- docs/changelog/5_x.rst | 20 +-- docs/changelog/6_x.rst | 44 +++--- docs/cheatsheets/admin.rst | 2 +- docs/cheatsheets/cli.rst | 29 ++-- docs/cheatsheets/index.rst | 7 +- docs/cheatsheets/link_properties.rst | 26 ++-- docs/cli/edgedb.rst | 107 ++++++--------- docs/cli/edgedb_analyze.rst | 14 +- .../edgedb_branch/edgedb_branch_create.rst | 20 +-- docs/cli/edgedb_branch/edgedb_branch_drop.rst | 18 +-- docs/cli/edgedb_branch/edgedb_branch_list.rst | 15 +-- .../cli/edgedb_branch/edgedb_branch_merge.rst | 17 +-- .../edgedb_branch/edgedb_branch_rebase.rst | 19 +-- .../edgedb_branch/edgedb_branch_rename.rst | 21 +-- .../edgedb_branch/edgedb_branch_switch.rst | 15 +-- docs/cli/edgedb_branch/edgedb_branch_wipe.rst | 20 +-- docs/cli/edgedb_branch/index.rst | 15 +-- docs/cli/edgedb_cli_upgrade.rst | 10 +- docs/cli/edgedb_cloud/edgedb_cloud_login.rst | 20 ++- docs/cli/edgedb_cloud/edgedb_cloud_logout.rst | 16 +-- .../edgedb_cloud_secretkey_create.rst | 12 +- .../edgedb_cloud_secretkey_list.rst | 11 +- .../edgedb_cloud_secretkey_revoke.rst | 12 +- .../edgedb_cloud_secretkey/index.rst | 10 +- docs/cli/edgedb_cloud/index.rst | 23 ++-- docs/cli/edgedb_configure.rst | 20 +-- docs/cli/edgedb_connopts.rst | 22 +-- .../edgedb_database_create.rst | 16 +-- .../edgedb_database/edgedb_database_drop.rst | 18 +-- .../edgedb_database/edgedb_database_wipe.rst | 18 +-- docs/cli/edgedb_database/index.rst | 14 +- .../edgedb_describe_object.rst | 10 +- .../edgedb_describe_schema.rst | 10 +- docs/cli/edgedb_describe/index.rst | 8 +- docs/cli/edgedb_dump.rst | 10 +- docs/cli/edgedb_info.rst | 20 +-- .../edgedb_instance_create.rst | 54 ++++---- .../edgedb_instance_credentials.rst | 14 +- .../edgedb_instance_destroy.rst | 16 +-- .../edgedb_instance/edgedb_instance_link.rst | 18 +-- .../edgedb_instance/edgedb_instance_list.rst | 14 +- .../edgedb_instance/edgedb_instance_logs.rst | 16 +-- .../edgedb_instance_reset_password.rst | 18 +-- .../edgedb_instance_restart.rst | 18 +-- .../edgedb_instance_revert.rst | 16 +-- .../edgedb_instance/edgedb_instance_start.rst | 18 +-- .../edgedb_instance_status.rst | 14 +- .../edgedb_instance/edgedb_instance_stop.rst | 18 +-- .../edgedb_instance_unlink.rst | 12 +- .../edgedb_instance_upgrade.rst | 16 +-- docs/cli/edgedb_instance/index.rst | 12 +- docs/cli/edgedb_list.rst | 29 ++-- docs/cli/edgedb_migrate.rst | 14 +- .../edgedb_migration_apply.rst | 21 ++- .../edgedb_migration_create.rst | 12 +- .../edgedb_migration_edit.rst | 8 +- .../edgedb_migration_extract.rst | 8 +- .../edgedb_migration/edgedb_migration_log.rst | 10 +- .../edgedb_migration_status.rst | 10 +- .../edgedb_migration_upgrade_check.rst | 12 +- docs/cli/edgedb_migration/index.rst | 10 +- .../edgedb_project/edgedb_project_info.rst | 8 +- .../edgedb_project/edgedb_project_init.rst | 40 +++--- .../edgedb_project/edgedb_project_unlink.rst | 14 +- .../edgedb_project/edgedb_project_upgrade.rst | 14 +- docs/cli/edgedb_project/index.rst | 12 +- docs/cli/edgedb_query.rst | 10 +- docs/cli/edgedb_restore.rst | 16 +-- docs/cli/edgedb_server/edgedb_server_info.rst | 12 +- .../edgedb_server/edgedb_server_install.rst | 14 +- .../edgedb_server_list_versions.rst | 14 +- .../edgedb_server/edgedb_server_uninstall.rst | 14 +- docs/cli/edgedb_server/index.rst | 14 +- docs/cli/edgedb_ui.rst | 22 +-- docs/cli/edgedb_watch.rst | 20 ++- docs/cli/index.rst | 28 ++-- docs/cli/network.rst | 30 ++--- docs/clients/connection.rst | 26 ++-- docs/clients/go/api.rst | 50 +++---- docs/clients/go/codegen.rst | 4 +- docs/clients/go/index.rst | 125 +++++++++--------- docs/clients/go/types.rst | 14 +- docs/clients/graphql/graphql.rst | 2 +- docs/clients/graphql/index.rst | 30 ++--- docs/clients/graphql/introspection.rst | 4 +- docs/clients/http/index.rst | 24 ++-- docs/clients/js/driver.rst | 41 +++--- docs/clients/js/for.rst | 2 +- docs/clients/js/generation.rst | 26 ++-- docs/clients/js/group.rst | 4 - docs/clients/js/index.rst | 76 +++++------ docs/clients/js/insert.rst | 2 +- docs/clients/js/interfaces.rst | 32 ++--- docs/clients/js/literals.rst | 28 ++-- docs/clients/js/queries.rst | 54 ++++---- docs/clients/js/querybuilder.rst | 38 +++--- docs/clients/js/reference.rst | 83 ++++++------ docs/clients/js/select.rst | 4 +- docs/clients/js/types.rst | 2 +- docs/clients/python/api/advanced.rst | 58 ++++---- docs/clients/python/api/asyncio_client.rst | 48 +++---- docs/clients/python/api/blocking_client.rst | 46 +++---- docs/clients/python/api/codegen.rst | 22 +-- docs/clients/python/api/types.rst | 86 ++++++------ docs/clients/python/index.rst | 12 +- docs/clients/python/installation.rst | 8 +- docs/clients/python/requirements.txt | 2 - docs/clients/python/usage.rst | 40 +++--- docs/cloud/cli.rst | 8 +- docs/cloud/deploy/index.rst | 2 +- docs/cloud/deploy/netlify.rst | 10 +- docs/cloud/deploy/railway.rst | 8 +- docs/cloud/deploy/render.rst | 8 +- docs/cloud/deploy/vercel.rst | 6 +- docs/cloud/http_gql.rst | 12 +- docs/cloud/index.rst | 10 +- docs/cloud/web.rst | 16 +-- docs/datamodel/access_policies.rst | 44 +++--- docs/datamodel/comparison.rst | 4 +- docs/datamodel/computeds.rst | 19 +-- docs/datamodel/extensions.rst | 4 +- docs/datamodel/functions.rst | 2 +- docs/datamodel/future.rst | 4 +- docs/datamodel/index.rst | 22 ++- docs/datamodel/indexes.rst | 8 +- docs/datamodel/inheritance.rst | 4 +- docs/datamodel/introspection/casts.rst | 4 +- docs/datamodel/introspection/index.rst | 4 +- docs/datamodel/introspection/operators.rst | 2 +- docs/datamodel/links.rst | 2 +- docs/datamodel/objects.rst | 2 +- docs/datamodel/primitives.rst | 2 +- docs/datamodel/properties.rst | 2 +- docs/datamodel/triggers.rst | 6 +- docs/edgeql/for.rst | 8 +- docs/edgeql/index.rst | 12 +- docs/edgeql/literals.rst | 4 +- docs/edgeql/parameters.rst | 22 ++- docs/edgeql/path_resolution.rst | 38 +++--- docs/edgeql/paths.rst | 10 +- docs/edgeql/select.rst | 10 +- docs/edgeql/sets.rst | 10 +- docs/edgeql/transactions.rst | 4 +- docs/edgeql/types.rst | 4 +- docs/edgeql/with.rst | 2 +- docs/guides/auth/built_in_ui.rst | 18 +-- docs/guides/auth/email_password.rst | 6 +- docs/guides/auth/index.rst | 16 +-- docs/guides/auth/magic_link.rst | 12 +- docs/guides/auth/oauth.rst | 10 +- docs/guides/auth/webauthn.rst | 14 +- docs/guides/contributing/code.rst | 6 +- docs/guides/contributing/documentation.rst | 79 +++++------ docs/guides/contributing/index.rst | 2 +- docs/guides/datamigrations/index.rst | 8 +- docs/guides/datamigrations/postgres.rst | 32 ++--- docs/guides/deployment/aws_aurora_ecs.rst | 38 +++--- .../deployment/azure_flexibleserver.rst | 14 +- docs/guides/deployment/bare_metal.rst | 26 ++-- docs/guides/deployment/digitalocean.rst | 10 +- docs/guides/deployment/docker.rst | 12 +- docs/guides/deployment/fly_io.rst | 50 +++---- docs/guides/deployment/gcp.rst | 24 ++-- docs/guides/deployment/health_checks.rst | 2 +- docs/guides/deployment/heroku.rst | 22 +-- docs/guides/deployment/index.rst | 4 +- docs/guides/index.rst | 4 +- docs/guides/migrations/guide.rst | 62 ++++----- docs/guides/migrations/index.rst | 2 +- docs/guides/migrations/tips.rst | 4 +- docs/guides/tutorials/chatgpt_bot.rst | 78 +++++------ docs/guides/tutorials/cloudflare_workers.rst | 56 ++++---- .../graphql_apis_with_strawberry.rst | 28 ++-- docs/guides/tutorials/index.rst | 6 +- docs/guides/tutorials/jupyter_notebook.rst | 16 +-- docs/guides/tutorials/nextjs_app_router.rst | 78 +++++------ docs/guides/tutorials/nextjs_pages_router.rst | 58 ++++---- .../guides/tutorials/phoenix_github_oauth.rst | 70 +++++----- .../tutorials/rest_apis_with_fastapi.rst | 80 +++++------ .../guides/tutorials/rest_apis_with_flask.rst | 18 +-- docs/guides/tutorials/trpc.rst | 94 ++++++------- docs/index.rst | 4 +- docs/intro/branches.rst | 6 +- docs/intro/cli.rst | 22 +-- docs/intro/clients.rst | 56 ++++---- docs/intro/edgeql.rst | 4 +- docs/intro/index.rst | 36 ++--- docs/intro/instances.rst | 12 +- docs/intro/migrations.rst | 10 +- docs/intro/projects.rst | 36 ++--- docs/intro/quickstart.rst | 94 ++++++------- docs/intro/schema.rst | 12 +- docs/reference/admin/branches.rst | 6 +- docs/reference/admin/configure.rst | 6 +- docs/reference/admin/databases.rst | 8 +- docs/reference/admin/index.rst | 4 +- docs/reference/backend_ha.rst | 26 ++-- docs/reference/bindings/datetime.rst | 6 +- docs/reference/configuration.rst | 2 +- docs/reference/connection.rst | 38 +++--- docs/reference/ddl/future.rst | 8 +- docs/reference/ddl/index.rst | 4 +- docs/reference/ddl/migrations.rst | 6 +- docs/reference/edgedb_toml.rst | 20 +-- docs/reference/edgeql/cardinality.rst | 8 +- docs/reference/edgeql/casts.rst | 2 +- docs/reference/edgeql/group.rst | 4 - docs/reference/edgeql/insert.rst | 4 +- docs/reference/edgeql/shapes.rst | 4 +- docs/reference/edgeql/tx_commit.rst | 2 +- docs/reference/edgeql/tx_rollback.rst | 2 +- docs/reference/edgeql/tx_sp_declare.rst | 2 +- docs/reference/edgeql/tx_sp_release.rst | 2 +- docs/reference/edgeql/tx_sp_rollback.rst | 2 +- docs/reference/edgeql/tx_start.rst | 14 +- docs/reference/environment.rst | 24 ++-- docs/reference/http.rst | 18 +-- docs/reference/projects.rst | 46 +++---- docs/reference/protocol/dataformats.rst | 4 +- docs/reference/protocol/errors.rst | 6 +- docs/reference/protocol/index.rst | 16 +-- docs/reference/protocol/messages.rst | 2 +- docs/reference/protocol/typedesc.rst | 8 +- docs/reference/sdl/index.rst | 12 +- docs/reference/sdl/links.rst | 2 +- docs/reference/sdl/properties.rst | 2 +- docs/reference/sql_adapter.rst | 115 +++++++--------- docs/stdlib/array.rst | 4 +- docs/stdlib/bool.rst | 2 +- docs/stdlib/bytes.rst | 4 +- docs/stdlib/cfg.rst | 32 ++--- docs/stdlib/datetime.rst | 6 +- docs/stdlib/fts.rst | 9 +- docs/stdlib/json.rst | 10 +- docs/stdlib/net.rst | 2 +- docs/stdlib/numbers.rst | 2 +- docs/stdlib/objects.rst | 2 +- docs/stdlib/pg_trgm.rst | 4 +- docs/stdlib/pg_unaccent.rst | 4 +- docs/stdlib/pgcrypto.rst | 6 +- docs/stdlib/pgvector.rst | 4 +- docs/stdlib/range.rst | 2 +- docs/stdlib/set.rst | 15 +-- docs/stdlib/string.rst | 8 +- docs/stdlib/sys.rst | 4 +- docs/stdlib/tuple.rst | 10 +- docs/stdlib/type.rst | 2 +- edb/api/errors.txt | 2 +- edb/tools/docs/edb.py | 7 +- 268 files changed, 2314 insertions(+), 2538 deletions(-) delete mode 100644 docs/clients/python/requirements.txt diff --git a/docs/ai/index.rst b/docs/ai/index.rst index 3cc202e4a6c..b5f10e3cabb 100644 --- a/docs/ai/index.rst +++ b/docs/ai/index.rst @@ -14,7 +14,7 @@ AI :edb-alt-title: Using Gel AI -EdgeDB AI allows you to ship AI-enabled apps with practically no effort. It +|Gel| AI allows you to ship AI-enabled apps with practically no effort. It automatically generates embeddings for your data. Works with OpenAI, Mistral AI, Anthropic, and any other provider with a compatible API. @@ -22,7 +22,7 @@ AI, Anthropic, and any other provider with a compatible API. Enable extension in your schema =============================== -AI is an EdgeDB extension. To enable it, you will need to add the extension +AI is an |Gel| extension. To enable it, you will need to add the extension to your app's schema: .. code-block:: sdl @@ -34,12 +34,12 @@ Extension configuration ======================= The AI extension may be configured via our UI or via EdgeQL. To use the -built-in UI, access it by running ``edgedb ui``. If you have the extension +built-in UI, access it by running ``gel ui``. If you have the extension enabled in your schema as shown above and have migrated that schema change, you will see the "AI Admin" icon in the left-hand toolbar. .. image:: images/ui-ai.png - :alt: The EdgeDB local development server UI highlighting the AI admin + :alt: The Gel local development server UI highlighting the AI admin icon in the left-hand toolbar. The icon is two stars, one larger and one smaller, the smaller being a light pink color and the larger being a light blue when selected. @@ -69,7 +69,7 @@ Provider" button, selecting the appropriate API, and pasting your key in the "Secret" field. .. image:: images/ui-ai-add-provider.png - :alt: The "Add Provider" form of the EdgeDB local development server UI. + :alt: The "Add Provider" form of the Gel local development server UI. On the left, the sidebar navigation for the view showing Playground, Prompts, and Providers options, with Provider selected (indicated with a purple border on the left). The main content area shows a @@ -104,13 +104,13 @@ We have provider config types for each of the three supported APIs: Usage ===== -Using EdgeDB AI requires some changes to your schema. +Using |Gel| AI requires some changes to your schema. Add an index ------------ -To start using EdgeDB AI on a type, create an index: +To start using |Gel| AI on a type, create an index: .. code-block:: sdl-diff @@ -154,13 +154,13 @@ can define an AI index on an expression: .. note:: When AI indexes aren't working… If you find your queries are not returning the expected results, try - inspecting your instance logs. On an EdgeDB Cloud instance, use the "Logs" + inspecting your instance logs. On an |Gel| Cloud instance, use the "Logs" tab in your instance dashboard. On local or :ref:`CLI-linked remote - instances `, use ``edgedb instance logs -I + instances `, use ``gel instance logs -I ``. You may find the problem there. Providers impose rate limits on their APIs which can often be the source of - AI index problems. If index creation hits a rate limit, EdgeDB will wait + AI index problems. If index creation hits a rate limit, |Gel| will wait the ``indexer_naptime`` (see the docs on :ref:`ext::ai configuration `) and resume index creation. @@ -209,13 +209,13 @@ Use RAG via HTTP ---------------- By making an HTTP request to -``https://:/branch//ai/rag``, you can generate +``https://:/branch//ai/rag``, you can generate text via the generative AI API of your choice within the context of a type with a deferred embedding index. .. note:: - Making HTTP requests to EdgeDB requires :ref:`authentication + Making HTTP requests to |Gel| requires :ref:`authentication `. .. code-block:: bash @@ -224,7 +224,7 @@ a deferred embedding index. "query": "What color is the sky on Mars?", "model": "gpt-4-turbo-preview", "context": {"query":"select Astronomy"} - }' https://:/branch//ai/rag + }' https://:/branch//ai/rag {"response": "The sky on Mars is red."} Since LLMs are often slow, it may be useful to stream the response. To do this, @@ -243,14 +243,14 @@ add ``"stream": true`` to your request JSON. Use RAG via JavaScript ---------------------- -``@edgedb/ai`` offers a convenient wrapper around ``ext::ai``. Install it with -``npm install @edgedb/ai`` (or via your package manager of choice) and +``@gel/ai`` offers a convenient wrapper around ``ext::ai``. Install it with +``npm install @gel/ai`` (or via your package manager of choice) and implement it like this example: .. code-block:: typescript - import { createClient } from "edgedb"; - import { createAI } from "@edgedb/ai"; + import { createClient } from "gel"; + import { createAI } from "@gel/ai"; const client = createClient(); diff --git a/docs/ai/javascript.rst b/docs/ai/javascript.rst index d29540136da..1adbdfe9123 100644 --- a/docs/ai/javascript.rst +++ b/docs/ai/javascript.rst @@ -6,30 +6,30 @@ JavaScript :edb-alt-title: Gel AI's JavaScript package -``@edgedb/ai`` offers a convenient wrapper around ``ext::ai``. Install it with +``@gel/ai`` offers a convenient wrapper around ``ext::ai``. Install it with npm or via your package manager of choice: .. code-block:: bash - $ npm install @edgedb/ai # or - $ yarn add @edgedb/ai # or - $ pnpm add @edgedb/ai # or - $ bun add @edgedb/ai + $ npm install @gel/ai # or + $ yarn add @gel/ai # or + $ pnpm add @gel/ai # or + $ bun add @gel/ai Usage ===== -Start by importing ``createClient`` from ``edgedb`` and ``createAI`` from -``@edgedb/ai``: +Start by importing ``createClient`` from ``gel`` and ``createAI`` from +``@gel/ai``: .. code-block:: typescript - import { createClient } from "edgedb"; - import { createAI } from "@edgedb/ai"; + import { createClient } from "gel"; + import { createAI } from "@gel/ai"; -Create an EdgeDB client. Create an instance of the AI client by passing in the -EdgeDB client and any options for the AI provider (like the text generation +Create an |Gel| client. Create an instance of the AI client by passing in the +Gel client and any options for the AI provider (like the text generation model): .. code-block:: typescript @@ -93,16 +93,16 @@ API Reference .. js:function:: createAI( \ client: Client, \ options: Partial = {} \ - ): EdgeDBAI + ): GelAI - Creates an instance of ``EdgeDBAI`` with the specified client and options. + Creates an instance of ``GelAI`` with the specified client and options. :param client: - An EdgeDB client instance. + A |Gel| client instance. :param string options.model: Required. Specifies the AI model to use. This could be a version of GPT - or any other model supported by EdgeDB AI. + or any other model supported by |Gel| AI. :param options.prompt: Optional. Defines the input prompt for the AI model. The prompt can be @@ -111,22 +111,22 @@ API Reference interactions. The default is the built-in system prompt. -EdgeDBAI --------- +GelAI +----- -Instances of ``EdgeDBAI`` offer methods for client configuration and utilizing +Instances of ``GelAI`` offer methods for client configuration and utilizing RAG. Public methods ^^^^^^^^^^^^^^ -.. js:method:: withConfig(options: Partial): EdgeDBAI +.. js:method:: withConfig(options: Partial): GelAI - Returns a new ``EdgeDBAI`` instance with updated configuration options. + Returns a new ``GelAI`` instance with updated configuration options. :param string options.model: Required. Specifies the AI model to use. This could be a version of GPT - or any other model supported by EdgeDB AI. + or any other model supported by |Gel| AI. :param options.prompt: Optional. Defines the input prompt for the AI model. The prompt can be @@ -134,9 +134,9 @@ Public methods structure that includes roles and content for more complex interactions. The default is the built-in system prompt. -.. js:method:: withContext(context: Partial): EdgeDBAI +.. js:method:: withContext(context: Partial): GelAI - Returns a new ``EdgeDBAI`` instance with an updated query context. + Returns a new ``GelAI`` instance with an updated query context. :param string context.query: Required. Specifies an expression to determine the relevant objects and diff --git a/docs/ai/python.rst b/docs/ai/python.rst index 2bd6033cc5b..19d0af9a791 100644 --- a/docs/ai/python.rst +++ b/docs/ai/python.rst @@ -6,37 +6,37 @@ Python :edb-alt-title: Gel AI's Python package -The ``edgedb.ai`` package is an optional binding of the AI extension in EdgeDB. -To use the AI binding, you need to install ``edgedb-python`` with the ``ai`` -extra dependencies: +The ``gel.ai`` package is an optional binding of the AI extension in |Gel|. +To use the AI binding, you need to install ``gel`` Python package with the +``ai`` extra dependencies: .. code-block:: bash - $ pip install 'edgedb[ai]' + $ pip install 'gel[ai]' Usage ===== -Start by importing ``edgedb`` and ``edgedb.ai``: +Start by importing ``gel`` and ``gel.ai``: .. code-block:: python - import edgedb - import edgedb.ai + import gel + import gel.ai Blocking -------- -The AI binding is built on top of the regular EdgeDB client objects, providing +The AI binding is built on top of the regular |Gel| client objects, providing both blocking and asynchronous versions of its API. For example, a blocking AI client is initialized like this: .. code-block:: python - client = edgedb.create_client() - gpt4ai = edgedb.ai.create_ai( + client = gel.create_client() + gpt4ai = gel.ai.create_ai( client, model="gpt-4-turbo-preview" ) @@ -76,12 +76,12 @@ To use an async client instead, do this: .. code-block:: python - import asyncio # alongside the EdgeDB imports + import asyncio # alongside the Gel imports - client = edgedb.create_async_client() + client = gel.create_async_client() async def main(): - gpt4ai = await edgedb.ai.create_async_ai( + gpt4ai = await gel.ai.create_async_ai( client, model="gpt-4-turbo-preview" ) @@ -103,15 +103,15 @@ To use an async client instead, do this: API reference ============= -.. py:function:: create_ai(client, **kwargs) -> EdgeDBAI +.. py:function:: create_ai(client, **kwargs) -> GelAI - Creates an instance of ``EdgeDBAI`` with the specified client and options. + Creates an instance of ``GelAI`` with the specified client and options. This function ensures that the client is connected before initializing the AI with the specified options. :param client: - An EdgeDB client instance. + A |Gel| client instance. :param kwargs: Keyword arguments that are passed to the ``AIOptions`` data class to @@ -122,15 +122,15 @@ API reference ``None`` will result in the client using the default prompt. (default: ``None``) -.. py:function:: create_async_ai(client, **kwargs) -> AsyncEdgeDBAI +.. py:function:: create_async_ai(client, **kwargs) -> AsyncGelAI - Creates an instance of ``AsyncEdgeDBAI`` w/ the specified client & options. + Creates an instance of ``AsyncGelAI`` w/ the specified client & options. This function ensures that the client is connected asynchronously before initializing the AI with the specified options. :param client: - An asynchronous EdgeDB client instance. + An asynchronous |Gel| client instance. :param kwargs: Keyword arguments that are passed to the ``AIOptions`` data class to @@ -144,12 +144,12 @@ AI client classes ----------------- -BaseEdgeDBAI -^^^^^^^^^^^^ +BaseGelAI +^^^^^^^^^ -.. py:class:: BaseEdgeDBAI +.. py:class:: BaseGelAI - The base class for EdgeDB AI clients. + The base class for |Gel| AI clients. This class handles the initialization and configuration of AI clients and provides methods to modify their configuration and context dynamically. @@ -168,7 +168,7 @@ BaseEdgeDBAI A placeholder for the client class, should be implemented by subclasses. :param client: - An instance of EdgeDB client, which could be either a synchronous or + An instance of |Gel| client, which could be either a synchronous or asynchronous client. :param options: @@ -210,12 +210,12 @@ BaseEdgeDBAI objects returned by the query. -EdgeDBAI -^^^^^^^^ +GelAI +^^^^^ -.. py:class:: EdgeDBAI +.. py:class:: GelAI - A synchronous class for creating EdgeDB AI clients. + A synchronous class for creating |Gel| AI clients. This class provides methods to send queries and receive responses using both blocking and streaming communication modes synchronously. @@ -254,12 +254,12 @@ EdgeDBAI instance. -AsyncEdgeDBAI -^^^^^^^^^^^^^ +AsyncGelAI +^^^^^^^^^^ -.. py:class:: AsyncEdgeDBAI +.. py:class:: AsyncGelAI - An asynchronous class for creating EdgeDB AI clients. + An asynchronous class for creating |Gel| AI clients. This class provides methods to send queries and receive responses using both blocking and streaming communication modes asynchronously. diff --git a/docs/ai/reference.rst b/docs/ai/reference.rst index 0fa10daab46..9d66c5cf564 100644 --- a/docs/ai/reference.rst +++ b/docs/ai/reference.rst @@ -4,7 +4,7 @@ ext::ai ======= -To activate EdgeDB AI functionality, you can use the :ref:`extension +To activate |Gel| AI functionality, you can use the :ref:`extension ` mechanism: .. code-block:: sdl @@ -184,13 +184,13 @@ When indexes aren't working… ---------------------------- If you find your queries are not returning the expected results, try -inspecting your instance logs. On an EdgeDB Cloud instance, use the "Logs" +inspecting your instance logs. On an |Gel| Cloud instance, use the "Logs" tab in your instance dashboard. On local or :ref:`CLI-linked remote -instances `, use ``edgedb instance logs -I +instances `, use ``gel instance logs -I ``. You may find the problem there. Providers impose rate limits on their APIs which can often be the source of -AI index problems. If index creation hits a rate limit, EdgeDB will wait +AI index problems. If index creation hits a rate limit, Gel will wait the ``indexer_naptime`` (see the docs on :ref:`ext::ai configuration `) and resume index creation. @@ -296,7 +296,7 @@ Functions .. note:: The ``query`` argument should *not* be a textual query but the - embeddings generated *from* a textual query. To have EdgeDB generate + embeddings generated *from* a textual query. To have |Gel| generate the query for you along with a text response, try :ref:`our built-in RAG `. @@ -328,16 +328,16 @@ using your AI indexes or to generate embeddings against a model of your choice. .. note:: - All EdgeDB server HTTP endpoints require :ref:`authentication + All |Gel| server HTTP endpoints require :ref:`authentication `. By default, you may use `HTTP Basic Authentication `_ - with your EdgeDB username and password. + with your Gel username and password. RAG --- -``POST``: ``https://:/branch//ai/rag`` +``POST``: ``https://:/branch//ai/rag`` Responds with text generated by the specified text generation model in response to the provided query. @@ -425,7 +425,7 @@ these properties: "query": "What color is the sky on Mars?", "model": "gpt-4-turbo-preview", "context": {"query":"Knowledge"} - }' http://:/branch/main/ai/rag + }' http://:/branch/main/ai/rag Response @@ -596,7 +596,7 @@ stream. Embeddings ---------- -``POST``: ``https://:/branch//ai/embeddings`` +``POST``: ``https://:/branch//ai/embeddings`` Responds with embeddings generated by the specified embeddings model in response to the provided input. diff --git a/docs/changelog/1_0_a2.rst b/docs/changelog/1_0_a2.rst index 2b15f77c5ce..1758a751f7b 100644 --- a/docs/changelog/1_0_a2.rst +++ b/docs/changelog/1_0_a2.rst @@ -13,7 +13,7 @@ This changelog summarizes new features and breaking changes in New JavaScript Driver ===================== -EdgeDB has a new high-performance native +|EdgeDB| has a new high-performance native `EdgeDB driver `_ for NodeJS 10+. The driver is written in strict TypeScript, thoroughly tested, and has @@ -147,8 +147,8 @@ Dump / Restore The new :ref:`edgedb dump ` and :ref:`edgedb restore ` commands can be used to -safely dump and restore EdgeDB databases, including when upgrading to new -versions of EdgeDB. +safely dump and restore |EdgeDB| databases, including when upgrading to new +versions of |EdgeDB|. EdgeQL @@ -339,7 +339,7 @@ Generic Describe ---------------- The new :eql:stmt:`describe` introspection command can generate DDL, -SDL, or a descriptive text summary of any schema object in EdgeDB. A +SDL, or a descriptive text summary of any schema object in |EdgeDB|. A few examples: .. code-block:: edgeql-repl @@ -456,7 +456,7 @@ Server Postgres 12 ----------- -EdgeDB is now based on PostgreSQL 12. +|EdgeDB| is now based on PostgreSQL 12. Other Fixes and Enhancements ---------------------------- diff --git a/docs/changelog/1_0_a4.rst b/docs/changelog/1_0_a4.rst index 22f7d5637e4..0ad3e32b3ac 100644 --- a/docs/changelog/1_0_a4.rst +++ b/docs/changelog/1_0_a4.rst @@ -40,7 +40,7 @@ CLI === * We have a new ``edgedb server`` group of commands that ships with - the default EdgeDB CLI. Check out the details in this `RFC 1001 + the default |EdgeDB| CLI. Check out the details in this `RFC 1001 `_. * The ``edgedb`` REPL prompt now has a transaction indicator: diff --git a/docs/changelog/1_0_a6.rst b/docs/changelog/1_0_a6.rst index fa0b95c6506..0d20058ab69 100644 --- a/docs/changelog/1_0_a6.rst +++ b/docs/changelog/1_0_a6.rst @@ -7,7 +7,7 @@ =========== This changelog summarizes new features and breaking changes in -EdgeDB 1.0 alpha 6 "Wolf". +|EdgeDB| 1.0 alpha 6 "Wolf". EdgeQL diff --git a/docs/changelog/1_0_a7.rst b/docs/changelog/1_0_a7.rst index 62a73879ad0..3f808d04064 100644 --- a/docs/changelog/1_0_a7.rst +++ b/docs/changelog/1_0_a7.rst @@ -108,7 +108,7 @@ with a default value: ... }; We use :eql:stmt:`describe current migration as json ` to see what EdgeDB is proposing. The JSON format makes it +migration>` to see what |EdgeDB| is proposing. The JSON format makes it easier to potentially integrate this with other tools. For this example it's worth turning on ``json`` output mode for edgedb REPL: @@ -196,7 +196,7 @@ Command-Line Tools * Default user and default database are now simply ``edgedb`` and no longer named after the system user. -* Add ``--connect-timeout`` to control how long to wait for EdgeDB +* Add ``--connect-timeout`` to control how long to wait for |EdgeDB| response (`#191 `_). * Add ``--dsn`` as a connection option (`#176 `_). diff --git a/docs/changelog/1_0_b1.rst b/docs/changelog/1_0_b1.rst index b1d7ebebee6..be31a5b576d 100644 --- a/docs/changelog/1_0_b1.rst +++ b/docs/changelog/1_0_b1.rst @@ -7,7 +7,7 @@ ========== This changelog summarizes new features and breaking changes in -EdgeDB 1.0 beta 1 "Sirius". +|EdgeDB| 1.0 beta 1 "Sirius". Migrations diff --git a/docs/changelog/1_0_b2.rst b/docs/changelog/1_0_b2.rst index 51871a8c725..6f5f8135c12 100644 --- a/docs/changelog/1_0_b2.rst +++ b/docs/changelog/1_0_b2.rst @@ -7,7 +7,7 @@ ========== This changelog summarizes new features and breaking changes in -EdgeDB 1.0 beta 2 "Luyten". +|EdgeDB| 1.0 beta 2 "Luyten". Migrations @@ -59,7 +59,7 @@ EdgeQL * Drop the deprecated ``Port``. The more general :ref:`extension ` mechanism introduced in - EdgeDB 1.0 beta 1 should be used (:eql:gh:`#2262`). + |EdgeDB| 1.0 beta 1 should be used (:eql:gh:`#2262`). * Reduce the maximum length for names of databases and roles to 51 characters (:eql:gh:`#2465`). * Enable ``br`` (or ``rb``) as a valid bytes literal prefix @@ -109,7 +109,7 @@ GraphQL Command-Line Tools ================== -We've added ``edgedb project init`` command to help manage EdgeDB +We've added ``edgedb project init`` command to help manage |EdgeDB| credentials for your project. Running this in a new project directory will setup an EdgeDB instance, create a schema and migrations directory and link the credentials for that instance to the project diff --git a/docs/changelog/1_0_b3.rst b/docs/changelog/1_0_b3.rst index 3b199886396..c948b5ce03c 100644 --- a/docs/changelog/1_0_b3.rst +++ b/docs/changelog/1_0_b3.rst @@ -7,7 +7,7 @@ ========== This changelog summarizes new features and breaking changes in -EdgeDB 1.0 beta 3 "Ross". +|EdgeDB| 1.0 beta 3 "Ross". Migrations diff --git a/docs/changelog/1_0_rc1.rst b/docs/changelog/1_0_rc1.rst index c4cc65449bd..8d25fc75118 100644 --- a/docs/changelog/1_0_rc1.rst +++ b/docs/changelog/1_0_rc1.rst @@ -7,7 +7,7 @@ ======== This changelog summarizes new features and breaking changes in -EdgeDB 1.0 Release Candidate 1 "Epsilon Eridani". +|EdgeDB| 1.0 Release Candidate 1 "Epsilon Eridani". Migrations diff --git a/docs/changelog/1_0_rc3.rst b/docs/changelog/1_0_rc3.rst index b9fc2a45f72..fe524772379 100644 --- a/docs/changelog/1_0_rc3.rst +++ b/docs/changelog/1_0_rc3.rst @@ -6,7 +6,7 @@ 1.0 RC 3 ======== -This changelog summarizes changes and bugfixes in EdgeDB 1.0 Release +This changelog summarizes changes and bugfixes in |EdgeDB| 1.0 Release Candidate 3 "Cygni". This release is focusing on fixing existing issues rather than introducing new features. diff --git a/docs/changelog/1_0_rc4.rst b/docs/changelog/1_0_rc4.rst index 43de120710d..66988ed925a 100644 --- a/docs/changelog/1_0_rc4.rst +++ b/docs/changelog/1_0_rc4.rst @@ -6,7 +6,7 @@ 1.0 RC 4 ======== -This changelog summarizes changes and bugfixes in EdgeDB 1.0 Release +This changelog summarizes changes and bugfixes in |EdgeDB| 1.0 Release Candidate 4 "Procyon". This release is focusing on fixing existing issues rather than introducing new features. diff --git a/docs/changelog/1_0_rc5.rst b/docs/changelog/1_0_rc5.rst index a5db292dbcd..2b531b99f96 100644 --- a/docs/changelog/1_0_rc5.rst +++ b/docs/changelog/1_0_rc5.rst @@ -6,7 +6,7 @@ 1.0 RC 5 ======== -This changelog summarizes changes and bugfixes in EdgeDB 1.0 Release +This changelog summarizes changes and bugfixes in |EdgeDB| 1.0 Release Candidate 5 "Tau Ceti". Compared to other releases this is a very small one as we're closing in on the stable version. diff --git a/docs/changelog/1_x.rst b/docs/changelog/1_x.rst index 41de57062a7..f0d3328ea72 100644 --- a/docs/changelog/1_x.rst +++ b/docs/changelog/1_x.rst @@ -2,7 +2,7 @@ v1.0 ==== -:edb-alt-title: Gel v1 (Nova) +:edb-alt-title: EdgeDB v1 (Nova) .. image:: images/v1_nova.jpg :width: 100% diff --git a/docs/changelog/2_x.rst b/docs/changelog/2_x.rst index 7e0fc4adb94..95c5160041d 100644 --- a/docs/changelog/2_x.rst +++ b/docs/changelog/2_x.rst @@ -2,12 +2,12 @@ v2.0 ==== -:edb-alt-title: Gel v2 (Sagittarius) +:edb-alt-title: EdgeDB v2 (Sagittarius) .. image:: images/v2_sagittarius.jpg :width: 100% -EdgeDB 2.0 was released on July 28th, 2022. Read the announcement +|EdgeDB| 2.0 was released on July 28th, 2022. Read the announcement blog post `here `_. We would like to thank our community for reporting issues and contributing diff --git a/docs/changelog/3_x.rst b/docs/changelog/3_x.rst index b2cfcd98065..7079ec449cd 100644 --- a/docs/changelog/3_x.rst +++ b/docs/changelog/3_x.rst @@ -2,12 +2,12 @@ v3.0 ==== -:edb-alt-title: Gel v3 +:edb-alt-title: EdgeDB v3 .. image:: images/v3_betelgeuse.jpg :width: 100% -EdgeDB 3.0 was released on June 22nd, 2023. Read the announcement +|EdgeDB| 3.0 was released on June 22nd, 2023. Read the announcement blog post `here `_. To play with the new features, install the CLI using `our installation guide diff --git a/docs/changelog/4_x.rst b/docs/changelog/4_x.rst index e50b4552045..ccf925ed792 100644 --- a/docs/changelog/4_x.rst +++ b/docs/changelog/4_x.rst @@ -2,10 +2,10 @@ v4.0 ==== -:edb-alt-title: Gel v4 +:edb-alt-title: EdgeDB v4 This release cycle is much shorter than the previous ones. It reflects our new -approach at EdgeDB where the goal is to provide improvements at a steady +approach at |EdgeDB| where the goal is to provide improvements at a steady regular pace rather than in big, but infrequent batches. Going forward we expect to maintain this shorter release cadence focusing on a few features at a time. diff --git a/docs/changelog/5_x.rst b/docs/changelog/5_x.rst index 2d0ebd0de08..34e2f0d9667 100644 --- a/docs/changelog/5_x.rst +++ b/docs/changelog/5_x.rst @@ -2,7 +2,7 @@ v5.0 ==== -:edb-alt-title: Gel v5 +:edb-alt-title: EdgeDB v5 To play with the new features, make sure to specify version 5.0 when initializing the project as pre-release versions are not considered stable @@ -32,7 +32,7 @@ Alternatively, specify an instance name if you aren't using a project. $ edgedb instance upgrade -I my_instance -The CLI will first check to see if your schema will migrate cleanly to EdgeDB +The CLI will first check to see if your schema will migrate cleanly to |EdgeDB| 5.0. If the upgrade check finds any problems, it will report them back to you. **Hosted instances** @@ -40,14 +40,14 @@ The CLI will first check to see if your schema will migrate cleanly to EdgeDB To upgrade a remote (hosted) instance, we recommend the following dump-and-restore process. -1. EdgeDB v5.0 supports PostgreSQL 14 (or above). So check the version of +1. |EdgeDB| v5.0 supports PostgreSQL 14 (or above). So check the version of PostgreSQL you are using before upgrading EdgeDB. If you're using Postgres 13 or below, you should upgrade Postgres first. 2. Spin up an empty 5.0 instance. You can use one of our :ref:`deployment guides `. - Under Debian/Ubuntu, when adding the EdgeDB package repository, use this + Under Debian/Ubuntu, when adding the |EdgeDB| package repository, use this command instead: .. code-block:: bash @@ -102,11 +102,11 @@ New features EdgeDB + AI ----------- -We've added an ``ext::ai`` extension for handling the integration of EdgeDB +We've added an ``ext::ai`` extension for handling the integration of |EdgeDB| with various AI backends such as: OpenAI, Mistral and Anthropic. There is a special ``ext::ai::index`` that can be used to delegate the search -functionality of EdgeDB objects to a specific AI search provider. +functionality of |EdgeDB| objects to a specific AI search provider. The function ``ext::ai::to_context(object: anyobject)`` evaluates the expression of the specific ``ext::ai::index`` defined on the passed object @@ -124,7 +124,7 @@ There are also two HTTP API points for interacting with the data: EdgeDB branches --------------- -EdgeDB 5.0 adds branching functionality in order to help bridge the gap between +|EdgeDB| 5.0 adds branching functionality in order to help bridge the gap between the code (and schema) managed by version control systems and the actual development database. @@ -167,7 +167,7 @@ With these new commands, here's how we envision developers using them to manage "feature" branches: 1) Create a new "feature" VCS branch (a clone of the "main" branch) and a - corresponding "feature" EdgeDB branch. + corresponding "feature" |EdgeDB| branch. 2) Work on the "feature" branch, add migrations, etc. @@ -181,7 +181,7 @@ manage "feature" branches: 5) Then we want to rebase the "feature" branch code on top of the "main" branch code. -6) After that we need to replicate the same rebase operation with the EdgeDB +6) After that we need to replicate the same rebase operation with the |EdgeDB| branch. Our CLI tools may need to first clone the "main" branch with the data into a "temp" branch. Then we can introspect the migration histories of the "temp" and "feature" branches so that we can establish where they @@ -196,7 +196,7 @@ manage "feature" branches: to "main", if the old branch is no longer needed). We've added :ref:`edgedb branch commands ` to our CLI -as well that create, copy, rename, drop, and rebase EdgeDB branches. +as well that create, copy, rename, drop, and rebase |EdgeDB| branches. Updated pgvector extension diff --git a/docs/changelog/6_x.rst b/docs/changelog/6_x.rst index 27a0e9caeb5..6a8c3060392 100644 --- a/docs/changelog/6_x.rst +++ b/docs/changelog/6_x.rst @@ -10,7 +10,7 @@ automatically suggested: .. code-block:: bash - $ edgedb project init --server-version 6.0-beta.1 + $ gel project init --server-version 6.0-beta.1 Upgrading @@ -21,20 +21,20 @@ Upgrading **Local instances** To upgrade a local project, first ensure that your CLI is up to date with -``edgedb cli upgrade``. Then run the following command inside the project +``gel cli upgrade``. Then run the following command inside the project directory. .. code-block:: bash - $ edgedb project upgrade --to-testing + $ gel project upgrade --to-testing Alternatively, specify an instance name if you aren't using a project: .. code-block:: bash - $ edgedb instance upgrade -I my_instance + $ gel instance upgrade -I my_instance -The CLI will check if your schema can migrate cleanly to EdgeDB 6.0. If any +The CLI will check if your schema can migrate cleanly to Gel 6.0. If any issues are found, they will be reported. **Hosted instances** @@ -42,32 +42,32 @@ issues are found, they will be reported. To upgrade a remote instance, we recommend the following dump-and-restore process: -1. EdgeDB v6.0 supports PostgreSQL 14 or above. Verify your PostgreSQL version - before upgrading EdgeDB. If you're using Postgres 13 or below, upgrade +1. Gel v6.0 supports PostgreSQL 14 or above. Verify your PostgreSQL version + before upgrading Gel. If you're using Postgres 13 or below, upgrade Postgres first. 2. Spin up an empty 6.0 instance. You can use one of our :ref:`deployment guides `. - For Debian/Ubuntu, when adding the EdgeDB package repository, use this + For Debian/Ubuntu, when adding the Gel package repository, use this command: .. code-block:: bash - $ echo deb [signed-by=/usr/local/share/keyrings/edgedb-keyring.gpg] \ - https://packages.edgedb.com/apt \ + $ echo deb [signed-by=/usr/local/share/keyrings/gel-keyring.gpg] \ + https://packages.geldata.com/apt \ $(grep "VERSION_CODENAME=" /etc/os-release | cut -d= -f2) main \ - | sudo tee /etc/apt/sources.list.d/edgedb.list - $ sudo apt-get update && sudo apt-get install edgedb-6 + | sudo tee /etc/apt/sources.list.d/gel.list + $ sudo apt-get update && sudo apt-get install gel-6 For CentOS/RHEL, use this installation command: .. code-block:: bash - $ sudo yum install edgedb-6 + $ sudo yum install gel-6 - In any required ``systemctl`` commands, replace ``edgedb-server-6`` with - ``edgedb-server-6``. + In any required ``systemctl`` commands, replace ``edgedb-server-5`` with + ``gel-server-6``. For Docker setups, use the ``6.0`` tag. @@ -75,7 +75,7 @@ process: .. code-block:: bash - $ edgedb dump --dsn --all --format dir my_database.dump/ + $ gel dump --dsn --all --format dir my_database.dump/ This will dump the schema and contents of your current database to a directory on your local disk called ``my_database.dump``. The directory name @@ -85,7 +85,7 @@ process: .. code-block:: bash - $ edgedb restore --all my_database.dump/ --dsn + $ gel restore --all my_database.dump/ --dsn Once the restore is complete, update your application to connect to the new instance. @@ -100,15 +100,15 @@ SQL write support ----------------- You can now use SQL DML (``insert``, ``update``, ``delete``) when connecting to -your EdgeDB instance via the PostgreSQL protocol. Our aim is to support most +your |Gel| instance via the PostgreSQL protocol. Our aim is to support most typical use cases from tools like SQL ORMs and SQL clients. -This allows more developers to use EdgeDB, leveraging our advanced data model, +This allows more developers to use Gel, leveraging our advanced data model, tooling, and high-performance connection management. Teams can migrate their -existing SQL codebases to EdgeDB without rewriting their queries. Once adopted, +existing SQL codebases to Gel without rewriting their queries. Once adopted, you can gradually take advantage of EdgeQL's powerful query capabilities. -Existing EdgeDB users who already use EdgeQL can benefit too. While some SQL +Existing Gel users who already use EdgeQL can benefit too. While some SQL features like window functions, recursive queries, and explicit locking are not yet supported, you can use these features in SQL today. We will continue to add support for more features in the future. @@ -213,7 +213,7 @@ Simpler scoping rules --------------------- We've simplified the scoping rules for queries. See `our RFC 1027 outlining the -changes `_. +changes `_. The RFC highlights two main reasons for removing path factoring: the need to simplify and enhance the language, and concerns about implementation. Path diff --git a/docs/cheatsheets/admin.rst b/docs/cheatsheets/admin.rst index 2440a547d6f..068f6b28a82 100644 --- a/docs/cheatsheets/admin.rst +++ b/docs/cheatsheets/admin.rst @@ -103,4 +103,4 @@ Run a script from command line: .. cli:synopsis:: - cat myscript.edgeql | edgedb [...] + cat myscript.edgeql | gel [...] diff --git a/docs/cheatsheets/cli.rst b/docs/cheatsheets/cli.rst index 65c9213c875..168e2d2ddfb 100644 --- a/docs/cheatsheets/cli.rst +++ b/docs/cheatsheets/cli.rst @@ -7,7 +7,7 @@ To initialize a new project: .. code-block:: bash - $ edgedb project init + $ gel project init If an :ref:`ref_reference_edgedb_toml` file exists in the current directory, it will initialize a new project according to the settings defined in it. @@ -24,11 +24,11 @@ command. ---------- -Explicitly create a new EdgeDB instance ``my_instance``: +Explicitly create a new |Gel| instance ``my_instance``: .. code-block:: bash - $ edgedb instance create my_instance + $ gel instance create my_instance ---------- @@ -38,7 +38,7 @@ Create a branch: .. code-block:: bash - $ edgedb branch create feature + $ gel branch create feature OK: CREATE @@ -49,7 +49,7 @@ Configure passwordless access (such as to a local development database): .. code-block:: bash - $ edgedb configure insert Auth \ + $ gel configure insert Auth \ > --comment 'passwordless access' \ > --priority 1 \ > --method Trust @@ -63,7 +63,7 @@ Configure access that checks password (with a higher priority): .. code-block:: bash - $ edgedb configure insert Auth \ + $ gel configure insert Auth \ > --comment 'password is required' \ > --priority 0 \ > --method SCRAM @@ -77,11 +77,16 @@ Connect to the default project branch: .. code-block:: bash - $ edgedb - EdgeDB 1.0-beta.2+ga7130d5c7.cv202104290000 (repl 1.0.0-beta.2) + $ gel + ▄██▄ + ▄▄▄▄▄ ▄▄▄ ████ + ▄███████▄ ▄███████▄ ████ + ▀███████▀ ▀███▀▀▀▀▀ ████ + ▀▀▀▀▀ ▀▀▀ ▀▀ + ▀▄▄▄▄▄▀ + ▀▀▀ + Gel 6.0-rc.1+673117d (repl 6.2.0-dev) Type \help for help, \quit to quit. - edgedb> - ---------- @@ -90,7 +95,7 @@ Connect to some specific branch: .. code-block:: bash - $ edgedb -b feature - EdgeDB 1.0-beta.2+ga7130d5c7.cv202104290000 (repl 1.0.0-beta.2) + $ gel -b feature + Gel 6.0-rc.1+673117d (repl 6.2.0-dev) Type \help for help, \quit to quit. special_db> diff --git a/docs/cheatsheets/index.rst b/docs/cheatsheets/index.rst index c1e80e60169..1992201f521 100644 --- a/docs/cheatsheets/index.rst +++ b/docs/cheatsheets/index.rst @@ -27,10 +27,7 @@ Cheatsheets Just getting started? Keep an eye on this collection of cheatsheets with -handy examples for what you'll need to get started with EdgeDB. -After familiarizing yourself with them, feel free to dive into more EdgeDB -via our longer `interactive tutorial `_ and -**much** longer `Easy EdgeDB textbook `_. +handy examples for what you'll need to get started with |Gel|. EdgeQL ====== @@ -63,6 +60,6 @@ CLI/Admin * :ref:`CLI Usage ` -- Getting your database started. * :ref:`Interactive Shell ` -- Shortcuts for - frequently used commands in the EdgeDB Interactive Shell. + frequently used commands in the Gel Interactive Shell. * :ref:`Administration ` -- Branch and role creation, passwords, port configuration, etc. diff --git a/docs/cheatsheets/link_properties.rst b/docs/cheatsheets/link_properties.rst index 0214b7776b8..d6a604c914a 100644 --- a/docs/cheatsheets/link_properties.rst +++ b/docs/cheatsheets/link_properties.rst @@ -245,13 +245,13 @@ Querying .. code-block:: edgeql-repl - edgedb> select Person { - ....... name, - ....... friends: { - ....... name, - ....... @strength - ....... } - ....... }; + gel> select Person { + .... name, + .... friends: { + .... name, + .... @strength + .... } + .... }; { default::Person {name: 'Alice', friends: {}}, default::Person { @@ -311,12 +311,8 @@ Querying :eql:func:`assert_distinct` here to assure the compiler that the result set is distinct. -.. note:: - - Specifying link properties of a computed backlink in your shape is - supported as of EdgeDB 3.0. - - If you have this schema: + Specifying link properties of a computed backlink in your shape is also + supported. If you have this schema: .. code-block:: sdl @@ -330,7 +326,7 @@ Querying multi link followers := ....] + gel [...] It's also possible to run an EdgeQL script by piping it into the -EdgeDB shell. The shell will then run in non-interactive mode and +|Gel| shell. The shell will then run in non-interactive mode and print all the responses into the standard output: .. cli:synopsis:: - cat myscript.edgeql | edgedb [...] + cat myscript.edgeql | gel [...] The above command also works on PowerShell in Windows, while the classic Windows Command Prompt uses a different command as shown below: .. cli:synopsis:: - type myscript.edgeql | edgedb [...] + type myscript.edgeql | gel [...] Description =========== -``edgedb`` is a terminal-based front-end to EdgeDB. It allows running +``edgedb`` is a terminal-based front-end to |Gel|. It allows running queries and seeing results interactively. @@ -53,13 +53,13 @@ Options Specifies the named instance to connect to. The actual connection parameters are stored in ``/credentials`` and are usually created by :ref:`ref_cli_edgedb_instance_create` or similar - commands. Run ``edgedb info`` to see the location of + commands. Run ``gel info`` to see the location of ```` on your machine. This option overrides host and port. :cli:synopsis:`--dsn=` - Specifies the DSN for EdgeDB to connect to. + Specifies the DSN for |Gel| to connect to. This option overrides all other options except password. @@ -92,16 +92,15 @@ Options .. note:: - EdgeDB 5.0 introduced :ref:`branches ` to - replace databases. This option requires CLI version 4.3.0 or later and - EdgeDB version 5.0 or later. If you are running an earlier version of - EdgeDB, you will instead use the ``-d , --database=`` + |EdgeDB| 5.0 introduced :ref:`branches ` to + replace databases. If you are running an earlier version of + Gel, you will instead use the ``-d , --database=`` option above. :cli:synopsis:`--password | --no-password` If :cli:synopsis:`--password` is specified, force ``edgedb`` to prompt for a password before connecting to the database. This is usually not - necessary, since ``edgedb`` will prompt for a password automatically + necessary, since ``gel`` will prompt for a password automatically if the server requires it. Specifying :cli:synopsis:`--no-password` disables all password prompts. @@ -137,11 +136,11 @@ Options Disable all TLS security measures. :cli:synopsis:`--wait-until-available=` - In case EdgeDB connection can't be established, keep retrying up + In case |Gel| connection can't be established, keep retrying up to :cli:synopsis:`` (e.g. ``30s``). :cli:synopsis:`--connect-timeout=` - Specifies a :cli:synopsis:`` period. In case EdgeDB + Specifies a :cli:synopsis:`` period. In case |Gel| doesn't respond for this period the command will fail (or retry if :cli:synopsis:`--wait-until-available` is also specified). The :cli:synopsis:`` value must be given using time units @@ -169,25 +168,15 @@ many of the commands: Describe the entire schema. :cli:synopsis:`\\l` - List branches on EdgeDB server 5+ or databases on prior versions. - -:cli:synopsis:`\\list databases` - List databases. - - .. note:: - - EdgeDB 5.0 introduced :ref:`branches ` to replace - databases. If you are running 5.0 or later, you will instead use the - ``\list branches`` command below. + List branches on |Gel| server 5+ or databases on prior versions. :cli:synopsis:`\\list branches` List branches. .. note:: - EdgeDB 5.0 introduced :ref:`branches ` to replace - databases. This command requires CLI version 4.3.0 or later and EdgeDB - version 5.0 or later. If you are running an earlier version of EdgeDB, + |EdgeDB| 5.0 introduced :ref:`branches ` to replace + databases. If you are running an earlier version of Gel, you will instead use the ``\list databases`` command above. :cli:synopsis:`\\ls [-sc] [PATTERN], \\list scalars [-sc] [PATTERN]` @@ -211,26 +200,13 @@ many of the commands: :cli:synopsis:`\\li [-vsc] [PATTERN], \\list indexes [-vsc] [PATTERN]` List indexes. -Database --------- - -.. note:: - - EdgeDB 5.0 introduced :ref:`branches ` to replace - databases. If you are running 5.0 or later, you will instead use the - commands in the "Branch" section below. - -:cli:synopsis:`\\database create NAME` - Create a new database. - Branch ------ -.. note:: +.. versionadded:: 5.0 - EdgeDB 5.0 introduced :ref:`branches ` to replace - databases. These commands require CLI version 4.3.0 or later and EdgeDB - version 5.0 or later. If you are running an earlier version of EdgeDB, + |EdgeDB| 5.0 introduced :ref:`branches ` to replace + databases. If you are running an earlier version of Gel, you will instead use the database commands above. :cli:synopsis:`\\branch create NAME` @@ -245,18 +221,10 @@ Query Analysis -------------- :cli:synopsis:`\\analyze QUERY` - .. note:: - - This command is compatible with EdgeDB server 3.0 and above. - Run a query performance analysis on the given query. Most conveniently used without a backslash by just adding ``analyze`` before any query. :cli:synopsis:`\\expand` - .. note:: - - This command is compatible with EdgeDB server 3.0 and above. - Print expanded output of last ``analyze`` operation. Data Operations @@ -294,20 +262,14 @@ Settings Connection ---------- -:cli:synopsis:`\\c, \\connect [DBNAME]` - Connect to database *DBNAME*. - - .. note:: - - EdgeDB 5.0 introduced :ref:`branches ` to replace - databases. If you are running 5.0 or later, you will instead use the - ``\branch switch NAME`` command to switch to a different branch. +:cli:synopsis:`\\c, \\connect [NAME]` + Connect to branch *NAME*. Migrations ---------- These migration commands are also accessible directly from the command line -without first entering the EdgeDB shell. Their counterpart commands are noted +without first entering the |Gel| shell. Their counterpart commands are noted and linked in their descriptions if you want more detail. :cli:synopsis:`\\migration create` @@ -328,7 +290,7 @@ and linked in their descriptions if you want more detail. Show the migration history, just like :ref:`ref_cli_edgedb_migration_log`. :cli:synopsis:`\\migration status` - Show how the state of the schema in the EdgeDB instance compares to the + Show how the state of the schema in the |Gel| instance compares to the migration stored in the schema directory, just like :ref:`ref_cli_edgedb_migration_status`. @@ -340,3 +302,16 @@ Help :cli:synopsis:`\\q, \\quit, \\exit` Quit the REPL. You can also do this by pressing Ctrl+D. + + +Database +-------- + +.. note:: + + |EdgeDB| 5.0 introduced :ref:`branches ` to replace + databases. If you are running 5.0 or later, you will instead use the + commands in the "Branch" section above. + +:cli:synopsis:`\\database create NAME` + Create a new database. diff --git a/docs/cli/edgedb_analyze.rst b/docs/cli/edgedb_analyze.rst index f8ce1607d91..7665bc0175d 100644 --- a/docs/cli/edgedb_analyze.rst +++ b/docs/cli/edgedb_analyze.rst @@ -1,13 +1,9 @@ .. _ref_cli_edgedb_analyze: -============== -edgedb analyze -============== - -.. note:: - - This CLI feature is compatible with EdgeDB server 3.0 and above. +=========== +gel analyze +=========== .. note:: @@ -20,7 +16,7 @@ Run a query performance analysis on the given query. .. cli:synopsis:: - edgedb analyze [] + gel analyze [] An example of ``analyze`` output from a simple query: @@ -61,4 +57,4 @@ the connection target see :ref:`connection options `. Media ===== -.. edb:youtube-embed:: WoHJu0nq5z0 \ No newline at end of file +.. edb:youtube-embed:: WoHJu0nq5z0 diff --git a/docs/cli/edgedb_branch/edgedb_branch_create.rst b/docs/cli/edgedb_branch/edgedb_branch_create.rst index 36650c6bda7..02081e0b244 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_create.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_create.rst @@ -1,28 +1,20 @@ .. _ref_cli_edgedb_branch_create: -==================== -edgedb branch create -==================== +================= +gel branch create +================= Create a new :ref:`branch `. .. cli:synopsis:: - edgedb branch create [] - -.. note:: - - This CLI command requires CLI version 4.3.0 or later and EdgeDB version 5.0 - or later. If you are running an earlier version of EdgeDB, you will instead - use the :ref:`ref_cli_edgedb_database_create` command to create a database, - which branches replaced in EdgeDB 5.0. - + gel branch create [] Description =========== -``edgedb branch create`` creates a new branch with the same schema as the +``gel branch create`` creates a new branch with the same schema as the current branch specified in ``$CONFIG/credentials``. Without any options, it is equivalent to :eql:stmt:`create schema branch`. @@ -30,7 +22,7 @@ equivalent to :eql:stmt:`create schema branch`. Options ======= -The ``branch create`` command runs in the EdgeDB instance it is +The ``branch create`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/edgedb_branch_drop.rst b/docs/cli/edgedb_branch/edgedb_branch_drop.rst index edf16747fde..b9031c0884e 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_drop.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_drop.rst @@ -1,28 +1,20 @@ .. _ref_cli_edgedb_branch_drop: -================== -edgedb branch drop -================== +=============== +gel branch drop +=============== Remove an existing :ref:`branch `. .. cli:synopsis:: - edgedb branch drop [] - -.. note:: - - This CLI command requires CLI version 4.3.0 or later and EdgeDB version 5.0 - or later. If you are running an earlier version of EdgeDB, you will instead - use the :ref:`ref_cli_edgedb_database_drop` command to drop a database, - which branches replaced in EdgeDB 5.0. - + gel branch drop [] Options ======= -The ``branch drop`` command runs in the EdgeDB instance it is +The ``branch drop`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/edgedb_branch_list.rst b/docs/cli/edgedb_branch/edgedb_branch_list.rst index deb28fa6ecc..5f4eead7dce 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_list.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_list.rst @@ -1,19 +1,12 @@ .. _ref_cli_edgedb_branch_list: -================== -edgedb branch list -================== +=============== +gel branch list +=============== List all :ref:`branches ` .. cli:synopsis:: - edgedb branch list - -.. note:: - - This CLI command requires CLI version 4.3.0 or later and EdgeDB version 5.0 - or later. If you are running an earlier version of EdgeDB, you will instead - use the :ref:`edgedb list databases ` command to list - databases, which branches replaced in EdgeDB 5.0. + gel branch list diff --git a/docs/cli/edgedb_branch/edgedb_branch_merge.rst b/docs/cli/edgedb_branch/edgedb_branch_merge.rst index 1131139aac5..374520be65f 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_merge.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_merge.rst @@ -1,22 +1,15 @@ .. _ref_cli_edgedb_branch_merge: -=================== -edgedb branch merge -=================== +================ +gel branch merge +================ Merge a :ref:`branch ` into the current branch. .. cli:synopsis:: - edgedb branch merge [] - -.. note:: - - This CLI command requires CLI version 4.0 or later and EdgeDB version 5.0 - or later. Earlier versions did not feature branches and instead featured - databases. Databases offered no analog to merging. See the - :ref:`ref_cli_edgedb_database` command suite to manage databases. + gel branch merge [] Description @@ -43,7 +36,7 @@ applying any new migrations from the target branch on the current branch. Options ======= -The ``branch merge`` command runs in the EdgeDB instance it is +The ``branch merge`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/edgedb_branch_rebase.rst b/docs/cli/edgedb_branch/edgedb_branch_rebase.rst index 247ee65711b..8ea567f147d 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_rebase.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_rebase.rst @@ -1,23 +1,16 @@ .. _ref_cli_edgedb_branch_rebase: -==================== -edgedb branch rebase -==================== +================= +gel branch rebase +================= Create a :ref:`branch ` based on the target branch but including new migrations on the current branch. .. cli:synopsis:: - edgedb branch rebase [] - -.. note:: - - This CLI command requires CLI version 4.0 or later and EdgeDB version 5.0 - or later. Earlier versions did not feature branches and instead featured - databases. Databases offered no analog to rebasing. See the - :ref:`ref_cli_edgedb_database` command suite to manage databases. + gel branch rebase [] Description @@ -29,7 +22,7 @@ current branch. .. note:: When rebasing, the data of the target branch is preserved. This means that - if you switch to a branch ``feature`` and run ``edgedb branch rebase + if you switch to a branch ``feature`` and run ``gel branch rebase main``, you will end up with a branch with the schema from ``main`` and any new migrations from ``feature`` and the data from ``main``. @@ -40,7 +33,7 @@ migrations guide `. Options ======= -The ``branch rebase`` command runs in the EdgeDB instance it is +The ``branch rebase`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/edgedb_branch_rename.rst b/docs/cli/edgedb_branch/edgedb_branch_rename.rst index 3ff2d1a17cd..6e7475ef74e 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_rename.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_rename.rst @@ -1,32 +1,21 @@ .. _ref_cli_edgedb_branch_rename: -==================== -edgedb branch rename -==================== +================= +gel branch rename +================= Rename a :ref:`branch ` .. cli:synopsis:: - edgedb branch rename [] - -.. note:: - - This CLI command requires CLI version 4.0 or later and EdgeDB version 5.0 - or later. Earlier versions did not feature branches and instead featured - databases. Databases offered no analog to the ``rename`` command. To - rename a database, you could :ref:`dump ` your - database, :ref:`create ` a new database - with the desired name, and :ref:`restore ` the dump - to that new database. See the :ref:`ref_cli_edgedb_database` command suite - for other database management commands. + gel branch rename [] Options ======= -The ``branch rename`` command runs in the EdgeDB instance it is +The ``branch rename`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/edgedb_branch_switch.rst b/docs/cli/edgedb_branch/edgedb_branch_switch.rst index ac5c317a7e6..fd732d05537 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_switch.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_switch.rst @@ -1,21 +1,20 @@ .. _ref_cli_edgedb_branch_switch: -==================== -edgedb branch switch -==================== +================= +gel branch switch +================= Change the currently active :ref:`branch ` .. cli:synopsis:: - edgedb branch switch [] + gel branch switch [] .. note:: - This CLI command requires CLI version 4.0 or later and EdgeDB version 5.0 - or later. Earlier versions did not feature branches and instead featured - databases. + This CLI command requires |Gel| version 5.0 or later. Earlier versions did + not feature branches and instead featured databases. Databases offered no direct analog to switching. @@ -38,7 +37,7 @@ Change the currently active :ref:`branch ` Options ======= -The ``branch switch`` command runs in the EdgeDB instance it is +The ``branch switch`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/edgedb_branch_wipe.rst b/docs/cli/edgedb_branch/edgedb_branch_wipe.rst index 328feb4991f..a410724987c 100644 --- a/docs/cli/edgedb_branch/edgedb_branch_wipe.rst +++ b/docs/cli/edgedb_branch/edgedb_branch_wipe.rst @@ -1,23 +1,15 @@ .. _ref_cli_edgedb_branch_wipe: -================== -edgedb branch wipe -================== +=============== +gel branch wipe +=============== Destroy the contents of a :ref:`branch ` .. cli:synopsis:: - edgedb branch wipe [] - -.. note:: - - This CLI command requires CLI version 4.3.0 or later and EdgeDB version 5.0 - or later. If you are running an earlier version of EdgeDB, you will instead - use the :ref:`ref_cli_edgedb_database_wipe` command to wipe a database, - which branches replaced in EdgeDB 5.0. - + gel branch wipe [] Description =========== @@ -25,14 +17,14 @@ Description The contents of the branch will be destroyed and the schema reset to its state before any migrations, but the branch itself will be preserved. -``edgedb branch wipe`` is a terminal command equivalent to +``gel branch wipe`` is a terminal command equivalent to :eql:stmt:`reset schema to initial`. Options ======= -The ``branch wipe`` command runs in the EdgeDB instance it is +The ``branch wipe`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_branch/index.rst b/docs/cli/edgedb_branch/index.rst index 035e413c0ba..50cbdf9af96 100644 --- a/docs/cli/edgedb_branch/index.rst +++ b/docs/cli/edgedb_branch/index.rst @@ -1,18 +1,11 @@ .. _ref_cli_edgedb_branch: -============= -edgedb branch -============= +========== +gel branch +========== -.. note:: - - These CLI commands require CLI version 4.3.0 or later and EdgeDB version - 5.0 or later. If you are running an earlier version of EdgeDB, you will - instead use the :ref:`ref_cli_edgedb_database` command suite to manage - databases, which branches replaced in EdgeDB 5.0. - -The ``edgedb branch`` group of commands contains various branch management +The ``gel branch`` group of commands contains various branch management tools. .. toctree:: diff --git a/docs/cli/edgedb_cli_upgrade.rst b/docs/cli/edgedb_cli_upgrade.rst index e7cece318a3..32b3d5c6c0f 100644 --- a/docs/cli/edgedb_cli_upgrade.rst +++ b/docs/cli/edgedb_cli_upgrade.rst @@ -1,21 +1,21 @@ .. _ref_cli_edgedb_cli_upgrade: -================== -edgedb cli upgrade -================== +=============== +gel cli upgrade +=============== Upgrade the CLI binary. .. cli:synopsis:: - edgedb cli upgrade [] + gel cli upgrade [] Description =========== -``edgedb cli upgrade`` is a terminal command used to upgrade the CLI +``gel cli upgrade`` is a terminal command used to upgrade the CLI tools to keep them up-to-date. diff --git a/docs/cli/edgedb_cloud/edgedb_cloud_login.rst b/docs/cli/edgedb_cloud/edgedb_cloud_login.rst index 587b1d70774..8dad0a70655 100644 --- a/docs/cli/edgedb_cloud/edgedb_cloud_login.rst +++ b/docs/cli/edgedb_cloud/edgedb_cloud_login.rst @@ -1,26 +1,22 @@ .. _ref_cli_edgedb_cloud_login: -================== -edgedb cloud login -================== +=============== +gel cloud login +=============== -.. note:: - - This CLI command requires CLI version 3.0 or later. - -Authenticate to the EdgeDB Cloud and remember the secret key locally +Authenticate to the |Gel| Cloud and remember the secret key locally .. cli:synopsis:: - edgedb cloud login + gel cloud login -This command will launch your browser and start the EdgeDB Cloud authentication +This command will launch your browser and start the |Gel| Cloud authentication flow. Once authentication is successful, the CLI will log a success message: .. code-block:: - Successfully logged in to EdgeDB Cloud as + Successfully logged in to |Gel| Cloud as If you are unable to complete authentication in the browser, you can interrupt the command by pressing Ctrl-C. @@ -28,7 +24,7 @@ the command by pressing Ctrl-C. .. warning:: CI users and scripters This command is not intended for use in scripting and CI. Instead, you - should generate a secret key in the EdgeDB Cloud UI or by running + should generate a secret key in the |Gel| Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` and set the ``EDGEDB_SECRET_KEY`` environment variable to your secret key. Once this variable is set to your secret key, logging in is no longer required. diff --git a/docs/cli/edgedb_cloud/edgedb_cloud_logout.rst b/docs/cli/edgedb_cloud/edgedb_cloud_logout.rst index 23844122ea1..bbe3b4e84f6 100644 --- a/docs/cli/edgedb_cloud/edgedb_cloud_logout.rst +++ b/docs/cli/edgedb_cloud/edgedb_cloud_logout.rst @@ -1,25 +1,21 @@ .. _ref_cli_edgedb_cloud_logout: -=================== -edgedb cloud logout -=================== - -.. note:: - - This CLI command requires CLI version 3.0 or later. +================ +gel cloud logout +================ Forget the stored access token .. cli:synopsis:: - edgedb cloud logout [] + gel cloud logout [] .. warning:: CI users and scripters This command is not intended for use in scripting and CI. Instead, to - authenticate to your EdgeDB Cloud account, you should generate a secret key - in the EdgeDB Cloud UI or by running + authenticate to your |Gel| Cloud account, you should generate a secret key + in the Gel Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` and set the ``EDGEDB_SECRET_KEY`` environment variable to your secret key. Logging out is not necessary. diff --git a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_create.rst b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_create.rst index c2872a27331..523355bf0dd 100644 --- a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_create.rst +++ b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_create.rst @@ -1,19 +1,15 @@ .. _ref_cli_edgedb_cloud_secretkey_create: -============================= -edgedb cloud secretkey create -============================= - -.. note:: - - This CLI command requires CLI version 3.0 or later. +========================== +gel cloud secretkey create +========================== Create a new secret key .. cli:synopsis:: - edgedb cloud secretkey create [] + gel cloud secretkey create [] .. note:: diff --git a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_list.rst b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_list.rst index 01b9fc35d94..37d240f9d76 100644 --- a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_list.rst +++ b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_list.rst @@ -1,19 +1,16 @@ .. _ref_cli_edgedb_cloud_secretkey_list: -=========================== -edgedb cloud secretkey list -=========================== +======================== +gel cloud secretkey list +======================== -.. note:: - - This CLI command requires CLI version 3.0 or later. List existing secret keys .. cli:synopsis:: - edgedb cloud secretkey list [] + gel cloud secretkey list [] .. note:: diff --git a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_revoke.rst b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_revoke.rst index 23a86f23000..f06c30a7620 100644 --- a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_revoke.rst +++ b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/edgedb_cloud_secretkey_revoke.rst @@ -1,19 +1,15 @@ .. _ref_cli_edgedb_cloud_secretkey_revoke: -============================= -edgedb cloud secretkey revoke -============================= - -.. note:: - - This CLI command requires CLI version 3.0 or later. +========================== +gel cloud secretkey revoke +========================== Revoke a secret key .. cli:synopsis:: - edgedb cloud secretkey revoke [] --secret-key-id + gel cloud secretkey revoke [] --secret-key-id .. note:: diff --git a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/index.rst b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/index.rst index a2f36852385..8639577c60d 100644 --- a/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/index.rst +++ b/docs/cli/edgedb_cloud/edgedb_cloud_secretkey/index.rst @@ -1,13 +1,9 @@ .. _ref_cli_edgedb_cloud_secretkey: -====================== -edgedb cloud secretkey -====================== - -.. note:: - - These CLI commands require CLI version 3.0 or later. +=================== +gel cloud secretkey +=================== Manage your secret keys diff --git a/docs/cli/edgedb_cloud/index.rst b/docs/cli/edgedb_cloud/index.rst index b7c36f7bb4a..3e6e14f879a 100644 --- a/docs/cli/edgedb_cloud/index.rst +++ b/docs/cli/edgedb_cloud/index.rst @@ -1,16 +1,13 @@ .. _ref_cli_edgedb_cloud: -============ -edgedb cloud -============ +========= +gel cloud +========= -.. note:: - These CLI commands require CLI version 3.0 or later. - -In addition to managing your own local and remote instances, the EdgeDB CLI -offers tools to manage your instances running on our EdgeDB Cloud. +In addition to managing your own local and remote instances, the |Gel| CLI +offers tools to manage your instances running on our Gel Cloud. .. toctree:: :maxdepth: 3 @@ -24,7 +21,7 @@ offers tools to manage your instances running on our EdgeDB Cloud. :class: funcoptable * - :ref:`ref_cli_edgedb_cloud_login` - - Authenticate to the EdgeDB Cloud and remember the access token locally + - Authenticate to the |Gel| Cloud and remember the access token locally * - :ref:`ref_cli_edgedb_cloud_logout` - Forget the stored access token * - :ref:`ref_cli_edgedb_cloud_secretkey` @@ -32,13 +29,13 @@ offers tools to manage your instances running on our EdgeDB Cloud. .. warning:: CI users and scripters - The ``edgedb cloud login`` and ``edgedb cloud logout`` commands are not + The ``gel cloud login`` and ``gel cloud logout`` commands are not intended for use in scripting and CI. Instead, you should generate a secret - key in the EdgeDB Cloud UI or by running + key in the |Gel| Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` and set the ``EDGEDB_SECRET_KEY`` environment variable to your secret key. Once this variable is set to your secret key, logging in and out are no longer required. -Follow :ref:`our EdgeDB Cloud guide ` for information on how -to use EdgeDB Cloud. +Follow :ref:`our Gel Cloud guide ` for information on how +to use Gel Cloud. diff --git a/docs/cli/edgedb_configure.rst b/docs/cli/edgedb_configure.rst index 5663f1457e0..5764b9a3def 100644 --- a/docs/cli/edgedb_configure.rst +++ b/docs/cli/edgedb_configure.rst @@ -1,35 +1,35 @@ .. _ref_cli_edgedb_configure: -================ -edgedb configure -================ +============= +gel configure +============= -Configure the EdgeDB server. +Configure the |Gel| server. .. cli:synopsis:: - edgedb configure [] \ + gel configure [] \ [ ] \ [ --= ...] Description =========== -``edgedb configure`` is a terminal command used to alter the -configuration of an EdgeDB instance. There are three types of +``gel configure`` is a terminal command used to alter the +configuration of a |Gel| instance. There are three types of configuration actions that can be performed. Actions ======= -:cli:synopsis:`edgedb configure insert` +:cli:synopsis:`gel configure insert` Insert a new configuration entry for a setting that supports multiple configuration objects (e.g. Auth or Port). -:cli:synopsis:`edgedb configure set` +:cli:synopsis:`gel configure set` Set a scalar configuration value. -:cli:synopsis:`edgedb configure reset` +:cli:synopsis:`gel configure reset` Reset an existing configuration entry or remove all values for an entry that supports multiple configuration objects. diff --git a/docs/cli/edgedb_connopts.rst b/docs/cli/edgedb_connopts.rst index af0b0c9a5ec..a79d4ef9a12 100644 --- a/docs/cli/edgedb_connopts.rst +++ b/docs/cli/edgedb_connopts.rst @@ -28,13 +28,13 @@ Connection flags :ref:`ref_cli_edgedb_instance_create` or similar commands. Run ``edgedb info`` to see the location of ```` on your machine. - EdgeDB Cloud instance names are in the format + |Gel| Cloud instance names are in the format ``/``. This option overrides host and port. :cli:synopsis:`--dsn=` - Specifies the DSN for EdgeDB to connect to. + Specifies the DSN for |Gel| to connect to. This option overrides all other options except password. @@ -51,7 +51,7 @@ Connection flags if not set, to ``5656``. :cli:synopsis:`--unix-path /path/to/socket` - Specifies a path to a Unix socket for an EdgeDB connection. If the path is + Specifies a path to a Unix socket for an |Gel| connection. If the path is a directory, the actual path will be computed using the ``port`` and ``admin`` parameters. @@ -69,19 +69,19 @@ Connection flags the ``EDGEDB_DATABASE`` environment variable. If that variable isn't set, local instances will default to ``edgedb`` while remote instances will default to the name provided when the link was created. This also includes - EdgeDB Cloud instance links created via :ref:`ref_cli_edgedb_project_init`. + |Gel| Cloud instance links created via :ref:`ref_cli_edgedb_project_init`. .. note:: - With EdgeDB 5, databases were refactored as branches. If you're using - EdgeDB 5+, use the option below instead of this one. + With |Gel| 5, databases were refactored as branches. If you're using + |Gel| 5+, use the option below instead of this one. :cli:synopsis:`-b , --branch=` Specifies the name of the branch to connect to. Defaults to the value of the ``EDGEDB_BRANCH`` environment variable. If that variable isn't set, local instances will default to the most recently switched branch or the ``main`` branch, while remote instances will default to the name provided - when the link was created. This also includes EdgeDB Cloud instance links + when the link was created. This also includes |Gel| Cloud instance links created via :ref:`ref_cli_edgedb_project_init`. :cli:synopsis:`--password | --no-password` @@ -123,18 +123,18 @@ Connection flags Disable all TLS security measures. :cli:synopsis:`--secret-key ` - Specifies the secret key to use for authentication with EdgeDB Cloud - instances. This is not required when connecting to your own EdgeDB Cloud + Specifies the secret key to use for authentication with |Gel| Cloud + instances. This is not required when connecting to your own Gel Cloud instance if you have logged in with :ref:`ref_cli_edgedb_cloud_login`. :cli:synopsis:`--wait-until-available=` - In case EdgeDB connection can't be established, keep retrying up + In case |Gel| connection can't be established, keep retrying up to :cli:synopsis:`` (e.g. ``30s``). The :cli:synopsis:`` value must be given using time units (e.g. ``hr``, ``min``, ``sec``, ``ms``, etc.). :cli:synopsis:`--connect-timeout=` - Specifies a :cli:synopsis:`` period. In the event EdgeDB doesn't + Specifies a :cli:synopsis:`` period. In the event |Gel| doesn't respond in this period, the command will fail (or retry if :cli:synopsis:`--wait-until-available` is also specified). The :cli:synopsis:`` value must be given using time units (e.g. diff --git a/docs/cli/edgedb_database/edgedb_database_create.rst b/docs/cli/edgedb_database/edgedb_database_create.rst index 2bccf65ca1f..04a5472df90 100644 --- a/docs/cli/edgedb_database/edgedb_database_create.rst +++ b/docs/cli/edgedb_database/edgedb_database_create.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_database_create: -====================== -edgedb database create -====================== +=================== +gel database create +=================== Create a new :ref:`database `. .. cli:synopsis:: - edgedb database create [] + gel database create [] .. note:: - EdgeDB 5.0 introduced :ref:`branches ` to + |EdgeDB| 5.0 introduced :ref:`branches ` to replace databases. This command works on instances running versions - prior to EdgeDB 5.0. If you are running a newer version of - EdgeDB, you will instead use :ref:`ref_cli_edgedb_branch_create`. + prior to |EdgeDB| 5.0. If you are running a newer version of + Gel, you will instead use :ref:`ref_cli_edgedb_branch_create`. Description @@ -29,7 +29,7 @@ Description Options ======= -The ``database create`` command runs in the EdgeDB instance it is +The ``database create`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_database/edgedb_database_drop.rst b/docs/cli/edgedb_database/edgedb_database_drop.rst index d610298c5a9..6aa10ba7a12 100644 --- a/docs/cli/edgedb_database/edgedb_database_drop.rst +++ b/docs/cli/edgedb_database/edgedb_database_drop.rst @@ -1,35 +1,35 @@ .. _ref_cli_edgedb_database_drop: -==================== -edgedb database drop -==================== +================= +gel database drop +================= Drop a :ref:`database `. .. cli:synopsis:: - edgedb database drop [] + gel database drop [] .. note:: - EdgeDB 5.0 introduced :ref:`branches ` to + |EdgeDB| 5.0 introduced :ref:`branches ` to replace databases. This command works on instances running versions - prior to EdgeDB 5.0. If you are running a newer version of - EdgeDB, you will instead use :ref:`ref_cli_edgedb_branch_drop`. + prior to |EdgeDB| 5.0. If you are running a newer version of + Gel, you will instead use :ref:`ref_cli_edgedb_branch_drop`. Description =========== -``edgedb database drop`` is a terminal command equivalent to +``gel database drop`` is a terminal command equivalent to :eql:stmt:`drop database`. Options ======= -The ``database drop`` command runs in the EdgeDB instance it is +The ``database drop`` command runs in the Gel instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_database/edgedb_database_wipe.rst b/docs/cli/edgedb_database/edgedb_database_wipe.rst index 714c441bfa8..ce8cfdbca52 100644 --- a/docs/cli/edgedb_database/edgedb_database_wipe.rst +++ b/docs/cli/edgedb_database/edgedb_database_wipe.rst @@ -1,28 +1,28 @@ .. _ref_cli_edgedb_database_wipe: -==================== -edgedb database wipe -==================== +================= +gel database wipe +================= Destroy the contents of a :ref:`database ` .. cli:synopsis:: - edgedb database wipe [] + gel database wipe [] .. note:: - EdgeDB 5.0 introduced :ref:`branches ` to + |EdgeDB| 5.0 introduced :ref:`branches ` to replace databases. This command works on instances running versions - prior to EdgeDB 5.0. If you are running a newer version of - EdgeDB, you will instead use :ref:`ref_cli_edgedb_branch_wipe`. + prior to |EdgeDB| 5.0. If you are running a newer version of + |EdgeDB| or Gel, you will instead use :ref:`ref_cli_edgedb_branch_wipe`. Description =========== -``edgedb database wipe`` is a terminal command equivalent to +``gel database wipe`` is a terminal command equivalent to :eql:stmt:`reset schema to initial`. The database wiped will be one of these values: the value passed for the @@ -34,7 +34,7 @@ state before any migrations, but the database itself will be preserved. Options ======= -The ``database wipe`` command runs in the EdgeDB instance it is +The ``database wipe`` command runs in the |Gel| instance it is connected to. For specifying the connection target see :ref:`connection options `. diff --git a/docs/cli/edgedb_database/index.rst b/docs/cli/edgedb_database/index.rst index 65f7e28efb4..69a1658e220 100644 --- a/docs/cli/edgedb_database/index.rst +++ b/docs/cli/edgedb_database/index.rst @@ -1,19 +1,19 @@ .. _ref_cli_edgedb_database: -=============== -edgedb database -=============== +============ +gel database +============ -The ``edgedb database`` group of commands contains various database +The ``gel database`` group of commands contains various database manipulation tools. .. note:: - EdgeDB 5.0 introduced :ref:`branches ` to + |EdgeDB| 5.0 introduced :ref:`branches ` to replace databases. These commands work on instances running versions - prior to EdgeDB 5.0. If you are running a newer version of - EdgeDB, you will instead use the :ref:`ref_cli_edgedb_branch` suite of + prior to |EdgeDB| 5.0. If you are running a newer version of + Gel, you will instead use the :ref:`ref_cli_edgedb_branch` suite of commands. .. toctree:: diff --git a/docs/cli/edgedb_describe/edgedb_describe_object.rst b/docs/cli/edgedb_describe/edgedb_describe_object.rst index 4c7c11621bd..97f76ce27b5 100644 --- a/docs/cli/edgedb_describe/edgedb_describe_object.rst +++ b/docs/cli/edgedb_describe/edgedb_describe_object.rst @@ -1,21 +1,21 @@ .. _ref_cli_edgedb_describe_object: -====================== -edgedb describe object -====================== +=================== +gel describe object +=================== Describe a named schema object. .. cli:synopsis:: - edgedb describe object [] + gel describe object [] Description =========== -``edgedb describe`` is a terminal command equivalent to +``gel describe`` is a terminal command equivalent to :eql:stmt:`describe object ` introspection command. diff --git a/docs/cli/edgedb_describe/edgedb_describe_schema.rst b/docs/cli/edgedb_describe/edgedb_describe_schema.rst index 37f2aa9d1b1..df2924e9e21 100644 --- a/docs/cli/edgedb_describe/edgedb_describe_schema.rst +++ b/docs/cli/edgedb_describe/edgedb_describe_schema.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_describe_schema: -====================== -edgedb describe schema -====================== +=================== +gel describe schema +=================== Give an :ref:`SDL ` description of the schema of the database specified by the connection options. .. cli:synopsis:: - edgedb describe schema [] + gel describe schema [] Description =========== -``edgedb describe schema`` is a terminal command equivalent to +``gel describe schema`` is a terminal command equivalent to :eql:stmt:`describe schema as sdl ` introspection command. diff --git a/docs/cli/edgedb_describe/index.rst b/docs/cli/edgedb_describe/index.rst index 07b800f27b6..b82e87ce87c 100644 --- a/docs/cli/edgedb_describe/index.rst +++ b/docs/cli/edgedb_describe/index.rst @@ -1,11 +1,11 @@ .. _ref_cli_edgedb_describe: -=============== -edgedb describe -=============== +============ +gel describe +============ -The ``edgedb describe`` group of commands contains various schema +The ``gel describe`` group of commands contains various schema introspection tools. .. toctree:: diff --git a/docs/cli/edgedb_dump.rst b/docs/cli/edgedb_dump.rst index e1e1d2e894f..ca06f7e6dd7 100644 --- a/docs/cli/edgedb_dump.rst +++ b/docs/cli/edgedb_dump.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_dump: -=========== -edgedb dump -=========== +======== +gel dump +======== -Backup an EdgeDB branch (or database pre-v5) to a file. +Backup an |Gel| branch (or database pre-v5) to a file. .. cli:synopsis:: - edgedb dump [] + gel dump [] Options diff --git a/docs/cli/edgedb_info.rst b/docs/cli/edgedb_info.rst index 06387d68204..c9982868964 100644 --- a/docs/cli/edgedb_info.rst +++ b/docs/cli/edgedb_info.rst @@ -1,16 +1,16 @@ .. _ref_cli_edgedb_info: -=========== -edgedb info -=========== +======== +gel info +======== -Display information about the EdgeDB installation. Currently this command -displays the filesystem paths used by EdgeDB. +Display information about the |Gel| installation. Currently this command +displays the filesystem paths used by Gel. .. cli:synopsis:: - edgedb info [] + gel info [] .. _ref_cli_edgedb_paths: @@ -18,14 +18,14 @@ displays the filesystem paths used by EdgeDB. Paths ----- -EdgeDB uses several directories, each storing different kinds of information. +|Gel| uses several directories, each storing different kinds of information. The exact path to these directories is determined by your operating system. -Throughout the documentation, these paths are referred to as "EdgeDB config -directory", "EdgeDB data directory", etc. +Throughout the documentation, these paths are referred to as "Gel config +directory", "Gel data directory", etc. - **Config**: contains auto-generated credentials for all local instances and project metadata. -- **Data**: contains the *contents* of all local EdgeDB instances. +- **Data**: contains the *contents* of all local Gel instances. - **CLI Binary**: contains the CLI binary, if installed. - **Service**: the home for running processes/daemons. - **Cache**: a catchall for logs and various caches. diff --git a/docs/cli/edgedb_instance/edgedb_instance_create.rst b/docs/cli/edgedb_instance/edgedb_instance_create.rst index 9929c432e58..4fc5e4bc0d9 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_create.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_create.rst @@ -1,41 +1,37 @@ .. _ref_cli_edgedb_instance_create: -====================== -edgedb instance create -====================== +=================== +gel instance create +=================== -Initialize a new EdgeDB instance. +Initialize a new |Gel| instance. .. cli:synopsis:: - edgedb instance create [] [] [] + gel instance create [] [] [] Description =========== -``edgedb instance create`` is a terminal command for making a new EdgeDB +``gel instance create`` is a terminal command for making a new Gel instance and creating a corresponding credentials file in -``/credentials``. Run ``edgedb info`` to see the path to +``/credentials``. Run ``gel info`` to see the path to ```` on your machine. .. note:: - The ``edgedb instance create`` command is not intended for use with + The ``gel instance create`` command is not intended for use with self-hosted instances. You can follow one of our :ref:`deployment guides ` for information on how to create one of these instances. -EdgeDB Cloud ------------- +Gel Cloud +--------- -.. note:: - - Creating a Cloud instance requires CLI version 3.0 or later. - -EdgeDB Cloud users may use this command to create a Cloud instance after +Gel Cloud users may use this command to create a Cloud instance after logging in using :ref:`ref_cli_edgedb_cloud_login`. To create a Cloud instance, your instance name should be in the format @@ -44,7 +40,7 @@ characters and hyphens (i.e., ``-``). .. note:: - Please be aware of the following restrictions on EdgeDB Cloud instance + Please be aware of the following restrictions on |Gel| Cloud instance names: * can contain only Latin alpha-numeric characters or ``-`` @@ -58,18 +54,18 @@ Options ======= :cli:synopsis:`` - The new EdgeDB instance name. Asked interactively if not specified. + The new |Gel| instance name. Asked interactively if not specified. :cli:synopsis:`` The default branch (or database pre-v5) name on the new instance. Defaults - to ``main`` or, when creating a pre-v5 instance, ``edgedb``. + to ``main`` or, when creating a pre-v5 instance, ``gel``. :cli:synopsis:`--nightly` Use the nightly server for this instance. :cli:synopsis:`--default-user=` Specifies the default user name (created during initialization, - and saved in credentials file). Defaults to: ``edgedb``. + and saved in credentials file). Defaults to: ``gel``. :cli:synopsis:`--port=` Specifies which port should the instance be configured on. By @@ -88,35 +84,35 @@ Options ``testing``, or ``nightly``. :cli:synopsis:`--version=` - Specifies the version of the EdgeDB server to be used to run the + Specifies the version of the |Gel| server to be used to run the new instance. To list the currently available options use :ref:`ref_cli_edgedb_server_list_versions`. By default, when you specify a version, the CLI will use the latest release in the major version specified. This command, for example, will install the - latest 2.x release: + latest X.Y release: .. code-block:: bash - $ edgedb instance create --version 2.6 demo26 + $ gel instance create --version X.0 demoxy You may pin to a specific version by prepending the version number with an - equals sign. This command will install version 2.6: + equals sign. This command will install version X.Y: .. code-block:: bash - $ edgedb instance create --version =2.6 demo26 + $ gel instance create --version =X.Y demoxy .. note:: Some shells like ZSH may require you to escape the equals sign (e.g., - ``\=2.6``) or quote the version string (e.g., ``"=2.6"``). + ``\=X.Y``) or quote the version string (e.g., ``"=X.Y"``). -EdgeDB Cloud options --------------------- +Gel Cloud options +----------------- :cli:synopsis:`--region=` - The region in which to create the instance (for EdgeDB Cloud instances). + The region in which to create the instance (for |Gel| Cloud instances). Possible values are ``aws-us-west-2``, ``aws-us-east-2``, and ``aws-eu-west-1``. @@ -125,7 +121,7 @@ EdgeDB Cloud options ``pro`` and ``free``. :cli:synopsis:`--compute-size=` - The size of compute to be allocated for the EdgeDB Cloud instance (in + The size of compute to be allocated for the Gel Cloud instance (in Compute Units) :cli:synopsis:`--storage-size=` diff --git a/docs/cli/edgedb_instance/edgedb_instance_credentials.rst b/docs/cli/edgedb_instance/edgedb_instance_credentials.rst index 99698423215..58c7d68ce0c 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_credentials.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_credentials.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_instance_credentials: -=========================== -edgedb instance credentials -=========================== +======================== +gel instance credentials +======================== Display instance credentials. .. cli:synopsis:: - edgedb instance credentials [options] [connection-options] + gel instance credentials [options] [connection-options] Description =========== -``edgedb instance credentials`` is a terminal command for displaying the -credentials of an EdgeDB instance. +``gel instance credentials`` is a terminal command for displaying the +credentials of an |Gel| instance. Options @@ -33,7 +33,7 @@ Options Connection Options ================== -By default, the ``edgedb.toml`` connection is used. +By default, the |gel.toml| connection is used. :cli:synopsis:`` See :ref:`connection options `. diff --git a/docs/cli/edgedb_instance/edgedb_instance_destroy.rst b/docs/cli/edgedb_instance/edgedb_instance_destroy.rst index 3795c020108..6c1477ff681 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_destroy.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_destroy.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_instance_destroy: -======================= -edgedb instance destroy -======================= +==================== +gel instance destroy +==================== -Remove an EdgeDB instance. +Remove an |Gel| instance. .. cli:synopsis:: - edgedb instance destroy [] + gel instance destroy [] Description =========== -``edgedb instance destroy`` is a terminal command for removing an EdgeDB +``gel instance destroy`` is a terminal command for removing an (or edgedb.toml) instance and all its data. .. note:: - The ``edgedb instance destroy`` command is not intended for use with + The ``gel instance destroy`` command is not intended for use with self-hosted instances. @@ -28,7 +28,7 @@ Options ======= :cli:synopsis:`` - The EdgeDB instance name. + The |Gel| instance name. :cli:synopsis:`--force` Destroy the instance even if it is referred to by a project. diff --git a/docs/cli/edgedb_instance/edgedb_instance_link.rst b/docs/cli/edgedb_instance/edgedb_instance_link.rst index 20e8456b370..f7c69383425 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_link.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_link.rst @@ -1,34 +1,34 @@ .. _ref_cli_edgedb_instance_link: -==================== -edgedb instance link -==================== +================= +gel instance link +================= -Authenticate a connection to a remote EdgeDB instance and assign an +Authenticate a connection to a remote |Gel| instance and assign an instance name to simplify future connections. .. cli:synopsis:: - edgedb instance link [] + gel instance link [] Description =========== -``edgedb instance link`` is a terminal command used to bind a set of +``gel instance link`` is a terminal command used to bind a set of connection credentials to an instance name. This is typically used as -a way to simplify connecting to remote EdgeDB database instances. +a way to simplify connecting to remote |Gel| database instances. Usually there's no need to do this for local instances as :ref:`ref_cli_edgedb_project_init` will already set up a named instance. .. note:: - Unlike other ``edgedb instance`` sub-commands, ``edgedb instance link`` is + Unlike other ``gel instance`` sub-commands, ``gel instance link`` is recommended to link self-hosted instances. This can make other operations like migrations, dumps, and restores more convenient. - Linking is not required for EdgeDB Cloud instances. They can always be + Linking is not required for |Gel| Cloud instances. They can always be accessed via CLI using ``/``. Options diff --git a/docs/cli/edgedb_instance/edgedb_instance_list.rst b/docs/cli/edgedb_instance/edgedb_instance_list.rst index bb1a604882a..bbbaa8619c1 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_list.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_list.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_instance_list: -==================== -edgedb instance list -==================== +================= +gel instance list +================= -Show all EdgeDB instances. +Show all |Gel| instances. .. cli:synopsis:: - edgedb instance list [] + gel instance list [] Description =========== -``edgedb instance list`` is a terminal command that shows all the -registered EdgeDB instances and some relevant information about them +``gel instance list`` is a terminal command that shows all the +registered |Gel| instances and some relevant information about them (status, port, etc.). diff --git a/docs/cli/edgedb_instance/edgedb_instance_logs.rst b/docs/cli/edgedb_instance/edgedb_instance_logs.rst index c60dcbb4f7e..dd6ee6338bf 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_logs.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_logs.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_instance_logs: -==================== -edgedb instance logs -==================== +================= +gel instance logs +================= Show instance logs. .. cli:synopsis:: - edgedb instance logs [] + gel instance logs [] Description =========== -``edgedb instance logs`` is a terminal command for displaying the logs -for a given EdgeDB instance. +``gel instance logs`` is a terminal command for displaying the logs +for a given |Gel| instance. .. note:: - The ``edgedb instance logs`` command is not intended for use with + The ``gel instance logs`` command is not intended for use with self-hosted instances. @@ -28,7 +28,7 @@ Options ======= :cli:synopsis:`` - The name of the EdgeDB instance. + The name of the |Gel| instance. :cli:synopsis:`-n, --tail=` Number of the most recent lines to show. diff --git a/docs/cli/edgedb_instance/edgedb_instance_reset_password.rst b/docs/cli/edgedb_instance/edgedb_instance_reset_password.rst index d9562b5ebc0..16c4a9276af 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_reset_password.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_reset_password.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_instance_reset_auth: -============================== -edgedb instance reset-password -============================== +=========================== +gel instance reset-password +=========================== -Reset password for a user in the EdgeDB instance. +Reset password for a user in the |Gel| instance. .. cli:synopsis:: - edgedb instance reset-password [] + gel instance reset-password [] Description =========== -``edgedb instance reset-password`` is a terminal command for resetting -or updating the password for a user of an EdgeDB instance. +``gel instance reset-password`` is a terminal command for resetting +or updating the password for a user of an |Gel| instance. .. note:: - The ``edgedb instance reset-password`` command is not intended for use with + The ``gel instance reset-password`` command is not intended for use with self-hosted instances. @@ -28,7 +28,7 @@ Options ======= :cli:synopsis:`` - The name of the EdgeDB instance. + The name of the |Gel| instance. :cli:synopsis:`--user=` User to change password for. Defaults to the user in the diff --git a/docs/cli/edgedb_instance/edgedb_instance_restart.rst b/docs/cli/edgedb_instance/edgedb_instance_restart.rst index a0d6b3621f2..ed57ca9244f 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_restart.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_restart.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_instance_restart: -======================= -edgedb instance restart -======================= +==================== +gel instance restart +==================== -Restart an EdgeDB instance. +Restart an |Gel| instance. .. cli:synopsis:: - edgedb instance restart + gel instance restart Description =========== -``edgedb instance restart`` is a terminal command for restarting an -EdgeDB instance. +``gel instance restart`` is a terminal command for restarting an +|Gel| instance. .. note:: - The ``edgedb instance restart`` command is not intended for use with + The ``gel instance restart`` command is not intended for use with self-hosted instances. @@ -28,4 +28,4 @@ Options ======= :cli:synopsis:`` - The EdgeDB instance name. + The |Gel| instance name. diff --git a/docs/cli/edgedb_instance/edgedb_instance_revert.rst b/docs/cli/edgedb_instance/edgedb_instance_revert.rst index ee47af56251..85aa2ed7489 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_revert.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_revert.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_instance_revert: -====================== -edgedb instance revert -====================== +=================== +gel instance revert +=================== Revert a major instance upgrade. .. cli:synopsis:: - edgedb instance revert [] + gel instance revert [] Description @@ -17,13 +17,13 @@ Description When :ref:`ref_cli_edgedb_instance_upgrade` performs a major version upgrade on an instance the old instance data is kept around. The -``edgedb instance revert`` command removes the new instance version and +``gel instance revert`` command removes the new instance version and replaces it with the old copy. It also ensures that the previous -version of EdgeDB server is used to run it. +version of |Gel| server is used to run it. .. note:: - The ``edgedb instance revert`` command is not intended for use with + The ``gel instance revert`` command is not intended for use with self-hosted instances. @@ -31,7 +31,7 @@ Options ======= :cli:synopsis:`` - The name of the EdgeDB instance to revert. + The name of the |Gel| instance to revert. :cli:synopsis:`--ignore-pid-check` Do not check if upgrade is in progress. diff --git a/docs/cli/edgedb_instance/edgedb_instance_start.rst b/docs/cli/edgedb_instance/edgedb_instance_start.rst index 561b2ed84f5..c8d45c470b7 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_start.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_start.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_instance_start: -===================== -edgedb instance start -===================== +================== +gel instance start +================== -Start an EdgeDB instance. +Start an |Gel| instance. .. cli:synopsis:: - edgedb instance start [--foreground] + gel instance start [--foreground] Description =========== -``edgedb instance start`` is a terminal command for starting a new -EdgeDB instance. +``gel instance start`` is a terminal command for starting a new +|Gel| instance. .. note:: - The ``edgedb instance start`` command is not intended for use with + The ``gel instance start`` command is not intended for use with self-hosted instances. @@ -28,7 +28,7 @@ Options ======= :cli:synopsis:`` - The EdgeDB instance name. + The |Gel| instance name. :cli:synopsis:`--foreground` Start the instance in the foreground rather than using systemd to diff --git a/docs/cli/edgedb_instance/edgedb_instance_status.rst b/docs/cli/edgedb_instance/edgedb_instance_status.rst index 28ecd8b4513..f10126aa630 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_status.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_status.rst @@ -1,29 +1,29 @@ .. _ref_cli_edgedb_instance_status: -====================== -edgedb instance status -====================== +=================== +gel instance status +=================== Show instance information. .. cli:synopsis:: - edgedb instance status [] [] + gel instance status [] [] Description =========== -``edgedb instance status`` is a terminal command for displaying the -information about EdgeDB instances. +``gel instance status`` is a terminal command for displaying the +information about |Gel| instances. Options ======= :cli:synopsis:`` - Show only the status of the specific EdgeDB instance. + Show only the status of the specific |Gel| instance. :cli:synopsis:`--json` Format output as JSON. diff --git a/docs/cli/edgedb_instance/edgedb_instance_stop.rst b/docs/cli/edgedb_instance/edgedb_instance_stop.rst index 47e758d7076..17ed385b548 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_stop.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_stop.rst @@ -1,27 +1,27 @@ .. _ref_cli_edgedb_instance_stop: -==================== -edgedb instance stop -==================== +================= +gel instance stop +================= -Stop an EdgeDB instance. +Stop an |Gel| instance. .. cli:synopsis:: - edgedb instance stop + gel instance stop Description =========== -``edgedb instance stop`` is a terminal command for stopping a running -EdgeDB instance. This is a necessary step before +``gel instance stop`` is a terminal command for stopping a running +|Gel| instance. This is a necessary step before :ref:`destroying ` an instance. .. note:: - The ``edgedb instance stop`` command is not intended for use with + The ``gel instance stop`` command is not intended for use with self-hosted instances. @@ -29,4 +29,4 @@ Options ======= :cli:synopsis:`` - The EdgeDB instance name. + The |Gel| instance name. diff --git a/docs/cli/edgedb_instance/edgedb_instance_unlink.rst b/docs/cli/edgedb_instance/edgedb_instance_unlink.rst index 81dedc96511..59ff9c01084 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_unlink.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_unlink.rst @@ -1,20 +1,20 @@ .. _ref_cli_edgedb_instance_unlink: -====================== -edgedb instance unlink -====================== +=================== +gel instance unlink +=================== -Unlink from a previously linked remote EdgeDB instance. +Unlink from a previously linked remote Gel instance. .. cli:synopsis:: - edgedb instance unlink + gel instance unlink Description =========== -``edgedb instance unlink`` is a terminal command used to unlink a +``gel instance unlink`` is a terminal command used to unlink a remote instance. This removes the instance name from the list of valid instances. diff --git a/docs/cli/edgedb_instance/edgedb_instance_upgrade.rst b/docs/cli/edgedb_instance/edgedb_instance_upgrade.rst index 7b94497a1ab..21b5855a0d5 100644 --- a/docs/cli/edgedb_instance/edgedb_instance_upgrade.rst +++ b/docs/cli/edgedb_instance/edgedb_instance_upgrade.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_instance_upgrade: -======================= -edgedb instance upgrade -======================= +==================== +gel instance upgrade +==================== -Upgrade EdgeDB instance or installation. +Upgrade |Gel| instance or installation. .. cli:synopsis:: - edgedb instance upgrade [] [] + gel instance upgrade [] [] Description =========== -This command is used to upgrade EdgeDB instances individually or in +This command is used to upgrade |Gel| instances individually or in bulk. .. note:: - The ``edgedb instance upgrade`` command is not intended for use with + The ``gel instance upgrade`` command is not intended for use with self-hosted instances. @@ -28,7 +28,7 @@ Options ======= :cli:synopsis:`` - The EdgeDB instance name to upgrade. + The |Gel| instance name to upgrade. :cli:synopsis:`--force` Force upgrade process even if there is no new version. diff --git a/docs/cli/edgedb_instance/index.rst b/docs/cli/edgedb_instance/index.rst index 565a8c98830..af4711968a1 100644 --- a/docs/cli/edgedb_instance/index.rst +++ b/docs/cli/edgedb_instance/index.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_instance: -=============== -edgedb instance -=============== +============ +gel instance +============ -The ``edgedb instance`` group of commands contains all sorts of tools -for managing EdgeDB instances. +The ``gel instance`` group of commands contains all sorts of tools +for managing |Gel| instances. .. note:: - Most commands in the ``edgedb instance`` command group are not intended to + Most commands in the ``gel instance`` command group are not intended to manage self-hosted instances. See individual commands for more details. .. toctree:: diff --git a/docs/cli/edgedb_list.rst b/docs/cli/edgedb_list.rst index 1313f30252b..1a3bab4a716 100644 --- a/docs/cli/edgedb_list.rst +++ b/docs/cli/edgedb_list.rst @@ -1,51 +1,52 @@ .. _ref_cli_edgedb_list: -=========== -edgedb list -=========== +======== +gel list +======== List matching database objects by name and type. .. cli:synopsis:: - edgedb list [] + gel list [] Description =========== -The ``edgedb list`` group of commands contains tools for listing +The ``gel list`` group of commands contains tools for listing database objects by matching name or type. The sub-commands are organized by the type of the objects listed. Types ===== -:cli:synopsis:`edgedb list aliases` +:cli:synopsis:`gel list aliases` Display list of aliases defined in the schema. -:cli:synopsis:`edgedb list casts` +:cli:synopsis:`gel list casts` Display list of casts defined in the schema. -.. TODO: Add `edgedb list branches` once the command is added. https://github.com/edgedb/edgedb-cli/issues/1275 +:cli:synopsis:`gel list branches` + Display list of branches. -:cli:synopsis:`edgedb list databases` +:cli:synopsis:`gel list databases` Display list of databases in the server instance. -:cli:synopsis:`edgedb list indexes` +:cli:synopsis:`gel list indexes` Display list of indexes defined in the schema. -:cli:synopsis:`edgedb list modules` +:cli:synopsis:`gel list modules` Display list of modules defined in the schema. -:cli:synopsis:`edgedb list roles` +:cli:synopsis:`gel list roles` Display list of roles in the server instance. -:cli:synopsis:`edgedb list scalars` +:cli:synopsis:`gel list scalars` Display list of scalar types defined in the schema. -:cli:synopsis:`edgedb list types` +:cli:synopsis:`gel list types` Display list of object types defined in the schema. Options diff --git a/docs/cli/edgedb_migrate.rst b/docs/cli/edgedb_migrate.rst index d432ac59d31..c9acaddc674 100644 --- a/docs/cli/edgedb_migrate.rst +++ b/docs/cli/edgedb_migrate.rst @@ -1,18 +1,18 @@ .. _ref_cli_edgedb_migrate: -============== -edgedb migrate -============== +=========== +gel migrate +=========== This command is an alias for :ref:`ref_cli_edgedb_migration_apply`. Once the migration scripts are in place, the changes can be applied to the database using this command. -.. warning:: EdgeDB Cloud CI users and scripters +.. warning:: Gel Cloud CI users and scripters - When scripting a ``migrate``/``migration apply`` for an EdgeDB Cloud - instance, do not use ``edgedb login`` to authenticate. Instead, you should - generate a secret key in the EdgeDB Cloud UI or by running + When scripting a ``migrate``/``migration apply`` for an |Gel| Cloud + instance, do not use ``gel login`` to authenticate. Instead, you should + generate a secret key in the Gel Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` and set the ``EDGEDB_SECRET_KEY`` environment variable to your secret key. Once this variable is set to your secret key, logging in is no longer required. diff --git a/docs/cli/edgedb_migration/edgedb_migration_apply.rst b/docs/cli/edgedb_migration/edgedb_migration_apply.rst index 0b6a879365e..5ac3d63ddf8 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_apply.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_apply.rst @@ -1,26 +1,26 @@ .. _ref_cli_edgedb_migration_apply: -====================== -edgedb migration apply -====================== +=================== +gel migration apply +=================== Once the migration scripts are in place the changes can be applied to the database by this command: .. cli:synopsis:: - edgedb migration apply [] + gel migration apply [] The tool will find all the unapplied migrations in ``dbschema/migrations/`` directory and sequentially run them on the target instance. -.. warning:: EdgeDB Cloud CI users and scripters +.. warning:: Gel Cloud CI users and scripters - When scripting a ``migrate``/``migration apply`` for an EdgeDB Cloud - instance, do not use ``edgedb login`` to authenticate. Instead, you should - generate a secret key in the EdgeDB Cloud UI or by running + When scripting a ``migrate``/``migration apply`` for an |Gel| Cloud + instance, do not use ``gel login`` to authenticate. Instead, you should + generate a secret key in the Gel Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` and set the ``EDGEDB_SECRET_KEY`` environment variable to your secret key. Once this variable is set to your secret key, logging in is no longer required. @@ -50,11 +50,6 @@ to. For specifying the connection target see :ref:`connection options revisions are applied on top. :cli:synopsis:`--dev-mode` - .. note:: - - The ``--dev-mod`` option is compatible with EdgeDB server 3.0 and - above. - Apply the current schema changes on top of the current migration history, without having created a new migration. This works the same way as :ref:`ref_cli_edgedb_watch` but without starting a long-running watch diff --git a/docs/cli/edgedb_migration/edgedb_migration_create.rst b/docs/cli/edgedb_migration/edgedb_migration_create.rst index b39440ff9db..7a88e5f3194 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_create.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_create.rst @@ -1,16 +1,16 @@ .. _ref_cli_edgedb_migration_create: -======================= -edgedb migration create -======================= +==================== +gel migration create +==================== The next step after setting up the desired target schema is creating a migration script. This is done by invoking the following command: .. cli:synopsis:: - edgedb migration create [] + gel migration create [] This will start an interactive tool that will provide the user with suggestions based on the differences between the current branch (or database @@ -55,8 +55,4 @@ to. For specifying the connection target see :ref:`connection options ``./dbschema``. :cli:synopsis:`--squash` - .. note:: - - This CLI feature is compatible with EdgeDB server 3.0 and above. - Squashes all your migrations into a single migration. diff --git a/docs/cli/edgedb_migration/edgedb_migration_edit.rst b/docs/cli/edgedb_migration/edgedb_migration_edit.rst index 33f9a5312a2..6bbd943e9b6 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_edit.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_edit.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_migration_edit: -======================= -edgedb migration edit -======================= +================== +gel migration edit +================== Edit migration file. .. cli:synopsis:: - edgedb migration edit [] + gel migration edit [] Invokes ``$EDITOR`` on the last migration file, and then fixes migration id after editor exits. Usually should be used for migrations that haven't been diff --git a/docs/cli/edgedb_migration/edgedb_migration_extract.rst b/docs/cli/edgedb_migration/edgedb_migration_extract.rst index 963908e853f..ee6c2438dfa 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_extract.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_extract.rst @@ -1,13 +1,13 @@ .. _ref_cli_edgedb_migration_extract: -======================== -edgedb migration extract -======================== +===================== +gel migration extract +===================== Extract migration history from the database and write it to ``/dbschema/migrations``. Useful when a direct DDL command has been used to -change the schema and now ``edgedb migrate`` will not comply because the +change the schema and now ``gel migrate`` will not comply because the database migration history is ahead of the migration history inside ``/dbschema/migrations``. diff --git a/docs/cli/edgedb_migration/edgedb_migration_log.rst b/docs/cli/edgedb_migration/edgedb_migration_log.rst index 92010f7640a..9274f63f67a 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_log.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_log.rst @@ -1,18 +1,18 @@ .. _ref_cli_edgedb_migration_log: -==================== -edgedb migration log -==================== +================= +gel migration log +================= Show all migration versions. .. cli:synopsis:: - edgedb migration log [] + gel migration log [] The tool will display the migration history either by reading it from -the EdgeDB instance or from the schema directory. +the |Gel| instance or from the schema directory. Options ======= diff --git a/docs/cli/edgedb_migration/edgedb_migration_status.rst b/docs/cli/edgedb_migration/edgedb_migration_status.rst index fc3af516a3f..4d6d88d7dd0 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_status.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_status.rst @@ -1,17 +1,17 @@ .. _ref_cli_edgedb_migration_status: -======================= -edgedb migration status -======================= +==================== +gel migration status +==================== Show current migration state. .. cli:synopsis:: - edgedb migration status [] + gel migration status [] -The tool will show how the state of the schema in the EdgeDB instance +The tool will show how the state of the schema in the |Gel| instance compares to the migrations stored in the schema directory. Options diff --git a/docs/cli/edgedb_migration/edgedb_migration_upgrade_check.rst b/docs/cli/edgedb_migration/edgedb_migration_upgrade_check.rst index 3306ff7a5c9..cf3dff69026 100644 --- a/docs/cli/edgedb_migration/edgedb_migration_upgrade_check.rst +++ b/docs/cli/edgedb_migration/edgedb_migration_upgrade_check.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_migration_upgrade_check: -============================== -edgedb migration upgrade-check -============================== +=========================== +gel migration upgrade-check +=========================== -Checks your schema against a different EdgeDB version. +Checks your schema against a different |Gel| version. .. cli:synopsis:: - edgedb migration upgrade-check [] + gel migration upgrade-check [] .. note:: @@ -19,7 +19,7 @@ Description =========== By default, ``upgrade-check`` checks your schema against the latest stable -release of EdgeDB. You can add ``--to-version ``, ``--to-testing``, +release of |Gel|. You can add ``--to-version ``, ``--to-testing``, ``--to-nightly``, or ``--to-channel `` to check against a specific version. diff --git a/docs/cli/edgedb_migration/index.rst b/docs/cli/edgedb_migration/index.rst index 638767653c0..c5602911723 100644 --- a/docs/cli/edgedb_migration/index.rst +++ b/docs/cli/edgedb_migration/index.rst @@ -1,11 +1,11 @@ .. _ref_cli_edgedb_migration: -================ -edgedb migration -================ +============= +gel migration +============= -EdgeDB provides schema migration tools as server-side tools. This means that, +|Gel| provides schema migration tools as server-side tools. This means that, from the point of view of the application, migrations are language- and platform-agnostic and don't require additional libraries. @@ -52,4 +52,4 @@ single SDL document. * - :ref:`ref_cli_edgedb_migration_status` - Show current migration state * - :ref:`ref_cli_edgedb_migration_upgrade_check` - - Checks your schema against a different EdgeDB version. + - Checks your schema against a different |Gel| version. diff --git a/docs/cli/edgedb_project/edgedb_project_info.rst b/docs/cli/edgedb_project/edgedb_project_info.rst index 52deb036b68..cad797b32b9 100644 --- a/docs/cli/edgedb_project/edgedb_project_info.rst +++ b/docs/cli/edgedb_project/edgedb_project_info.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_project_info: -=================== -edgedb project info -=================== +================ +gel project info +================ Display various metadata about the project. .. cli:synopsis:: - edgedb project info [OPTIONS] + gel project info [OPTIONS] Description diff --git a/docs/cli/edgedb_project/edgedb_project_init.rst b/docs/cli/edgedb_project/edgedb_project_init.rst index 3093ffcb6c8..cad2ce6ed52 100644 --- a/docs/cli/edgedb_project/edgedb_project_init.rst +++ b/docs/cli/edgedb_project/edgedb_project_init.rst @@ -1,46 +1,42 @@ .. _ref_cli_edgedb_project_init: -=================== -edgedb project init -=================== +================ +gel project init +================ Setup a new project. .. cli:synopsis:: - edgedb project init [] + gel project init [] Description =========== This command sets up a new project, creating an instance, a schema directory, -and an :ref:`edgedb.toml ` file. It can also be used +and an :ref:`gel.toml ` file. It can also be used to convert an existing directory to a project directory, connecting the existing instance to the project. Typically this tool will prompt for specific details about how the project should be setup. -EdgeDB Cloud ------------- +Gel Cloud +--------- -.. note:: - - Creating a Cloud instance requires CLI version 3.0 or later. - -EdgeDB Cloud users may use this command to create a Cloud instance after +|Gel| Cloud users may use this command to create a Cloud instance after logging in using :ref:`ref_cli_edgedb_cloud_login`. To create a Cloud instance, your instance name should be in the format ``/``. Cloud instance names may contain alphanumeric characters and hyphens (i.e., ``-``). You can provide this Cloud instance name -through the interactive project initiation by running ``edgedb project init`` +through the interactive project initiation by running ``gel project init`` or by providing it via the ``--server-instance`` option. .. note:: - Please be aware of the following restrictions on EdgeDB Cloud instance + Please be aware of the following restrictions on |Gel| Cloud instance names: * can contain only Latin alpha-numeric characters or ``-`` @@ -54,7 +50,7 @@ Options ======= :cli:synopsis:`--link` - Specifies whether the existing EdgeDB server instance should be + Specifies whether the existing |Gel| server instance should be linked with the project. This option is useful for initializing a copy of a project freshly @@ -77,29 +73,29 @@ Options current directory. :cli:synopsis:`--server-instance=` - Specifies the EdgeDB server instance to be associated with the + Specifies the |Gel| server instance to be associated with the project. :cli:synopsis:`--server-version=` - Specifies the EdgeDB server instance to be associated with the + Specifies the Gel server instance to be associated with the project. By default, when you specify a version, the CLI will use the latest release in the major version specified. This command, for example, will install the - latest 2.x release: + latest 6.x release: .. code-block:: bash - $ edgedb project init --server-version 2.6 + $ gel project init --server-version 6.1 You may pin to a specific version by prepending the version number with an - equals sign. This command will install version 2.6: + equals sign. This command will install version 6.1: .. code-block:: bash - $ edgedb project init --server-version =2.6 + $ gel project init --server-version =6.1 .. note:: Some shells like ZSH may require you to escape the equals sign (e.g., - ``\=2.6``) or quote the version string (e.g., ``"=2.6"``). + ``\=6.1``) or quote the version string (e.g., ``"=6.1"``). diff --git a/docs/cli/edgedb_project/edgedb_project_unlink.rst b/docs/cli/edgedb_project/edgedb_project_unlink.rst index 8808f41d84a..47b736b78b7 100644 --- a/docs/cli/edgedb_project/edgedb_project_unlink.rst +++ b/docs/cli/edgedb_project/edgedb_project_unlink.rst @@ -1,23 +1,23 @@ .. _ref_cli_edgedb_project_unlink: -===================== -edgedb project unlink -===================== +================== +gel project unlink +================== -Remove association with and optionally destroy the linked EdgeDB +Remove association with and optionally destroy the linked |Gel| instance. .. cli:synopsis:: - edgedb project unlink [] + gel project unlink [] Description =========== This command unlinks the project directory from the instance. By -default the EdgeDB instance remains untouched, but it can also be +default the |Gel| instance remains untouched, but it can also be destroyed with an explicit option. @@ -25,7 +25,7 @@ Options ======= :cli:synopsis:`-D, --destroy-server-instance` - If specified, the associated EdgeDB instance is destroyed by + If specified, the associated |Gel| instance is destroyed by running :ref:`ref_cli_edgedb_instance_destroy`. :cli:synopsis:`--non-interactive` diff --git a/docs/cli/edgedb_project/edgedb_project_upgrade.rst b/docs/cli/edgedb_project/edgedb_project_upgrade.rst index b1c5249f983..11e45732d9d 100644 --- a/docs/cli/edgedb_project/edgedb_project_upgrade.rst +++ b/docs/cli/edgedb_project/edgedb_project_upgrade.rst @@ -1,15 +1,15 @@ .. _ref_cli_edgedb_project_upgrade: -====================== -edgedb project upgrade -====================== +=================== +gel project upgrade +=================== -Upgrade EdgeDB instance used for the current project +Upgrade |Gel| instance used for the current project .. cli:synopsis:: - edgedb project upgrade [] + gel project upgrade [] Description @@ -20,7 +20,7 @@ This command has two modes of operation. 1) Upgrade instance to a version specified in :ref:`ref_reference_edgedb_toml`. This happens when the command is invoked without any explicit target version. -2) Update ``edgedb.toml`` to a new version and upgrade the instance. +2) Update |gel.toml| to a new version and upgrade the instance. Which happens when one of the options for providing the target version is used. @@ -30,7 +30,7 @@ if upgrading from nightly to the stable version). .. note:: - The ``edgedb project upgrade`` command is not intended for use with + The ``gel project upgrade`` command is not intended for use with self-hosted instances. diff --git a/docs/cli/edgedb_project/index.rst b/docs/cli/edgedb_project/index.rst index 5a10e3c57fb..5bad25f3374 100644 --- a/docs/cli/edgedb_project/index.rst +++ b/docs/cli/edgedb_project/index.rst @@ -1,12 +1,12 @@ .. _ref_cli_edgedb_project: -============== -edgedb project -============== +=========== +gel project +=========== -EdgeDB provides a way to quickly setup a project. This way the project -directory gets associated with a specific EdgeDB instance and thus +|Gel| provides a way to quickly setup a project. This way the project +directory gets associated with a specific Gel instance and thus makes it the default instance to connect to. This is done by creating an :ref:`ref_reference_edgedb_toml` file in the project directory. @@ -29,4 +29,4 @@ an :ref:`ref_reference_edgedb_toml` file in the project directory. * - :ref:`ref_cli_edgedb_project_unlink` - Remove project association with an instance * - :ref:`ref_cli_edgedb_project_upgrade` - - Upgrade EdgeDB instance used for the current project + - Upgrade |Gel| instance used for the current project diff --git a/docs/cli/edgedb_query.rst b/docs/cli/edgedb_query.rst index cdb6ae82401..070f6af5bba 100644 --- a/docs/cli/edgedb_query.rst +++ b/docs/cli/edgedb_query.rst @@ -1,21 +1,21 @@ .. _ref_cli_edgedb_query: -============ -edgedb query -============ +========= +gel query +========= Execute one or more EdgeQL queries. .. cli:synopsis:: - edgedb query [] ... + gel query [] ... Description =========== -``edgedb query`` is a terminal command used to execute EdgeQL queries +``gel query`` is a terminal command used to execute EdgeQL queries provided as space-separated strings. diff --git a/docs/cli/edgedb_restore.rst b/docs/cli/edgedb_restore.rst index ca25d111a23..c0a0410680d 100644 --- a/docs/cli/edgedb_restore.rst +++ b/docs/cli/edgedb_restore.rst @@ -1,21 +1,21 @@ .. _ref_cli_edgedb_restore: -============== -edgedb restore -============== +=========== +gel restore +=========== -Restore an EdgeDB branch (or database pre-v5) from a backup file. +Restore a |Gel| branch from a backup file. .. cli:synopsis:: - edgedb restore [] + gel restore [] Description =========== -``edgedb restore`` is a terminal command used to restore an EdgeDB database +``gel restore`` is a terminal command used to restore an Gel database branch (or database pre-v5) from a backup file. The backup is restored to the currently active branch (or to the currently connected database pre-v5). @@ -26,8 +26,8 @@ currently active branch (or to the currently connected database pre-v5). - a new empty branch which can be created using :ref:`ref_cli_edgedb_branch_create` with the ``--empty`` option - - a new empty database if your instance is running EdgeDB versions prior to - 5 + - a new empty database if your instance is running |EdgeDB| versions + prior to 5 - an existing branch or database that has been wiped with the appropriate ``wipe`` command (either :ref:`ref_cli_edgedb_branch_wipe` or :ref:`ref_cli_edgedb_database_wipe`; note that this will destroy all data diff --git a/docs/cli/edgedb_server/edgedb_server_info.rst b/docs/cli/edgedb_server/edgedb_server_info.rst index 31103f96a8b..60e0ac7d8b3 100644 --- a/docs/cli/edgedb_server/edgedb_server_info.rst +++ b/docs/cli/edgedb_server/edgedb_server_info.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_server_info: -================== -edgedb server info -================== +=============== +gel server info +=============== Show server information. .. cli:synopsis:: - edgedb server info [] + gel server info [] Description =========== -``edgedb server info`` is a terminal command for displaying the -information about installed EdgeDB servers. +``gel server info`` is a terminal command for displaying the +information about installed |Gel| servers. Options diff --git a/docs/cli/edgedb_server/edgedb_server_install.rst b/docs/cli/edgedb_server/edgedb_server_install.rst index 7af813f9ad8..412291a942b 100644 --- a/docs/cli/edgedb_server/edgedb_server_install.rst +++ b/docs/cli/edgedb_server/edgedb_server_install.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_server_install: -===================== -edgedb server install -===================== +================== +gel server install +================== -Install EdgeDB server. +Install |Gel| server. .. cli:synopsis:: - edgedb server install [] + gel server install [] Description =========== -``edgedb server install`` is a terminal command for installing a -specific EdgeDB server version. +``gel server install`` is a terminal command for installing a +specific |Gel| server version. Options diff --git a/docs/cli/edgedb_server/edgedb_server_list_versions.rst b/docs/cli/edgedb_server/edgedb_server_list_versions.rst index 40a17f1c5ba..380094c3795 100644 --- a/docs/cli/edgedb_server/edgedb_server_list_versions.rst +++ b/docs/cli/edgedb_server/edgedb_server_list_versions.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_server_list_versions: -=========================== -edgedb server list-versions -=========================== +======================== +gel server list-versions +======================== -List available and installed versions of the EdgeDB server. +List available and installed versions of the |Gel| server. .. cli:synopsis:: - edgedb server list-versions [] + gel server list-versions [] Description =========== -``edgedb server list-versions`` is a terminal command for displaying -all the available EdgeDB server versions along with indicating whether +``gel server list-versions`` is a terminal command for displaying +all the available |Gel| server versions along with indicating whether or not and how they are currently installed. diff --git a/docs/cli/edgedb_server/edgedb_server_uninstall.rst b/docs/cli/edgedb_server/edgedb_server_uninstall.rst index 4571e3814f2..fafe43537a8 100644 --- a/docs/cli/edgedb_server/edgedb_server_uninstall.rst +++ b/docs/cli/edgedb_server/edgedb_server_uninstall.rst @@ -1,22 +1,22 @@ .. _ref_cli_edgedb_server_uninstall: -======================= -edgedb server uninstall -======================= +==================== +gel server uninstall +==================== -Uninstall EdgeDB server. +Uninstall |Gel| server. .. cli:synopsis:: - edgedb server uninstall [] + gel server uninstall [] Description =========== -``edgedb server uninstall`` is a terminal command for removing a -specific EdgeDB server version from your system. +``gel server uninstall`` is a terminal command for removing a +specific |Gel| server version from your system. Options diff --git a/docs/cli/edgedb_server/index.rst b/docs/cli/edgedb_server/index.rst index 3afcbeb4dfd..8164e003a40 100644 --- a/docs/cli/edgedb_server/index.rst +++ b/docs/cli/edgedb_server/index.rst @@ -1,11 +1,11 @@ .. _ref_cli_edgedb_server: -============= -edgedb server -============= +========== +gel server +========== -The ``edgedb server`` group of commands contains all sorts of tools -for managing EdgeDB server versions. +The ``gel server`` group of commands contains all sorts of tools +for managing |Gel| server versions. .. toctree:: :maxdepth: 3 @@ -22,8 +22,8 @@ for managing EdgeDB server versions. * - :ref:`ref_cli_edgedb_server_info` - Show server information * - :ref:`ref_cli_edgedb_server_install` - - Install edgedb server + - Install |Gel| server * - :ref:`ref_cli_edgedb_server_list_versions` - List available and installed versions of the server * - :ref:`ref_cli_edgedb_server_uninstall` - - Uninstall edgedb server + - Uninstall |Gel| server diff --git a/docs/cli/edgedb_ui.rst b/docs/cli/edgedb_ui.rst index d063244f42c..f6db5840abe 100644 --- a/docs/cli/edgedb_ui.rst +++ b/docs/cli/edgedb_ui.rst @@ -1,33 +1,33 @@ .. _ref_cli_edgedb_ui: -========= -edgedb ui -========= +====== +gel ui +====== -Open the EdgeDB UI of the current instance in your default browser. +Open the |Gel| UI of the current instance in your default browser. .. cli:synopsis:: - edgedb ui [] + gel ui [] Description =========== -``edgedb ui`` is a terminal command used to open the EdgeDB UI in your default +``gel ui`` is a terminal command used to open the |Gel| UI in your default browser. Alternatively, it can be used to print the UI URL with the ``--print-url`` option. -The EdgeDB UI is a tool that allows you to graphically manage and query your -EdgeDB databases. It contains a REPL, a textual and graphical view of your +The Gel UI is a tool that allows you to graphically manage and query your +Gel database. It contains a REPL, a textual and graphical view of your database schemas, and a data explorer which allows for viewing your data as a table. .. note:: The UI is served by default by development instances. To enable the UI on a - production instance, use the ``--admin-ui`` option with ``edgedb-server`` + production instance, use the ``--admin-ui`` option with ``gel-server`` or set the ``EDGEDB_SERVER_ADMIN_UI`` :ref:`environment variable ` to ``enabled``. @@ -40,7 +40,7 @@ connection target see :ref:`connection options `. :cli:synopsis:`--print-url` Print URL in console instead of opening in the browser. This is useful if - you prefer to open the EdgeDB UI in a browser other than your default + you prefer to open the Gel UI in a browser other than your default browser. :cli:synopsis:`--no-server-check` @@ -51,4 +51,4 @@ connection target see :ref:`connection options `. Media ===== -.. edb:youtube-embed:: iwnP_6tkKgc \ No newline at end of file +.. edb:youtube-embed:: iwnP_6tkKgc diff --git a/docs/cli/edgedb_watch.rst b/docs/cli/edgedb_watch.rst index f980938b27d..3f3c445a033 100644 --- a/docs/cli/edgedb_watch.rst +++ b/docs/cli/edgedb_watch.rst @@ -1,13 +1,9 @@ .. _ref_cli_edgedb_watch: -============ -edgedb watch -============ - -.. note:: - - This CLI feature is compatible with EdgeDB server 3.0 and above. +========= +gel watch +========= Start a long-running process that watches for changes in schema files in your project's ``dbschema`` directory and applies those changes to your database in @@ -15,21 +11,21 @@ real time. Starting it is as simple as running this command: .. cli:synopsis:: - edgedb watch + gel watch .. note:: - If a schema change cannot be applied, you will see an error in the ``edgedb + If a schema change cannot be applied, you will see an error in the ``gel watch`` console. You will also receive the error when you try to run a - query with any EdgeDB client binding. + query with any |Gel| client binding. -To learn about our recommended development migration workflow using ``edgedb +To learn about our recommended development migration workflow using ``gel watch``, read our :ref:`intro to migrations `. .. note:: If you want to apply a migration in the same manner as ``watch`` but - without the long-running process, use ``edgedb migrate --dev-mode``. See + without the long-running process, use ``gel migrate --dev-mode``. See :ref:`ref_cli_edgedb_migration_apply` for more details. Demo diff --git a/docs/cli/index.rst b/docs/cli/index.rst index 288af98be98..a696f27fc29 100644 --- a/docs/cli/index.rst +++ b/docs/cli/index.rst @@ -9,7 +9,7 @@ CLI :edb-alt-title: The Gel CLI The ``edgedb`` command-line interface (CLI) provides an idiomatic way to -install EdgeDB, spin up local instances, open a REPL, execute queries, +install |Gel|, spin up local instances, open a REPL, execute queries, manage auth roles, introspect schema, create migrations, and more. You can install it with one shell command. @@ -32,13 +32,13 @@ For Windows, the installation script is: PS> iwr https://ps1.edgedb.com -useb | iex * The `script `_, inspired by ``rustup``, will - detect the OS and download the appropriate build of the EdgeDB CLI - tool, ``edgedb``. + detect the OS and download the appropriate build of the Gel CLI + tool, ``gel``. * The ``edgedb`` command is a single executable (it's `open source! `_) -* Once installed, the ``edgedb`` command can be used to install, - uninstall, upgrade, and interact with EdgeDB server instances. -* You can uninstall EdgeDB server or remove the ``edgedb`` command at +* Once installed, the ``gel`` command can be used to install, + uninstall, upgrade, and interact with |Gel| server instances. +* You can uninstall Gel server or remove the ``gel`` command at any time. @@ -55,7 +55,7 @@ remotely. .. rubric:: Nightly version To install the nightly version of the CLI (not to be confused with the nightly -version of EdgeDB itself!) use this command: +version of |Gel| itself!) use this command: .. code-block:: bash @@ -72,31 +72,31 @@ macOS run: .. code-block:: bash - $ rm "$(which edgedb)" + $ rm "$(which gel)" -To remove all configuration files, run ``edgedb info`` to list the directories -where EdgeDB stores data, then use ``rm -rf `` to delete those +To remove all configuration files, run ``gel info`` to list the directories +where |Gel| stores data, then use ``rm -rf `` to delete those directories. If the command-line tool was installed by the user (recommended) then it will also remove the binary. -If you've used ``edgedb`` commands you can also delete +If you've used ``gel`` commands you can also delete :ref:`instances ` and :ref:`server ` packages, prior to removing the tool: .. code-block:: bash - $ edgedb instance destroy + $ gel instance destroy To list instances and server versions use the following commands respectively: .. code-block:: bash - $ edgedb instance status - $ edgedb server list-versions --installed-only + $ gel instance status + $ gel server list-versions --installed-only .. _ref_cli_edgedb_config: diff --git a/docs/cli/network.rst b/docs/cli/network.rst index 18e0932da20..8ae0717d57f 100644 --- a/docs/cli/network.rst +++ b/docs/cli/network.rst @@ -14,8 +14,8 @@ exceptions: docker images and also invoke package managers and the docker engine to do :ref:`index updates and related data. ` -3. The CLI communicates with the EdgeDB Cloud API to provide easy access to - your EdgeDB Cloud instances. +3. The CLI communicates with the |Gel| Cloud API to provide easy access to + your Gel Cloud instances. .. _ref_cli_edgedb_version_check: @@ -24,17 +24,17 @@ Version Check ============= Version check checks the current version of command-line tool by fetching -``https://packages.edgedb.com/.jsonindexes/*.json``. +``https://packages.geldata.com/.jsonindexes/*.json``. Here is how such a request looks like:: GET /archive/.jsonindexes/linux-x86_64.json HTTP/1.1 - host: packages.edgedb.com + host: packages.geldata.com content-length: 0 - user-agent: edgedb + user-agent: gel The ``User-Agent`` header only specifies that request is done by -``edgedb`` command-line tool (without version number). The platform, +``gel`` command-line tool (without version number). The platform, architecture and whether nightly is used can be devised from the URL of the query. @@ -59,15 +59,15 @@ logging facility can be used:: $ export RUST_LOG=edgedb::version_check=debug $ edgedb --no-cli-update-check [..snip..] Skipping version check due to --no-cli-update-check - edgedb> + gel> $ EDGEDB_RUN_VERSION_CHECK=never edgedb [..snip..] Skipping version check due to EDGEDB_RUN_VERSION_CHECK=never - edgedb> + gel> .. _ref_cli_edgedb_net_server: -``edgedb server`` and ``edgedb self upgrade`` +``gel server`` and ``gel self upgrade`` ============================================= Generally these commands do requests with exactly the headers @@ -75,22 +75,22 @@ like :ref:`version check `. Data sources for the commands directly: -1. Package indexes and packages at ``https://packages.edgedb.com`` +1. Package indexes and packages at ``https://packages.geldata.com`` 2. Docker image index at ``https://registry.hub.docker.com`` Data sources that can be used indirectly: 1. Docker engine may fetch indexes and images. Currently the only images used are at Docker Hub. More specifically - are ``edgedb/*`` and ``busybox`` (Docker's official image). + are ``gel/*`` and ``busybox`` (Docker's official image). 2. Package managers (currently ``apt-get``, ``yum``) can fetch indexes - and install packages from ``https://packages.edgedb.com``. And + and install packages from ``https://packages.geldata.com``. And as we use generic commands (e.g. ``apt-get update``) and system dependencies, package manager can fetch package indexes and package data from any sources listed in repositories configured in the system. -To avoid reaching these hosts, avoid using: ``edgedb server`` and -``edgedb self upgrade`` subcommands. These commands only simplify -installation and maintenance of the installations. All EdgeDB features +To avoid reaching these hosts, avoid using: ``gel server`` and +``gel self upgrade`` subcommands. These commands only simplify +installation and maintenance of the installations. All |Gel| features are available without using them. diff --git a/docs/clients/connection.rst b/docs/clients/connection.rst index 570abea0bd8..bcb8a0bb24f 100644 --- a/docs/clients/connection.rst +++ b/docs/clients/connection.rst @@ -16,26 +16,26 @@ library. - Set the ``EDGEDB_DSN`` environment variable to a valid DSN (connection string). This is the recommended approach in *production*. A DSN is a - connection URL of the form ``edgedb://user:pass@host:port/branch``. For a + connection URL of the form ``gel://user:pass@host:port/branch``. For a guide to DSNs, see the :ref:`DSN Specification `. - Set the ``EDGEDB_INSTANCE`` environment variable to a :ref:`name ` of a local instance, remote linked - instance, or an EdgeDB Cloud instance. (:ref:`More info on EdgeDB Cloud + instance, or an |Gel| Cloud instance. (:ref:`More info on |Gel| Cloud connection details below. `) You can create - new instances manually with the :ref:`edgedb instance create + new instances manually with the :ref:`gel instance create ` command. - Explicitly pass a DSN or :ref:`instance name ` into the client creation function: - ``edgedb.createClient`` in JS, ``edgedb.create_client()`` in Python, and - ``edgedb.CreateClient`` in Go. + ``gel.createClient`` in JS, ``gel.create_client()`` in Python, and + ``gel.CreateClient`` in Go. .. code-block:: typescript - const client = edgedb.createClient({ - dsn: "edgedb://..." + const client = gel.createClient({ + dsn: "gel://..." }); Only use this approach in development; it isn't recommended to include @@ -44,19 +44,19 @@ library. providers, and container-based workflows each provide various mechanisms for setting environment variables. -These are the most common ways to connect to an instance, however EdgeDB +These are the most common ways to connect to an instance, however |Gel| supports several other options for advanced use cases. For a complete reference on connection configuration, see :ref:`Reference > Connection Parameters `. .. _edgedb_client_connection_cloud: -EdgeDB Cloud -============ +Gel Cloud +========= -To provide client connection information for an EdgeDB Cloud instance, set the +To provide client connection information for an |Gel| Cloud instance, set the ``EDGEDB_INSTANCE`` variable to the instance name (``/`` where ```` is the name you set -when you created the EdgeDB Cloud instance) and the ``EDGEDB_SECRET_KEY`` -variable to your secret key which can be created in the EdgeDB Cloud UI or by +when you created the Gel Cloud instance) and the ``EDGEDB_SECRET_KEY`` +variable to your secret key which can be created in the Gel Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` via the CLI. diff --git a/docs/clients/go/api.rst b/docs/clients/go/api.rst index 2f7a815eeb7..2b12242d249 100644 --- a/docs/clients/go/api.rst +++ b/docs/clients/go/api.rst @@ -11,29 +11,29 @@ Client is a connection pool and is safe for concurrent use. .. code-block:: go - type Client = edgedb.Client + type Client = gel.Client *type* Error ------------ -Error is the error type returned from edgedb. +Error is the error type returned from gel. .. code-block:: go - type Error = edgedb.Error + type Error = gel.Error *type* ErrorCategory -------------------- -ErrorCategory values represent EdgeDB's error types. +ErrorCategory values represent Gel's error types. .. code-block:: go - type ErrorCategory = edgedb.ErrorCategory + type ErrorCategory = gel.ErrorCategory *type* ErrorTag @@ -44,31 +44,31 @@ ErrorTag is the argument type to Error.HasTag(). .. code-block:: go - type ErrorTag = edgedb.ErrorTag + type ErrorTag = gel.ErrorTag *type* Executor --------------- Executor is a common interface between \*Client and \*Tx, -that can run queries on an EdgeDB database. +that can run queries on an Gel database. .. code-block:: go - type Executor = edgedb.Executor + type Executor = gel.Executor *type* IsolationLevel --------------------- IsolationLevel documentation can be found here -`docs/reference/edgeql/tx_start#parameters `_ +`docs/reference/edgeql/tx_start#parameters `_ .. code-block:: go - type IsolationLevel = edgedb.IsolationLevel + type IsolationLevel = gel.IsolationLevel *type* ModuleAlias @@ -79,18 +79,18 @@ ModuleAlias is an alias name and module name pair. .. code-block:: go - type ModuleAlias = edgedb.ModuleAlias + type ModuleAlias = gel.ModuleAlias *type* Options -------------- -Options for connecting to an EdgeDB server +Options for connecting to an |Gel| server .. code-block:: go - type Options = edgedb.Options + type Options = gel.Options *type* RetryBackoff @@ -102,7 +102,7 @@ before making the next attempt when retrying a transaction. .. code-block:: go - type RetryBackoff = edgedb.RetryBackoff + type RetryBackoff = gel.RetryBackoff *type* RetryCondition @@ -114,7 +114,7 @@ run in Tx() methods to be retried. .. code-block:: go - type RetryCondition = edgedb.RetryCondition + type RetryCondition = gel.RetryCondition *type* RetryOptions @@ -127,7 +127,7 @@ yourself. .. code-block:: go - type RetryOptions = edgedb.RetryOptions + type RetryOptions = gel.RetryOptions *type* RetryRule @@ -139,19 +139,19 @@ methods. See Client.Tx() for details. .. code-block:: go - type RetryRule = edgedb.RetryRule + type RetryRule = gel.RetryRule *type* TLSOptions ----------------- -TLSOptions contains the parameters needed to configure TLS on EdgeDB +TLSOptions contains the parameters needed to configure TLS on |Gel| server connections. .. code-block:: go - type TLSOptions = edgedb.TLSOptions + type TLSOptions = gel.TLSOptions *type* TLSSecurityMode @@ -162,7 +162,7 @@ TLSSecurityMode specifies how strict TLS validation is. .. code-block:: go - type TLSSecurityMode = edgedb.TLSSecurityMode + type TLSSecurityMode = gel.TLSSecurityMode *type* Tx @@ -173,7 +173,7 @@ Tx is a transaction. Use Client.Tx() to get a transaction. .. code-block:: go - type Tx = edgedb.Tx + type Tx = gel.Tx *type* TxBlock @@ -184,7 +184,7 @@ TxBlock is work to be done in a transaction. .. code-block:: go - type TxBlock = edgedb.TxBlock + type TxBlock = gel.TxBlock *type* TxOptions @@ -195,17 +195,17 @@ TxOptions configures how transactions behave. .. code-block:: go - type TxOptions = edgedb.TxOptions + type TxOptions = gel.TxOptions *type* WarningHandler --------------------- -WarningHandler takes a slice of edgedb.Error that represent warnings and +WarningHandler takes a slice of gel.Error that represent warnings and optionally returns an error. This can be used to log warnings, increment metrics, promote warnings to errors by returning them etc. .. code-block:: go - type WarningHandler = edgedb.WarningHandler \ No newline at end of file + type WarningHandler = gel.WarningHandler diff --git a/docs/clients/go/codegen.rst b/docs/clients/go/codegen.rst index 636a6e17190..e451de48f2a 100644 --- a/docs/clients/go/codegen.rst +++ b/docs/clients/go/codegen.rst @@ -2,7 +2,7 @@ Codegen ======= edgeql-go is a tool to generate go functions from edgeql queries. When run -in an EdgeDB project directory (or subdirectory) a \*_edgeql.go source file +in an |Gel| project directory (or subdirectory) a \*_edgeql.go source file will be generated for each \*.edgeql file. The generated go will have an edgeqlFileName and edgeqlFileNameJSON function with typed arguments and return value matching the query's arguments and result shape. @@ -13,7 +13,7 @@ Install .. code-block:: go - go install github.com/edgedb/edgedb-go/cmd/edgeql-go@latest + go install github.com/geldata/gel-go/cmd/edgeql-go@latest See also `pinning tool dependencies `_. diff --git a/docs/clients/go/index.rst b/docs/clients/go/index.rst index 49eee7a35df..506353d647b 100644 --- a/docs/clients/go/index.rst +++ b/docs/clients/go/index.rst @@ -1,8 +1,8 @@ -.. _edgedb-go-intro: +.. _gel-go-intro: -================ -EdgeDB Go Driver -================ +============= +Gel Go Driver +============= .. toctree:: @@ -14,11 +14,6 @@ EdgeDB Go Driver codegen - -Package edgedb is the official Go driver for `EdgeDB `_. Additionally, -`edgeql-go `_ is a code generator that -generates go functions from edgeql files. - Typical client usage looks like this: .. code-block:: go @@ -29,12 +24,12 @@ Typical client usage looks like this: "context" "log" - "github.com/edgedb/edgedb-go" + "github.com/geldata/gel-go" ) func main() { ctx := context.Background() - client, err := edgedb.CreateClient(ctx, edgedb.Options{}) + client, err := gel.CreateClient(ctx, gel.Options{}) if err != nil { log.Fatal(err) } @@ -43,8 +38,8 @@ Typical client usage looks like this: var ( age int64 = 21 users []struct { - ID edgedb.UUID `edgedb:"id"` - Name string `edgedb:"name"` + ID gel.UUID `gel:"id"` + Name string `gel:"name"` } ) @@ -54,32 +49,32 @@ Typical client usage looks like this: } We recommend using environment variables for connection parameters. See the -`client connection docs `_ for more information. +`client connection docs `_ for more information. You may also connect to a database using a DSN: .. code-block:: go - url := "edgedb://edgedb@localhost/edgedb" - client, err := edgedb.CreateClientDSN(ctx, url, opts) + url := "gel://admin@localhost/gel" + client, err := gel.CreateClientDSN(ctx, url, opts) Or you can use Option fields. .. code-block:: go - opts := edgedb.Options{ - Database: "edgedb", - User: "edgedb", + opts := gel.Options{ + Database: "gel", + User: "gel", Concurrency: 4, } - client, err := edgedb.CreateClient(ctx, opts) + client, err := gel.CreateClient(ctx, opts) Errors ------ -edgedb never returns underlying errors directly. +``gel`` never returns underlying errors directly. If you are checking for things like context expiration use errors.Is() or errors.As(). @@ -88,15 +83,15 @@ use errors.Is() or errors.As(). err := client.Query(...) if errors.Is(err, context.Canceled) { ... } -Most errors returned by the edgedb package will satisfy the edgedb.Error +Most errors returned by the gel package will satisfy the gel.Error interface which has methods for introspecting. .. code-block:: go err := client.Query(...) - var edbErr edgedb.Error - if errors.As(err, &edbErr) && edbErr.Category(edgedb.NoDataError){ + var edbErr gel.Error + if errors.As(err, &edbErr) && edbErr.Category(gel.NoDataError){ ... } @@ -105,53 +100,53 @@ Datatypes --------- The following list shows the marshal/unmarshal -mapping between EdgeDB types and go types: +mapping between |Gel| types and go types: .. code-block:: go - EdgeDB Go + Gel Go --------- --------- Set []anytype array []anytype tuple struct named tuple struct Object struct - bool bool, edgedb.OptionalBool - bytes []byte, edgedb.OptionalBytes - str string, edgedb.OptionalStr - anyenum string, edgedb.OptionalStr - datetime time.Time, edgedb.OptionalDateTime - cal::local_datetime edgedb.LocalDateTime, - edgedb.OptionalLocalDateTime - cal::local_date edgedb.LocalDate, edgedb.OptionalLocalDate - cal::local_time edgedb.LocalTime, edgedb.OptionalLocalTime - duration edgedb.Duration, edgedb.OptionalDuration - cal::relative_duration edgedb.RelativeDuration, - edgedb.OptionalRelativeDuration - float32 float32, edgedb.OptionalFloat32 - float64 float64, edgedb.OptionalFloat64 - int16 int16, edgedb.OptionalFloat16 - int32 int32, edgedb.OptionalInt16 - int64 int64, edgedb.OptionalInt64 - uuid edgedb.UUID, edgedb.OptionalUUID - json []byte, edgedb.OptionalBytes - bigint *big.Int, edgedb.OptionalBigInt + bool bool, gel.OptionalBool + bytes []byte, gel.OptionalBytes + str string, gel.OptionalStr + anyenum string, gel.OptionalStr + datetime time.Time, gel.OptionalDateTime + cal::local_datetime gel.LocalDateTime, + gel.OptionalLocalDateTime + cal::local_date gel.LocalDate, gel.OptionalLocalDate + cal::local_time gel.LocalTime, gel.OptionalLocalTime + duration gel.Duration, gel.OptionalDuration + cal::relative_duration gel.RelativeDuration, + gel.OptionalRelativeDuration + float32 float32, gel.OptionalFloat32 + float64 float64, gel.OptionalFloat64 + int16 int16, gel.OptionalFloat16 + int32 int32, gel.OptionalInt16 + int64 int64, gel.OptionalInt64 + uuid gel.UUID, gel.OptionalUUID + json []byte, gel.OptionalBytes + bigint *big.Int, gel.OptionalBigInt decimal user defined (see Custom Marshalers) -Note that EdgeDB's std::duration type is represented in int64 microseconds +Note that Gel's std::duration type is represented in int64 microseconds while go's time.Duration type is int64 nanoseconds. It is incorrect to cast one directly to the other. Shape fields that are not required must use optional types for receiving -query results. The edgedb.Optional struct can be embedded to make structs +query results. The gel.Optional struct can be embedded to make structs optional. .. code-block:: go type User struct { - edgedb.Optional - Email string `edgedb:"email"` + gel.Optional + Email string `gel:"email"` } var result User @@ -164,29 +159,29 @@ optional. // Output: false Not all types listed above are valid query parameters. To pass a slice of -scalar values use array in your query. EdgeDB doesn't currently support +scalar values use array in your query. |Gel| doesn't currently support using sets as parameters. .. code-block:: go query := `select User filter .id in array_unpack(>$1)` - client.QuerySingle(ctx, query, $user, []edgedb.UUID{...}) + client.QuerySingle(ctx, query, $user, []gel.UUID{...}) -Nested structures are also not directly allowed but you can use `json `_ +Nested structures are also not directly allowed but you can use `json `_ instead. -By default EdgeDB will ignore embedded structs when marshaling/unmarshaling. +By default |Gel| will ignore embedded structs when marshaling/unmarshaling. To treat an embedded struct's fields as part of the parent struct's fields, -tag the embedded struct with \`edgedb:"$inline"\`. +tag the embedded struct with \`gel:"$inline"\`. .. code-block:: go type Object struct { - ID edgedb.UUID + ID gel.UUID } type User struct { - Object `edgedb:"$inline"` + Object `gel:"$inline"` Name string } @@ -204,7 +199,7 @@ Usage Example .. code-block:: go - package edgedb_test + package gel_test import ( "context" @@ -212,19 +207,19 @@ Usage Example "log" "time" - edgedb "github.com/edgedb/edgedb-go" + gel "github.com/gel/gel-go" ) type User struct { - ID edgedb.UUID `edgedb:"id"` - Name string `edgedb:"name"` - DOB time.Time `edgedb:"dob"` + ID gel.UUID `gel:"id"` + Name string `gel:"name"` + DOB time.Time `gel:"dob"` } func Example() { - opts := edgedb.Options{Concurrency: 4} + opts := gel.Options{Concurrency: 4} ctx := context.Background() - db, err := edgedb.CreateClientDSN(ctx, "edgedb://edgedb@localhost/test", opts) + db, err := gel.CreateClientDSN(ctx, "gel://gel@localhost/test", opts) if err != nil { log.Fatal(err) } @@ -242,7 +237,7 @@ Usage Example } // Insert a new user. - var inserted struct{ id edgedb.UUID } + var inserted struct{ id gel.UUID } err = db.QuerySingle(ctx, ` INSERT User { name := $0, diff --git a/docs/clients/go/types.rst b/docs/clients/go/types.rst index 9d521a8eb79..14a60074e12 100644 --- a/docs/clients/go/types.rst +++ b/docs/clients/go/types.rst @@ -94,7 +94,7 @@ from a `time.Duration `_ represented as nanoseconds. func ParseDuration(s string) (Duration, error) -ParseDuration parses an EdgeDB duration string. +ParseDuration parses an |Gel| duration string. @@ -128,7 +128,7 @@ the internal int64 representation. ---------------- LocalDate is a date without a time zone. -`docs/stdlib/datetime#type::cal::local_date `_ +`docs/stdlib/datetime#type::cal::local_date `_ .. code-block:: go @@ -188,7 +188,7 @@ UnmarshalText unmarshals bytes into \*d. -------------------- LocalDateTime is a date and time without timezone. -`docs/stdlib/datetime#type::cal::local_datetime `_ +`docs/stdlib/datetime#type::cal::local_datetime `_ .. code-block:: go @@ -250,7 +250,7 @@ UnmarshalText unmarshals bytes into \*dt. ---------------- LocalTime is a time without a time zone. -`docs/stdlib/datetime#type::cal::local_time `_ +`docs/stdlib/datetime#type::cal::local_time `_ .. code-block:: go @@ -439,8 +439,8 @@ Optional is embedded in structs to make them optional. For example: .. code-block:: go type User struct { - edgedb.Optional - Name string `edgedb:"name"` + gel.Optional + Name string `gel:"name"` } @@ -3516,7 +3516,7 @@ UnmarshalText unmarshals bytes into \*rd. ----------- UUID is a universally unique identifier -`docs/stdlib/uuid `_ +`docs/stdlib/uuid `_ .. code-block:: go diff --git a/docs/clients/graphql/graphql.rst b/docs/clients/graphql/graphql.rst index 49b0c238ea9..b6f79676d36 100644 --- a/docs/clients/graphql/graphql.rst +++ b/docs/clients/graphql/graphql.rst @@ -24,7 +24,7 @@ containing the following schema: } } -From the schema above, EdgeDB will expose to GraphQL: +From the schema above, |Gel| will expose to GraphQL: * object types ``Author`` and ``Book`` * scalars ``String`` and ``ID`` diff --git a/docs/clients/graphql/index.rst b/docs/clients/graphql/index.rst index e199b893e8a..f83338ca836 100644 --- a/docs/clients/graphql/index.rst +++ b/docs/clients/graphql/index.rst @@ -17,7 +17,7 @@ GraphQL cheatsheet -EdgeDB supports `GraphQL queries`__ via the built-in ``graphql`` extension. A +|Gel| supports `GraphQL queries`__ via the built-in ``graphql`` extension. A full CRUD API for all object types, their properties (both material and computed), their links, and all :ref:`aliases ` is reflected in the GraphQL schema. @@ -36,8 +36,8 @@ Then create a new migration and apply it. .. code-block:: bash - $ edgedb migration create - $ edgedb migrate + $ gel migration create + $ gel migrate Refer to the :ref:`connection docs ` for various methods of running these commands against remotely-hosted instances. @@ -54,28 +54,28 @@ The default ``branch-name`` will be ``main``, and after initializing your database, all queries are executed against it by default. If you want to query another branch instead, simply use that branch name in the URL. -To find the port number associated with a local instance, run ``edgedb +To find the port number associated with a local instance, run ``gel instance list``. .. code-block:: bash - $ edgedb instance list + $ gel instance list ┌────────┬──────────────┬──────────┬───────────────┬─────────────┐ │ Kind │ Name │ Port │ Version │ Status │ ├────────┼──────────────┼──────────┼───────────────┼─────────────┤ - │ local │ inst1 │ 10700 │ 2.x │ running │ - │ local │ inst2 │ 10702 │ 2.x │ running │ - │ local │ inst3 │ 10703 │ 2.x │ running │ + │ local │ inst1 │ 10700 │ 6.x │ running │ + │ local │ inst2 │ 10702 │ 6.x │ running │ + │ local │ inst3 │ 10703 │ 6.x │ running │ └────────┴──────────────┴──────────┴───────────────┴─────────────┘ To execute a GraphQL query against the branch ``main`` on the instance named ``inst2``, we would send an HTTP request to -``http://localhost:10702/branch/edgedb/main``. +``http://localhost:10702/branch/gel/main``. -To determine the URL of an EdgeDB Cloud instance, find the host by running -``edgedb instance credentials -I /``. Use the +To determine the URL of an |Gel| Cloud instance, find the host by running +``gel instance credentials -I /``. Use the ``host`` and ``port`` from that table in the URL format at the top of this -section. Change the protocol to ``https`` since EdgeDB Cloud instances are +section. Change the protocol to ``https`` since Gel Cloud instances are secured with TLS. .. note:: @@ -99,7 +99,7 @@ Authentication for the GraphQL endpoint is identical to that for the The protocol ------------ -EdgeDB can recieve GraphQL queries via both ``GET`` and ``POST`` requests. +|Gel| can recieve GraphQL queries via both ``GET`` and ``POST`` requests. Requests can contain the following fields: - ``query`` - the GraphQL query string @@ -223,11 +223,11 @@ There are also some additional limitations: - :ref:`Link properties` are not reflected, as GraphQL has no such concept. -- Every non-abstract EdgeDB object type is simultaneously an interface +- Every non-abstract |Gel| object type is simultaneously an interface and an object in terms of the GraphQL type system, which means that, for every one object type name, two names are needed in reflected GraphQL. This potentially results in name clashes if the convention - of using camel-case names for user types is not followed in EdgeDB. + of using camel-case names for user types is not followed in Gel. .. __: http://graphql.org/docs/queries/ diff --git a/docs/clients/graphql/introspection.rst b/docs/clients/graphql/introspection.rst index 42ff63841d6..6a2e8f51298 100644 --- a/docs/clients/graphql/introspection.rst +++ b/docs/clients/graphql/introspection.rst @@ -4,9 +4,9 @@ Introspection ============= -GraphQL introspection can be used to explore the exposed EdgeDB types +GraphQL introspection can be used to explore the exposed |Gel| types and expresssion aliases. Note that there are certain types like -:eql:type:`tuple` that cannot be expressed in terms of the GraphQL +:eql:type:`tuple` that cannot be expressed in terms of the GraphQL type system (a ``tuple`` can be like a heterogeneous "List"). Consider the following GraphQL introspection query: diff --git a/docs/clients/http/index.rst b/docs/clients/http/index.rst index fac728a8fc5..51c6adead46 100644 --- a/docs/clients/http/index.rst +++ b/docs/clients/http/index.rst @@ -11,7 +11,7 @@ EdgeQL over HTTP protocol health-checks -EdgeDB can expose an HTTP endpoint for EdgeQL queries. Since HTTP is a +|Gel| can expose an HTTP endpoint for EdgeQL queries. Since HTTP is a stateless protocol, no :ref:`DDL `, :ref:`transaction commands `, can be executed using this endpoint. Only one query per request can be @@ -33,26 +33,26 @@ Your instance can now receive EdgeQL queries over HTTP at .. note:: - Here's how to determine your local EdgeDB instance's HTTP server URL: + Here's how to determine your local |Gel| instance's HTTP server URL: - The ``hostname`` will be ``localhost`` - - Find the ``port`` by running ``edgedb instance list``. This will print a - table of all EdgeDB instances on your machine, including their associated + - Find the ``port`` by running ``gel instance list``. This will print a + table of all |Gel| instances on your machine, including their associated port number. - The default ``branch-name`` will be ``main``, and after initializing your database, all queries are executed against it by default. If you want to query another branch instead, simply use that branch name in the URL. - To determine the URL of an EdgeDB Cloud instance, find the host by running - ``edgedb instance credentials -I /``. Use the + To determine the URL of an |Gel| Cloud instance, find the host by running + ``gel instance credentials -I /``. Use the ``host`` and ``port`` from that table in the URL format above this note. - Change the protocol to ``https`` since EdgeDB Cloud instances are secured + Change the protocol to ``https`` since Gel Cloud instances are secured with TLS. To determine the URL of a self-hosted remote instance you have linked with the CLI, you can get both the hostname and port of the instance from the - "Port" column of the ``edgedb instance list`` table (formatted as + "Port" column of the ``gel instance list`` table (formatted as ``:``). The same guidance on local branch names applies here. @@ -70,7 +70,7 @@ By default, the HTTP endpoint uses :eql:type:`cfg::Password` based authentication, in which `HTTP Basic Authentication `_ -is used to provide an EdgeDB username and password. +is used to provide an |Gel| username and password. .. lint-on @@ -81,7 +81,7 @@ mechanism can be configured by adjusting which If :eql:type:`cfg::JWT` is used, the requests should contain these headers: -* ``X-EdgeDB-User``: The EdgeDB username. +* ``X-Gel-User``: The |Gel| username. * ``Authorization``: The JWT authorization token prefixed by ``Bearer``. @@ -96,8 +96,8 @@ behavior:: ... }; OK: CONFIGURE INSTANCE -To authenticate to your EdgeDB Cloud instance, first create a secret key using -the EdgeDB Cloud UI or :ref:`ref_cli_edgedb_cloud_secretkey_create`. Use the +To authenticate to your |Gel| Cloud instance, first create a secret key using +the Gel Cloud UI or :ref:`ref_cli_edgedb_cloud_secretkey_create`. Use the secret key as your token with the bearer authentication method. Here is an example showing how you might send the query ``select Person {*};`` using cURL: diff --git a/docs/clients/js/driver.rst b/docs/clients/js/driver.rst index 4455b59b60c..0868a97f99d 100644 --- a/docs/clients/js/driver.rst +++ b/docs/clients/js/driver.rst @@ -25,9 +25,9 @@ To create a client: .. code-block:: javascript - const edgedb = require("edgedb"); + const gel = require("gel"); - const client = edgedb.createClient(); + const client = gel.createClient(); If you're using TypeScript or have ES modules enabled, you can use @@ -35,9 +35,9 @@ If you're using TypeScript or have ES modules enabled, you can use .. code-block:: javascript - import * as edgedb from "edgedb"; + import * as gel from "gel"; - const client = edgedb.createClient(); + const client = gel.createClient(); Connections @@ -45,7 +45,7 @@ Connections Notice we didn't pass any arguments into ``createClient``. That's intentional. -**In development**, we recommend using ``edgedb project init`` to create an +**In development**, we recommend using ``gel project init`` to create an instance and link it to your project directory. As long as you're inside this directory, ``createClient()`` with auto-detect the project and connect to the associated instance automatically. @@ -71,7 +71,7 @@ value specified below is the *default value* for that setting. .. code-block:: typescript - import {createClient, Duration, IsolationLevel} from "edgedb"; + import {createClient, Duration, IsolationLevel} from "gel"; const baseClient = createClient(); const client = baseClient @@ -104,9 +104,9 @@ To execute a basic query: .. code-block:: javascript - const edgedb = require("edgedb"); + const gel = require("gel"); - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { const result = await client.query(`select 2 + 2;`); @@ -139,7 +139,7 @@ constraints on cardinality. ``.querySingle`` method ^^^^^^^^^^^^^^^^^^^^^^^ -If you know your query will only return a single element, you can tell EdgeDB +If you know your query will only return a single element, you can tell |Gel| to expect a *singleton result* by using the ``.querySingle`` method. This is intended for queries that return *zero or one* elements. If the query returns a set with more than one elements, the ``Client`` will throw a runtime error. @@ -190,14 +190,14 @@ The TypeScript signatures of these methods reflects their behavior. Type conversion --------------- -The client converts EdgeDB types into a corresponding JavaScript data -structure. Some EdgeDB types like ``duration`` don't have a corresponding type +The client converts |Gel| types into a corresponding JavaScript data +structure. Some Gel types like ``duration`` don't have a corresponding type in the JavaScript type system, so we've implemented classes like :js:class:`Duration` to represent them. .. list-table:: - * - **EdgeDB type** + * - **Gel type** - **JavaScript type** * - Sets - ``Array`` @@ -260,13 +260,6 @@ documentation. - :js:class:`Range` -.. .. note:: - -.. **A message for query builder users** - -.. Everything below this point isn't necessary/applicable for query builder users. Continue to the :ref:`Query Builder ` docs. - - JSON results ------------ @@ -298,7 +291,7 @@ query to return a value. title := "Avengers: Endgame" };`); -With EdgeDB 2.0 or later, you can execute a "script" consisting of multiple +You can also execute a "script" consisting of multiple semicolon-separated statements in a single ``.execute`` call. .. code-block:: javascript @@ -376,9 +369,9 @@ a query, use the ``.ensureConnected()`` method. .. code-block:: javascript - const edgedb = require("edgedb"); + const gel = require("gel"); - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { await client.ensureConnected(); @@ -421,7 +414,7 @@ JavaScript code. Here is an example: .. code-block:: javascript - const email = "timmy@edgedb.com" + const email = "timmy@geldata.com" await client.transaction(async tx => { await tx.execute( @@ -455,7 +448,7 @@ shouldn't have side effects or run for a significant amount of time. * RFC1004_ * :js:meth:`Client.transaction\` - .. _RFC1004: https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst + .. _RFC1004: https://github.com/geldata/rfcs/blob/master/text/1004-transactions-api.rst Next up diff --git a/docs/clients/js/for.rst b/docs/clients/js/for.rst index 70c100a2edb..ca80a7cea6a 100644 --- a/docs/clients/js/for.rst +++ b/docs/clients/js/for.rst @@ -215,7 +215,7 @@ We need to separate this movie insert query so that we can use ``distinct`` on it. We could just nest an insert inside our character insert if movies weren't duplicated across characters (e.g., two characters have "The Avengers" in ``movies``). Even though the query is separated from the character inserts -here, it will still be built as part of a single EdgeDB query using ``with`` +here, it will still be built as part of a single |Gel| query using ``with`` which we'll get to a bit later. The ``distinct`` operator can only operate on sets. We use ``array_unpack`` to diff --git a/docs/clients/js/generation.rst b/docs/clients/js/generation.rst index 759e3133306..6b8502080d8 100644 --- a/docs/clients/js/generation.rst +++ b/docs/clients/js/generation.rst @@ -3,13 +3,13 @@ Generators ========== -The ``@edgedb/generate`` package provides a set of code generation tools that -are useful when developing an EdgeDB-backed applications with +The ``@gel/generate`` package provides a set of code generation tools that +are useful when developing an Gel-backed applications with TypeScript/JavaScript. -To get started with generators, first initialize an :ref:`EdgeDB project +To get started with generators, first initialize an :ref:`Gel project ` in the root of your application. Generators will -look for an ``edgedb.toml`` file to determine the root of your application. See +look for an |gel.toml| file to determine the root of your application. See the :ref:`Overview ` page for details on installing. .. note:: @@ -23,7 +23,7 @@ Run a generator with the following command. .. code-tab:: bash :caption: npm - $ npx @edgedb/generate [options] + $ npx @gel/generate [options] .. code-tab:: bash :caption: yarn @@ -41,12 +41,12 @@ Run a generator with the following command. $ deno run \ --allow-all \ --unstable \ - https://deno.land/x/edgedb/generate.ts [options] + https://deno.land/x/gel/generate.ts [options] .. code-tab:: bash :caption: bun - $ bunx @edgedb/generate [options] + $ bunx @gel/generate [options] The value of ```` should be one of the following: @@ -67,14 +67,14 @@ The value of ```` should be one of the following: * - ``interfaces`` - Introspects your schema and generates file containing *TypeScript interfaces* that correspond to each object type. This is useful for - writing typesafe code to interact with EdgeDB. + writing typesafe code to interact with |Gel|. - :ref:`docs ` Connection ^^^^^^^^^^ -The generators require a connection to an active EdgeDB database. It does -**not** simply read your local ``.esdl`` schema files. Generators rely on the +The generators require a connection to an active |Gel| database. It does +**not** simply read your local |.gel| schema files. Generators rely on the database to introspect the schema and analyze queries. Doing so without a database connection would require implementing a full EdgeQL parser and static analyzer in JavaScript—which we don't intend to do anytime soon. @@ -84,7 +84,7 @@ analyzer in JavaScript—which we don't intend to do anytime soon. Make sure your development database is up-to-date with your latest schema before running a generator! -If you're using ``edgedb project init``, the connection is automatically handled +If you're using ``gel project init``, the connection is automatically handled for you. Otherwise, you'll need to explicitly pass connection information via environment variables or CLI flags, just like any other CLI command. See :ref:`Client Libraries > Connection ` for guidance. @@ -115,8 +115,8 @@ modules). You can override this with the ``--target`` flag. Help ^^^^ -To see helptext for the ``@edgedb/generate`` command, run the following. +To see helptext for the ``@gel/generate`` command, run the following. .. code-block:: bash - $ npx @edgedb/generate --help + $ npx @gel/generate --help diff --git a/docs/clients/js/group.rst b/docs/clients/js/group.rst index 63fbf7525a5..4ddec308a1f 100644 --- a/docs/clients/js/group.rst +++ b/docs/clients/js/group.rst @@ -3,10 +3,6 @@ Group ===== -.. note:: - - The ``group`` statement is only available in EdgeDB 2.0 or later. - The ``group`` statement provides a powerful mechanism for categorizing a set of objects (e.g., movies) into *groups*. You can group by properties, expressions, or combinatations thereof. diff --git a/docs/clients/js/index.rst b/docs/clients/js/index.rst index ea45a11dcf4..7169d3393c0 100644 --- a/docs/clients/js/index.rst +++ b/docs/clients/js/index.rst @@ -1,8 +1,8 @@ .. _edgedb-js-intro: -=========================== -EdgeDB TypeScript/JS Client -=========================== +======================== +Gel TypeScript/JS Client +======================== .. toctree:: :maxdepth: 3 @@ -27,10 +27,6 @@ EdgeDB TypeScript/JS Client group reference -This is the official EdgeDB client library for JavaScript and TypeScript. It's -the easiest way to connect to your database and execute queries from a Node.js -or Deno backend. - .. _edgedb-js-installation: @@ -45,31 +41,31 @@ generators from npm using your package manager of choice. .. code-tab:: bash :caption: npm - $ npm install --save-prod edgedb # database driver - $ npm install --save-dev @edgedb/generate # generators + $ npm install --save-prod gel # database driver + $ npm install --save-dev @gel/generate # generators .. code-tab:: bash :caption: yarn - $ yarn add edgedb # database driver - $ yarn add --dev @edgedb/generate # generators + $ yarn add gel # database driver + $ yarn add --dev @gel/generate # generators .. code-tab:: bash :caption: pnpm - $ pnpm add --save-prod edgedb # database driver - $ pnpm add --save-dev @edgedb/generate # generators + $ pnpm add --save-prod gel # database driver + $ pnpm add --save-dev @gel/generate # generators .. code-tab:: typescript :caption: deno - import * as edgedb from "http://deno.land/x/edgedb/mod.ts"; + import * as gel from "http://deno.land/x/gel/mod.ts"; .. code-tab:: bash :caption: bun - $ bun add edgedb # database driver - $ bun add --dev @edgedb/generate # generators + $ bun add gel # database driver + $ bun add --dev @gel/generate # generators .. note:: Deno users @@ -80,8 +76,8 @@ generators from npm using your package manager of choice. { "imports": { - "edgedb": "https://deno.land/x/edgedb/mod.ts", - "edgedb/": "https://deno.land/x/edgedb/" + "gel": "https://deno.land/x/gel/mod.ts", + "gel/": "https://deno.land/x/gel/" } } @@ -103,7 +99,7 @@ Setup This section assumes you have gone through the :ref:`Quickstart Guide ` and understand how to update schemas, run migrations, and have -a working EdgeDB project. Let's update the schema to make the ``title`` property +a working |Gel| project. Let's update the schema to make the ``title`` property of the ``Movie`` type exclusive. This will help with filtering by ``Movie.title`` in our queries. @@ -128,8 +124,8 @@ Generate the new migration and apply them: .. code-block:: bash - $ edgedb migration create - $ edgedb migrate + $ gel migration create + $ gel migrate We'll be using TypeScript and Node for this example, so let's setup a simple app: @@ -137,8 +133,8 @@ app: .. code-block:: bash $ npm init -y # initialize a new npm project - $ npm i edgedb - $ npm i -D typescript @types/node @edgedb/generate tsx + $ npm i gel + $ npm i -D typescript @types/node @gel/generate tsx $ npx tsc --init # initialize a basic TypeScript project Client @@ -154,9 +150,9 @@ insert query directly with the driver: .. code-block:: typescript :caption: seed.ts - import * as edgedb from "edgedb"; + import * as gel from "gel"; - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { await client.execute(` @@ -182,7 +178,7 @@ We can now seed the database by running this script with ``tsx`` $ npx tsx seed.ts -Feel free to explore the database in the :ref:`EdgeDB UI `, +Feel free to explore the database in the :ref:`Gel UI `, where you will find the new data you inserted through this script, as well as any data you inserted when running the Quickstart. @@ -203,9 +199,9 @@ Iron Man 2: .. code-block:: typescript :caption: query.ts - import * as edgedb from "edgedb"; + import * as gel from "gel"; - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { const result = await client.querySingle(` @@ -236,7 +232,7 @@ First we run the generator: .. code-block:: bash - $ npx @edgedb/generate interfaces + $ npx @gel/generate interfaces This generator introspects your database schema and generates a set of equivalent TypeScript interfaces. @@ -246,10 +242,10 @@ Now we can annotate our query since we are selecting the whole ``Movie`` type: .. code-block:: typescript-diff :caption: query.ts - import * as edgedb from "edgedb"; + import * as gel from "gel"; import { Movie } from "./dbschema/interfaces" - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { // result will be inferred as Movie | null @@ -304,7 +300,7 @@ which will compile all the queries it finds into a single TypeScript module: .. code-block:: bash - $ npx @edgedb/generate queries --file + $ npx @gel/generate queries --file Now, let's update our query script to call the generated function, which will provide us with type-safe querying. @@ -312,11 +308,11 @@ provide us with type-safe querying. .. code-block:: typescript-diff :caption: query.ts - import * as edgedb from "edgedb"; + import * as gel from "gel"; - import { Movie } from "./dbschema/interfaces" + import { getMovie } from "./dbschema/queries" - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { // result will be inferred as Movie | null @@ -344,8 +340,8 @@ change. It'll be completely type safe! Query builder ^^^^^^^^^^^^^ -At last we've arrived at the most powerful API for querying your EdgeDB -instance: the query builder. The EdgeDB query builder provides a **code-first** +At last we've arrived at the most powerful API for querying your |Gel| +instance: the query builder. The Gel query builder provides a **code-first** way to write **fully-typed** EdgeQL queries with TypeScript. We recommend it for TypeScript users, or anyone who prefers writing queries with code. @@ -353,7 +349,7 @@ First, we'll run the query builder generator: .. code-block:: bash - $ npx @edgedb/generate edgeql-js + $ npx @gel/generate edgeql-js .. note:: Version control @@ -373,11 +369,11 @@ in TypeScript, getting editor completion, type checking, and type inferrence: .. code-block:: typescript-diff :caption: query.ts - import * as edgedb from "edgedb"; + import * as gel from "gel"; - import { getMovie } from "./dbschema/queries"; + import e from "./dbschema/edgeql-js"; - const client = edgedb.createClient(); + const client = gel.createClient(); async function main() { - // result will be inferred as Movie | null @@ -404,4 +400,4 @@ We recommend reading the :ref:`client docs ` first and getting familiar with configuring the client. You'll find important APIs like ``withGlobals`` and connection details there. After that, depending on your preferences, look through the :ref:`query builder ` documentation -and use the other pages as a reference for writing code-first EdgeDB queries. +and use the other pages as a reference for writing code-first Gel queries. diff --git a/docs/clients/js/insert.rst b/docs/clients/js/insert.rst index d246b04f01d..1f8cff329cb 100644 --- a/docs/clients/js/insert.rst +++ b/docs/clients/js/insert.rst @@ -86,7 +86,7 @@ builder, this is possible with the ``.unlessConflict`` method (available only on ``insert`` expressions). In the simplest case, adding ``.unlessConflict`` (no arguments) will prevent -EdgeDB from throwing an error if the insertion would violate an exclusivity +Gel from throwing an error if the insertion would violate an exclusivity contstraint. Instead, the query returns an empty set (``null``). .. code-block:: typescript diff --git a/docs/clients/js/interfaces.rst b/docs/clients/js/interfaces.rst index 0a4b4c1bfab..0c6380b4a8d 100644 --- a/docs/clients/js/interfaces.rst +++ b/docs/clients/js/interfaces.rst @@ -4,34 +4,34 @@ Interfaces Generator ==================== -The ``interfaces`` generator introspects your schema and generates file containing *TypeScript interfaces* that correspond to each object type. This is useful for writing typesafe code to interact with EdgeDB. +The ``interfaces`` generator introspects your schema and generates file containing *TypeScript interfaces* that correspond to each object type. This is useful for writing typesafe code to interact with |Gel|. Installation ------------ To get started, install the following packages. (If you're using Deno, you can skip this step.) -Install the ``edgedb`` package. +Install the ``gel`` package. .. code-block:: bash - $ npm install edgedb # npm users - $ yarn add edgedb # yarn users - $ bun add edgedb # bun users + $ npm install gel # npm users + $ yarn add gel # yarn users + $ bun add gel # bun users -Then install ``@edgedb/generate`` as a dev dependency. +Then install ``@gel/generate`` as a dev dependency. .. code-block:: bash - $ npm install @edgedb/generate --save-dev # npm users - $ yarn add @edgedb/generate --dev # yarn users - $ bun add --dev @edgedb/generate # bun users + $ npm install @gel/generate --save-dev # npm users + $ yarn add @gel/generate --dev # yarn users + $ bun add --dev @gel/generate # bun users Generation ---------- -Assume your database contains the following EdgeDB schema. +Assume your database contains the following Gel schema. .. code-block:: sdl @@ -56,17 +56,17 @@ The following command will run the ``interfaces`` generator. .. code-tab:: bash :caption: Node.js - $ npx @edgedb/generate interfaces + $ npx @gel/generate interfaces .. code-tab:: bash :caption: Deno - $ deno run --allow-all --unstable https://deno.land/x/edgedb/generate.ts interfaces + $ deno run --allow-all --unstable https://deno.land/x/gel/generate.ts interfaces .. code-tab:: bash :caption: Bun - $ bunx @edgedb/generate interfaces + $ bunx @gel/generate interfaces .. note:: Deno users @@ -77,8 +77,8 @@ The following command will run the ``interfaces`` generator. { "imports": { - "edgedb": "https://deno.land/x/edgedb/mod.ts", - "edgedb/": "https://deno.land/x/edgedb/" + "gel": "https://deno.land/x/gel/mod.ts", + "gel/": "https://deno.land/x/gel/" } } @@ -121,7 +121,7 @@ Pass a ``--file`` flag to specify the output file path. .. code-block:: bash - $ npx @edgedb/generate interfaces --file schema.ts + $ npx @gel/generate interfaces --file schema.ts If the value passed as ``--file`` is a relative path, it will be evaluated relative to the current working directory (``process.cwd()``). If the value is an absolute path, it will be used as-is. diff --git a/docs/clients/js/literals.rst b/docs/clients/js/literals.rst index 68ffad2c744..90983b309f3 100644 --- a/docs/clients/js/literals.rst +++ b/docs/clients/js/literals.rst @@ -14,7 +14,7 @@ Primitives ^^^^^^^^^^ Primitive literal expressions are created using constructor functions that -correspond to EdgeDB datatypes. Each expression below is accompanied by the +correspond to Gel datatypes. Each expression below is accompanied by the EdgeQL it produces. .. code-block:: typescript @@ -83,12 +83,12 @@ To create an instance of ``datetime``, pass a JavaScript ``Date`` object into e.datetime(new Date('1999-01-01')); // '1999-01-01T00:00:00.000Z' -EdgeDB's other temporal datatypes don't have equivalents in the JavaScript +Gel's other temporal datatypes don't have equivalents in the JavaScript type system: ``duration``, ``cal::relative_duration``, ``cal::date_duration``, ``cal::local_date``, ``cal::local_time``, and ``cal::local_datetime``, To resolve this, each of these datatypes can be represented with an instance -of a corresponding class, as defined in ``edgedb`` module. Clients use +of a corresponding class, as defined in ``gel`` module. Clients use these classes to represent these values in query results; they are documented on the :ref:`Client API ` docs. @@ -116,18 +116,18 @@ along with the equivalent EdgeQL. .. code-block:: typescript - import * as edgedb from "edgedb"; + import * as gel from "gel"; - const myDuration = new edgedb.Duration(0, 0, 0, 0, 1, 2, 3); + const myDuration = new gel.Duration(0, 0, 0, 0, 1, 2, 3); e.duration(myDuration); - const myLocalDate = new edgedb.LocalDate(1776, 7, 4); + const myLocalDate = new gel.LocalDate(1776, 7, 4); e.cal.local_date(myLocalDate); - const myLocalTime = new edgedb.LocalTime(13, 15, 0); + const myLocalTime = new gel.LocalTime(13, 15, 0); e.cal.local_time(myLocalTime); - const myLocalDateTime = new edgedb.LocalDateTime(1776, 7, 4, 13, 15, 0); + const myLocalDateTime = new gel.LocalDateTime(1776, 7, 4, 13, 15, 0); e.cal.local_datetime(myLocalDateTime); @@ -154,12 +154,12 @@ JSON ^^^^ JSON literals are created with the ``e.json`` function. You can pass in any -EdgeDB-compatible data structure. +Gel-compatible data structure. -What does "EdgeDB-compatible" mean? It means any JavaScript data structure -with an equivalent in EdgeDB: strings, number, booleans, ``bigint``\ s, -``Uint8Array``\ s, ``Date``\ s, and instances of EdgeDB's built-in classes: +What does "Gel-compatible" mean? It means any JavaScript data structure +with an equivalent in Gel: strings, number, booleans, ``bigint``\ s, +``Uint8Array``\ s, ``Date``\ s, and instances of Gel's built-in classes: (``LocalDate`` ``LocalTime``, ``LocalDateTime``, ``DateDuration``, ``Duration``, and ``RelativeDuration``), and any array or object of these types. Other JavaScript data structures like symbols, instances of custom @@ -175,7 +175,7 @@ docs/Web/JavaScript/Typed_arrays>`_ are not supported. name: "Billie", numbers: [1,2,3], nested: { foo: "bar"}, - duration: new edgedb.Duration(1, 3, 3) + duration: new gel.Duration(1, 3, 3) }) JSON expressions support indexing, as in EdgeQL. The returned expression also @@ -357,7 +357,7 @@ Supply named parameters as the first argument. e.range({inc_lower: true, inc_upper: true, empty: true}, 0, 8); // => std::range(0, 8, true, true); -JavaScript doesn't have a native way to represent range values. Any range value returned from a query will be encoded as an instance of the :js:class:`Range` class, which is exported from the ``edgedb`` package. +JavaScript doesn't have a native way to represent range values. Any range value returned from a query will be encoded as an instance of the :js:class:`Range` class, which is exported from the ``gel`` package. .. code-block:: typescript diff --git a/docs/clients/js/queries.rst b/docs/clients/js/queries.rst index ec1471bf34b..b2b7945d49e 100644 --- a/docs/clients/js/queries.rst +++ b/docs/clients/js/queries.rst @@ -15,21 +15,21 @@ To get started, install the following packages. If you're using Deno, you can skip this step. -Install the ``edgedb`` package. +Install the ``gel`` package. .. code-block:: bash - $ npm install edgedb # npm users - $ yarn add edgedb # yarn users - $ bun add edgedb # bun users + $ npm install gel # npm users + $ yarn add gel # yarn users + $ bun add gel # bun users -Then install ``@edgedb/generate`` as a dev dependency. +Then install ``@gel/generate`` as a dev dependency. .. code-block:: bash - $ npm install @edgedb/generate --save-dev # npm users - $ yarn add @edgedb/generate --dev # yarn users - $ bun add --dev @edgedb/generate # bun users + $ npm install @gel/generate --save-dev # npm users + $ yarn add @gel/generate --dev # yarn users + $ bun add --dev @gel/generate # bun users Generation @@ -41,7 +41,7 @@ Consider the following file tree. . ├── package.json - ├── edgedb.toml + ├── gel.toml ├── index.ts ├── dbschema └── queries @@ -55,17 +55,17 @@ The following command will run the ``queries`` generator. .. code-tab:: bash :caption: Node.js - $ npx @edgedb/generate queries + $ npx @gel/generate queries .. code-tab:: bash :caption: Deno - $ deno run --allow-all --unstable https://deno.land/x/edgedb/generate.ts queries + $ deno run --allow-all --unstable https://deno.land/x/gel/generate.ts queries .. code-tab:: bash :caption: Bun - $ bunx @edgedb/generate queries + $ bunx @gel/generate queries .. note:: Deno users @@ -76,8 +76,8 @@ The following command will run the ``queries`` generator. { "imports": { - "edgedb": "https://deno.land/x/edgedb/mod.ts", - "edgedb/": "https://deno.land/x/edgedb/" + "gel": "https://deno.land/x/gel/mod.ts", + "gel/": "https://deno.land/x/gel/" } } @@ -88,7 +88,7 @@ The following command will run the ``queries`` generator. "importMap": "./importMap.json" } -The generator will detect the project root by looking for an ``edgedb.toml``, +The generator will detect the project root by looking for an ``gel.toml``, then scan the directory for ``*.edgeql`` files. In this case, there's only one: ``queries/getUser.edgeql``. @@ -106,7 +106,7 @@ return type. The generator uses this information to create a new file . ├── package.json - ├── edgedb.toml + ├── gel.toml ├── index.ts ├── dbschema └── queries @@ -125,7 +125,7 @@ The generated file will look something like this: .. code-block:: typescript - import type { Client } from "edgedb"; + import type { Client } from "gel"; export type GetUserArgs = { user_id: string; @@ -162,7 +162,7 @@ We can now use this function in our code. createClient, type GetUserArgs, type GetUserReturns, - } from "edgedb"; + } from "gel"; const client = await createClient(); @@ -193,7 +193,7 @@ Let's say we start with the following file tree. . ├── package.json - ├── edgedb.toml + ├── gel.toml ├── index.ts ├── dbschema └── queries @@ -204,7 +204,7 @@ The following command will run the generator in ``--file`` mode. .. code-block:: bash - $ npx @edgedb/generate queries --file + $ npx @gel/generate queries --file A single file will be generated that exports two functions, ``getUser`` and ``getMovies``. By default this file is generated into the ``dbschema`` directory. @@ -212,7 +212,7 @@ A single file will be generated that exports two functions, ``getUser`` and ``ge . ├── package.json - ├── edgedb.toml + ├── gel.toml ├── index.ts ├── dbschema │ └── queries.ts <-- generated file @@ -226,7 +226,7 @@ We can now use these functions in our code. .. code-block:: typescript import * as queries from "./dbschema/queries"; - import {createClient} from "edgedb"; + import {createClient} from "gel"; const client = await createClient(); @@ -239,7 +239,7 @@ To override the file path and name, you can optionally pass a value to the ``--f .. code-block:: bash - $ npx @edgedb/generate queries --file path/to/myqueries + $ npx @gel/generate queries --file path/to/myqueries The file extension is determined by the generator ``--target`` and will be automatically appended to the provided path. Extensionless "absolute" paths will work; relative paths will be resolved relative to the current working directory. @@ -249,7 +249,7 @@ This will result in the following file tree. . ├── package.json - ├── edgedb.toml + ├── gel.toml ├── path │ └── to │ └── myqueries.ts @@ -271,11 +271,11 @@ To exclude the generated files, add the following lines to your ``.gitignore`` f Writing Queries with Parameters ------------------------------- -To inject external values into your EdgeQL queries, you can use `parameters `__. +To inject external values into your EdgeQL queries, you can use `parameters `__. -When using the queries generator, you may be tempted to declare the same parameter in multiple places. +When using the queries generator, you may be tempted to declare the same parameter in multiple places. However, it's better practice to declare it once by assigning it to a variable in a `with block `__ -and reference that variable in the rest of your query. This way you avoid mismatched types in your declarations, +and reference that variable in the rest of your query. This way you avoid mismatched types in your declarations, such as forgetting to mark them all as `optional `__. Check out the `EdgeQL docs `__ to learn more about writing queries. diff --git a/docs/clients/js/querybuilder.rst b/docs/clients/js/querybuilder.rst index 8db198ef656..56ae6224b99 100644 --- a/docs/clients/js/querybuilder.rst +++ b/docs/clients/js/querybuilder.rst @@ -5,16 +5,16 @@ Query Builder Generator ======================= :index: querybuilder generator typescript -The EdgeDB query builder provides a **code-first** way to write +The |Gel| query builder provides a **code-first** way to write **fully-typed** EdgeQL queries with TypeScript. We recommend it for TypeScript users, or anyone who prefers writing queries with code. .. code-block:: typescript - import * as edgedb from "edgedb"; + import * as gel from "gel"; import e from "./dbschema/edgeql-js"; - const client = edgedb.createClient(); + const client = gel.createClient(); async function run() { const query = e.select(e.Movie, ()=>({ @@ -39,9 +39,9 @@ users, or anyone who prefers writing queries with code. No—it's better! Like any modern TypeScript ORM, the query builder gives you full typesafety and autocompletion, but without the power and `performance - `_ + `_ tradeoffs. You have access to the **full power** of EdgeQL and can write - EdgeQL queries of arbitrary complexity. And since EdgeDB compiles each + EdgeQL queries of arbitrary complexity. And since |Gel| compiles each EdgeQL query into a single, highly-optimized SQL query, your queries stay fast, even when they're complex. @@ -71,21 +71,21 @@ To get started, install the following packages. If you're using Deno, you can skip this step. -Install the ``edgedb`` package. +Install the ``gel`` package. .. code-block:: bash - $ npm install edgedb # npm users - $ yarn add edgedb # yarn users - $ bun add edgedb # bun users + $ npm install gel # npm users + $ yarn add gel # yarn users + $ bun add gel # bun users -Then install ``@edgedb/generate`` as a dev dependency. +Then install ``@gel/generate`` as a dev dependency. .. code-block:: bash - $ npm install @edgedb/generate --save-dev # npm users - $ yarn add @edgedb/generate --dev # yarn users - $ bun add --dev @edgedb/generate # bun users + $ npm install @gel/generate --save-dev # npm users + $ yarn add @gel/generate --dev # yarn users + $ bun add --dev @gel/generate # bun users Generation @@ -98,17 +98,17 @@ The following command will run the ``edgeql-js`` query builder generator. .. code-tab:: bash :caption: Node.js - $ npx @edgedb/generate edgeql-js + $ npx @gel/generate edgeql-js .. code-tab:: bash :caption: Deno - $ deno run --allow-all --unstable https://deno.land/x/edgedb/generate.ts edgeql-js + $ deno run --allow-all --unstable https://deno.land/x/gel/generate.ts edgeql-js .. code-tab:: bash :caption: Bun - $ bunx @edgedb/generate edgeql-js + $ bunx @gel/generate edgeql-js .. note:: Deno users @@ -119,8 +119,8 @@ The following command will run the ``edgeql-js`` query builder generator. { "imports": { - "edgedb": "https://deno.land/x/edgedb/mod.ts", - "edgedb/": "https://deno.land/x/edgedb/" + "gel": "https://deno.land/x/gel/mod.ts", + "gel/": "https://deno.land/x/gel/" } } @@ -145,7 +145,7 @@ The generation command is configurable in a number of ways. ``--force-overwrite``. The generator also supports all the :ref:`connection flags -` supported by the EdgeDB CLI. These aren't +` supported by the |Gel| CLI. These aren't necessary when using a project or environment variables to configure a connection. diff --git a/docs/clients/js/reference.rst b/docs/clients/js/reference.rst index 3ff9b8d8d87..0a6ea4b1d7a 100644 --- a/docs/clients/js/reference.rst +++ b/docs/clients/js/reference.rst @@ -17,16 +17,17 @@ Client :param options: This is an optional parameter. When it is not specified the client - will connect to the current EdgeDB Project instance. + will connect to the current |Gel| Project instance. If this parameter is a string it can represent either a DSN or an instance name: - * when the string does not start with ``edgedb://`` it is a - :ref:`name of an instance `; + * when the string does not start with ``gel://`` (or legacy + ``edgedb://``) it is a :ref:`name of an instance + `; * otherwise it specifies a single string in the connection URI format: - ``edgedb://user:password@host:port/database?option=value``. + ``gel://user:password@host:port/database?option=value``. See the :ref:`Connection Parameters ` docs for full details. @@ -99,10 +100,10 @@ Client // Use the Node.js assert library to test results. const assert = require("assert"); - const edgedb = require("edgedb"); + const gel = require("gel"); async function main() { - const client = edgedb.createClient(); + const client = gel.createClient(); const data = await client.querySingle("select 1 + 1"); @@ -116,7 +117,7 @@ Client .. js:class:: Client - A ``Client`` allows you to run queries on an EdgeDB instance. + A ``Client`` allows you to run queries on a |Gel| instance. Since opening connections is an expensive operation, ``Client`` also maintains a internal pool of connections to the instance, allowing @@ -413,7 +414,7 @@ Client .. code-block:: javascript - import {createClient} from 'edgedb'; + import {createClient} from 'gel'; async function getClient() { try { @@ -503,7 +504,7 @@ Client .. code-block:: javascript - import {createClient} from 'edgedb'; + import {createClient} from 'gel'; function main() { const client = createClient(); @@ -556,15 +557,15 @@ Client Type conversion =============== -The client automatically converts EdgeDB types to the corresponding JavaScript +The client automatically converts |Gel| types to the corresponding JavaScript types and vice versa. -The table below shows the correspondence between EdgeDB and JavaScript types. +The table below shows the correspondence between Gel and JavaScript types. .. list-table:: - * - **EdgeDB Type** + * - **Gel Type** - **JavaScript Type** * - ``multi`` set - ``Array`` @@ -631,16 +632,16 @@ The table below shows the correspondence between EdgeDB and JavaScript types. Arrays ====== -EdgeDB ``array`` maps onto the JavaScript ``Array``. +Gel ``array`` maps onto the JavaScript ``Array``. .. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); - const edgedb = require("edgedb"); + const gel = require("gel"); async function main() { - const client = edgedb.createClient("edgedb://edgedb@localhost/"); + const client = gel.createClient(); const data = await client.querySingle("select [1, 2, 3]"); @@ -665,10 +666,10 @@ object property or a link can be accessed through a corresponding object key: // Use the Node.js assert library to test results. const assert = require("assert"); - const edgedb = require("edgedb"); + const gel = require("gel"); async function main() { - const client = edgedb.createClient("edgedb://edgedb@localhost/"); + const client = gel.createClient(); const data = await client.querySingle(` select schema::Property { @@ -684,7 +685,7 @@ object property or a link can be accessed through a corresponding object key: assert(typeof data.name === "string"); // The link 'annotaions' is accessible and is a Set. assert(typeof data.annotations === "object"); - assert(data.annotations instanceof edgedb.Set); + assert(data.annotations instanceof gel.Set); // The Set of 'annotations' is array-like. assert(data.annotations.length > 0); assert(data.annotations[0].name === "cfg::system"); @@ -696,16 +697,16 @@ object property or a link can be accessed through a corresponding object key: Tuples ====== -A regular EdgeDB ``tuple`` becomes an ``Array`` in JavaScript. +A regular |Gel| ``tuple`` becomes an ``Array`` in JavaScript. .. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); - const edgedb = require("edgedb"); + const gel = require("gel"); async function main() { - const client = edgedb.createClient("edgedb://edgedb@localhost/"); + const client = gel.createClient(); const data = await client.querySingle(` select (1, 'a', [3]) @@ -724,17 +725,17 @@ A regular EdgeDB ``tuple`` becomes an ``Array`` in JavaScript. Named Tuples ============ -A named EdgeDB ``tuple`` becomes an ``Array``-like ``object`` in JavaScript, +A named |Gel| ``tuple`` becomes an ``Array``-like ``object`` in JavaScript, where the elements are accessible either by their names or indexes. .. code-block:: javascript // Use the Node.js assert library to test results. const assert = require("assert"); - const edgedb = require("edgedb"); + const gel = require("gel"); async function main() { - const client = edgedb.createClient("edgedb://edgedb@localhost/"); + const client = gel.createClient(); const data = await client.querySingle(` select (a := 1, b := 'a', c := [3]) @@ -763,7 +764,7 @@ Local Date month: number, \ day: number) - A JavaScript representation of an EdgeDB ``local_date`` value. Implements + A JavaScript representation of an |Gel| ``local_date`` value. Implements a subset of the `TC39 Temporal Proposal`_ ``PlainDate`` type. Assumes the calendar is always `ISO 8601`_. @@ -848,14 +849,14 @@ Local Time microsecond: number = 0, \ nanosecond: number = 0) - A JavaScript representation of an EdgeDB ``local_time`` value. Implements + A JavaScript representation of an Gel ``local_time`` value. Implements a subset of the `TC39 Temporal Proposal`_ ``PlainTime`` type. .. note:: - The EdgeDB ``local_time`` type only has microsecond precision, any + The Gel ``local_time`` type only has microsecond precision, any nanoseconds specified in the ``LocalTime`` will be ignored when - encoding to an EdgeDB ``local_time``. + encoding to an Gel ``local_time``. .. js:attribute:: hour: number @@ -909,7 +910,7 @@ Local Date and Time microsecond: number = 0, \ nanosecond: number = 0) extends LocalDate, LocalTime - A JavaScript representation of an EdgeDB ``local_datetime`` value. + A JavaScript representation of an |Gel| ``local_datetime`` value. Implements a subset of the `TC39 Temporal Proposal`_ ``PlainDateTime`` type. @@ -945,7 +946,7 @@ Duration microseconds: number = 0, \ nanoseconds: number = 0) - A JavaScript representation of an EdgeDB ``duration`` value. This class + A JavaScript representation of a Gel ``duration`` value. This class attempts to conform to the `TC39 Temporal Proposal`_ ``Duration`` type as closely as possible. @@ -960,24 +961,24 @@ Duration their absolute duration changes depending on the exact date they are relative to (eg. different months have a different number of days). - The EdgeDB ``duration`` type only supports absolute durations, so any + The Gel ``duration`` type only supports absolute durations, so any ``Duration`` with non-zero years, months, weeks, or days will throw an error when trying to encode them. .. note:: - The EdgeDB ``duration`` type only has microsecond precision, any + The Gel ``duration`` type only has microsecond precision, any nanoseconds specified in the ``Duration`` will be ignored when - encoding to an EdgeDB ``duration``. + encoding to an Gel ``duration``. .. note:: Temporal ``Duration`` objects can be unbalanced_, (ie. have a greater value in any property than it would naturally have, eg. have a seconds - property greater than 59), but EdgeDB ``duration`` objects are always + property greater than 59), but Gel ``duration`` objects are always balanced. - Therefore in a round-trip of a ``Duration`` object to EdgeDB and back, + Therefore in a round-trip of a ``Duration`` object to Gel and back, the returned object, while being an equivalent duration, may not have exactly the same property values as the sent object. @@ -1058,7 +1059,7 @@ RelativeDuration milliseconds: number = 0, \ microseconds: number = 0) - A JavaScript representation of an EdgeDB + A JavaScript representation of an Gel :eql:type:`cal::relative_duration` value. This type represents a non-definite span of time such as "2 years 3 days". This cannot be represented as a :eql:type:`duration` because a year has no absolute @@ -1133,7 +1134,7 @@ DateDuration days: number = 0, \ ) - A JavaScript representation of an EdgeDB + A JavaScript representation of an Gel :eql:type:`cal::date_duration` value. This type represents a non-definite span of time consisting of an integer number of *months* and *days*. @@ -1189,7 +1190,7 @@ Memory .. js:class:: ConfigMemory(bytes: BigInt) - A JavaScript representation of an EdgeDB ``cfg::memory`` value. + A JavaScript representation of an Gel ``cfg::memory`` value. .. js:attribute:: bytes: number @@ -1197,7 +1198,7 @@ Memory .. note:: - The EdgeDB ``cfg::memory`` represents a number of bytes stored as + The Gel ``cfg::memory`` represents a number of bytes stored as an ``int64``. Since JS the ``number`` type is a ``float64``, values above ``~8191TiB`` will lose precision when represented as a JS ``number``. To keep full precision use the ``bytesBigInt`` @@ -1230,7 +1231,7 @@ Memory .. js:method:: toString(): string Get the string representation of the memory value. Format is the same - as returned by string casting a ``cfg::memory`` value in EdgeDB. + as returned by string casting a ``cfg::memory`` value in Gel. Range ===== @@ -1242,7 +1243,7 @@ Range incUpper: boolean = false \ ) - A JavaScript representation of an EdgeDB ``std::range`` value. This is a generic TypeScript class with the following type signature. + A JavaScript representation of an Gel ``std::range`` value. This is a generic TypeScript class with the following type signature. .. code-block:: typescript diff --git a/docs/clients/js/select.rst b/docs/clients/js/select.rst index 801e5be85dc..4f5d0ac2ebd 100644 --- a/docs/clients/js/select.rst +++ b/docs/clients/js/select.rst @@ -271,7 +271,7 @@ params object. This should correspond to a boolean expression. .. note:: Since ``filter`` is a :ref:`reserved keyword ` in - EdgeDB, there is minimal danger of conflicting with a property or link named + |Gel|, there is minimal danger of conflicting with a property or link named ``filter``. All shapes can contain filter clauses, even nested ones. If you have many conditions you want to test for, your filter can start to get @@ -462,7 +462,7 @@ simply order by a property: .. note:: - Unlike ``filter``, ``order_by`` is *not* a reserved word in EdgeDB. Using + Unlike ``filter``, ``order_by`` is *not* a reserved word in |Gel|. Using ``order_by`` as a link or property name will create a naming conflict and likely cause bugs. diff --git a/docs/clients/js/types.rst b/docs/clients/js/types.rst index e77736941a1..f10fb69eea2 100644 --- a/docs/clients/js/types.rst +++ b/docs/clients/js/types.rst @@ -4,7 +4,7 @@ Types ----- -The entire type system of EdgeDB is reflected in the ``e`` object, including +The entire type system of |Gel| is reflected in the ``e`` object, including scalar types, object types, and enums. These types are used in queries for thinks like *casting* and *declaring parameters*. .. code-block:: typescript diff --git a/docs/clients/python/api/advanced.rst b/docs/clients/python/api/advanced.rst index 371c26f4a7c..ecb012829e6 100644 --- a/docs/clients/python/api/advanced.rst +++ b/docs/clients/python/api/advanced.rst @@ -4,7 +4,7 @@ Advanced Usage ============== -.. py:currentmodule:: edgedb +.. py:currentmodule:: gel .. _edgedb-python-transaction-options: @@ -37,18 +37,18 @@ Transactions can be customized with different options: Repeatable read isolation level (supported in read-only transactions) -:py:class:`TransactionOptions` can be set on :py:class:`~edgedb.Client` or -:py:class:`~edgedb.AsyncIOClient` using one of these methods: +:py:class:`TransactionOptions` can be set on :py:class:`~gel.Client` or +:py:class:`~gel.AsyncIOClient` using one of these methods: -* :py:meth:`edgedb.Client.with_transaction_options` -* :py:meth:`edgedb.AsyncIOClient.with_transaction_options` +* :py:meth:`gel.Client.with_transaction_options` +* :py:meth:`gel.AsyncIOClient.with_transaction_options` These methods return a "shallow copy" of the current client object with modified transaction options. Both ``self`` and the returned object can be used, but different transaction options will applied respectively. Transaction options are used by the future calls to the method -:py:meth:`edgedb.Client.transaction` or :py:meth:`edgedb.AsyncIOClient.transaction`. +:py:meth:`gel.Client.transaction` or :py:meth:`gel.AsyncIOClient.transaction`. .. _edgedb-python-retry-options: @@ -57,7 +57,7 @@ Retry Options ============= Individual EdgeQL commands or whole transaction blocks are automatically retried on -retryable errors. By default, edgedb-python will try at most 3 times, with an +retryable errors. By default, gel-python will try at most 3 times, with an exponential backoff time interval starting from 100ms, plus a random hash under 100ms. Retry rules can be granularly customized with different retry options: @@ -94,11 +94,11 @@ Retry rules can be granularly customized with different retry options: Triggered when a ClientError occurs. -:py:class:`RetryOptions` can be set on :py:class:`~edgedb.Client` or -:py:class:`~edgedb.AsyncIOClient` using one of these methods: +:py:class:`RetryOptions` can be set on :py:class:`~gel.Client` or +:py:class:`~gel.AsyncIOClient` using one of these methods: -* :py:meth:`edgedb.Client.with_retry_options` -* :py:meth:`edgedb.AsyncIOClient.with_retry_options` +* :py:meth:`gel.Client.with_retry_options` +* :py:meth:`gel.AsyncIOClient.with_retry_options` These methods return a "shallow copy" of the current client object with modified retry options. Both ``self`` and the returned object can be used, but different @@ -256,11 +256,11 @@ different ways: default module, module aliases, session config and global values If no names were given, all globals will be reset. -:py:class:`State` can be set on :py:class:`~edgedb.Client` or -:py:class:`~edgedb.AsyncIOClient` using one of these methods: +:py:class:`State` can be set on :py:class:`~gel.Client` or +:py:class:`~gel.AsyncIOClient` using one of these methods: -* :py:meth:`edgedb.Client.with_state` -* :py:meth:`edgedb.AsyncIOClient.with_state` +* :py:meth:`gel.Client.with_state` +* :py:meth:`gel.AsyncIOClient.with_state` These methods return a "shallow copy" of the current client object with modified state, affecting all future commands executed using the returned copy. @@ -269,20 +269,20 @@ applied respectively. Alternatively, shortcuts are available on client objects: -* :py:meth:`edgedb.Client.with_default_module` -* :py:meth:`edgedb.Client.with_module_aliases` -* :py:meth:`edgedb.Client.without_module_aliases` -* :py:meth:`edgedb.Client.with_config` -* :py:meth:`edgedb.Client.without_config` -* :py:meth:`edgedb.Client.with_globals` -* :py:meth:`edgedb.Client.without_globals` -* :py:meth:`edgedb.AsyncIOClient.with_default_module` -* :py:meth:`edgedb.AsyncIOClient.with_module_aliases` -* :py:meth:`edgedb.AsyncIOClient.without_module_aliases` -* :py:meth:`edgedb.AsyncIOClient.with_config` -* :py:meth:`edgedb.AsyncIOClient.without_config` -* :py:meth:`edgedb.AsyncIOClient.with_globals` -* :py:meth:`edgedb.AsyncIOClient.without_globals` +* :py:meth:`gel.Client.with_default_module` +* :py:meth:`gel.Client.with_module_aliases` +* :py:meth:`gel.Client.without_module_aliases` +* :py:meth:`gel.Client.with_config` +* :py:meth:`gel.Client.without_config` +* :py:meth:`gel.Client.with_globals` +* :py:meth:`gel.Client.without_globals` +* :py:meth:`gel.AsyncIOClient.with_default_module` +* :py:meth:`gel.AsyncIOClient.with_module_aliases` +* :py:meth:`gel.AsyncIOClient.without_module_aliases` +* :py:meth:`gel.AsyncIOClient.with_config` +* :py:meth:`gel.AsyncIOClient.without_config` +* :py:meth:`gel.AsyncIOClient.with_globals` +* :py:meth:`gel.AsyncIOClient.without_globals` They work the same way as ``with_state``, and adjusts the corresponding state values. diff --git a/docs/clients/python/api/asyncio_client.rst b/docs/clients/python/api/asyncio_client.rst index 4ec117f290e..c10718609e6 100644 --- a/docs/clients/python/api/asyncio_client.rst +++ b/docs/clients/python/api/asyncio_client.rst @@ -4,7 +4,7 @@ AsyncIO API =========== -.. py:currentmodule:: edgedb +.. py:currentmodule:: gel .. _edgedb-python-async-api-client: @@ -34,12 +34,12 @@ Client Returns a new :py:class:`AsyncIOClient` object. :param str dsn: - If this parameter does not start with ``edgedb://`` then this is + If this parameter does not start with ``gel://`` then this is interpreted as the :ref:`name of a local instance `. Otherwise it specifies a single string in the following format: - ``edgedb://user:password@host:port/database?option=value``. + ``gel://user:password@host:port/database?option=value``. The following options are recognized: host, port, user, database, password. For a complete reference on DSN, see the :ref:`DSN Specification `. @@ -118,14 +118,14 @@ Client .. code-block:: python - client = edgedb.create_async_client() + client = gel.create_async_client() await client.query('SELECT {1, 2, 3}') The same for transactions: .. code-block:: python - client = edgedb.create_async_client() + client = gel.create_async_client() async for tx in client.transaction(): async with tx: await tx.query('SELECT {1, 2, 3}') @@ -137,12 +137,12 @@ Client An asynchronous client with a connection pool, safe for concurrent use. Async clients are created by calling - :py:func:`~edgedb.create_async_client`. + :py:func:`~gel.create_async_client`. .. py:coroutinemethod:: query(query, *args, **kwargs) Acquire a connection and use it to run a query and return the results - as an :py:class:`edgedb.Set` instance. The temporary + as an :py:class:`gel.Set` instance. The temporary connection is automatically returned back to the pool. :param str query: Query text. @@ -150,7 +150,7 @@ Client :param kwargs: Named query arguments. :return: - An instance of :py:class:`edgedb.Set` containing + An instance of :py:class:`gel.Set` containing the query result. Note that positional and named query arguments cannot be mixed. @@ -170,7 +170,7 @@ Client Query result. The *query* must return no more than one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` is raised, if it returns an empty set, ``None`` is returned. Note, that positional and named query arguments cannot be mixed. @@ -190,8 +190,8 @@ Client Query result. The *query* must return exactly one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` - is raised, if it returns an empty set, an ``edgedb.NoDataError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``gel.NoDataError`` is raised. Note, that positional and named query arguments cannot be mixed. @@ -239,7 +239,7 @@ Client Query result encoded in JSON. The *query* must return no more than one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` is raised, if it returns an empty set, ``"null"`` is returned. Note, that positional and named query arguments cannot be mixed. @@ -271,8 +271,8 @@ Client Query result encoded in JSON. The *query* must return exactly one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` - is raised, if it returns an empty set, an ``edgedb.NoDataError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``gel.NoDataError`` is raised. Note, that positional and named query arguments cannot be mixed. @@ -358,7 +358,7 @@ Client Wait until all pool connections are released, close them and shut down the pool. If any error (including cancellation) occurs in ``aclose()`` the pool will terminate by calling - :py:meth:`~edgedb.AsyncIOClient.terminate`. + :py:meth:`~gel.AsyncIOClient.terminate`. It is advisable to use :py:func:`python:asyncio.wait_for` to set a timeout. @@ -611,12 +611,12 @@ See also: Acquire a connection if the current transaction doesn't have one yet, and use it to run a query and return the results - as an :py:class:`edgedb.Set` instance. The temporary + as an :py:class:`gel.Set` instance. The temporary connection is automatically returned back to the pool when exiting the transaction block. See :py:meth:`AsyncIOClient.query() - ` for details. + ` for details. .. py:coroutinemethod:: query_single(query, *args, **kwargs) @@ -626,7 +626,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`AsyncIOClient.query_single() - ` for details. + ` for details. .. py:coroutinemethod:: query_required_single(query, *args, **kwargs) @@ -636,7 +636,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`AsyncIOClient.query_required_single() - ` for details. + ` for details. .. py:coroutinemethod:: query_json(query, *args, **kwargs) @@ -646,7 +646,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`AsyncIOClient.query_json() - ` for details. + ` for details. .. py:coroutinemethod:: query_single_json(query, *args, **kwargs) @@ -657,7 +657,7 @@ See also: block. See :py:meth:`AsyncIOClient.query_single_json() - ` for details. + ` for details. .. py:coroutinemethod:: query_required_single_json(query, *args, **kwargs) @@ -668,7 +668,7 @@ See also: block. See :py:meth:`AsyncIOClient.query_requried_single_json() - ` for details. + ` for details. .. py:coroutinemethod:: execute(query) @@ -678,6 +678,6 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`AsyncIOClient.execute() - ` for details. + ` for details. -.. _RFC1004: https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst +.. _RFC1004: https://github.com/gel/rfcs/blob/master/text/1004-transactions-api.rst diff --git a/docs/clients/python/api/blocking_client.rst b/docs/clients/python/api/blocking_client.rst index 94f10dce132..4de5a1916f4 100644 --- a/docs/clients/python/api/blocking_client.rst +++ b/docs/clients/python/api/blocking_client.rst @@ -4,7 +4,7 @@ Blocking API ============ -.. py:currentmodule:: edgedb +.. py:currentmodule:: gel .. _edgedb-python-blocking-api-client: @@ -34,12 +34,12 @@ Client Returns a new :py:class:`Client` object. :param dsn: - If this parameter does not start with ``edgedb://`` then this is + If this parameter does not start with ``gel://`` then this is interpreted as the :ref:`name of a local instance `. Otherwise it specifies a single string in the following format: - ``edgedb://user:password@host:port/database?option=value``. + ``gel://user:password@host:port/database?option=value``. The following options are recognized: host, port, user, database, password. For a complete reference on DSN, see the :ref:`DSN Specification `. @@ -114,14 +114,14 @@ Client .. code-block:: python - client = edgedb.create_client() + client = gel.create_client() client.query('SELECT {1, 2, 3}') The same for transactions: .. code-block:: python - client = edgedb.create_client() + client = gel.create_client() for tx in client.transaction(): with tx: tx.query('SELECT {1, 2, 3}') @@ -138,7 +138,7 @@ Client .. py:method:: query(query, *args, **kwargs) Acquire a connection and use it to run a query and return the results - as an :py:class:`edgedb.Set` instance. The temporary + as an :py:class:`gel.Set` instance. The temporary connection is automatically returned back to the pool. :param str query: Query text. @@ -146,7 +146,7 @@ Client :param kwargs: Named query arguments. :return: - An instance of :py:class:`edgedb.Set` containing + An instance of :py:class:`gel.Set` containing the query result. Note that positional and named query arguments cannot be mixed. @@ -166,7 +166,7 @@ Client Query result. The *query* must return no more than one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` is raised, if it returns an empty set, ``None`` is returned. Note, that positional and named query arguments cannot be mixed. @@ -186,8 +186,8 @@ Client Query result. The *query* must return exactly one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` - is raised, if it returns an empty set, an ``edgedb.NoDataError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``gel.NoDataError`` is raised. Note, that positional and named query arguments cannot be mixed. @@ -235,7 +235,7 @@ Client Query result encoded in JSON. The *query* must return no more than one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` is raised, if it returns an empty set, ``"null"`` is returned. Note, that positional and named query arguments cannot be mixed. @@ -267,8 +267,8 @@ Client Query result encoded in JSON. The *query* must return exactly one element. If the query returns - more than one element, an ``edgedb.ResultCardinalityMismatchError`` - is raised, if it returns an empty set, an ``edgedb.NoDataError`` + more than one element, an ``gel.ResultCardinalityMismatchError`` + is raised, if it returns an empty set, an ``gel.NoDataError`` is raised. Note, that positional and named query arguments cannot be mixed. @@ -354,7 +354,7 @@ Client Wait until all pool connections are released, close them and shut down the pool. If any error (including timeout) occurs in ``close()`` the pool will terminate by calling - :py:meth:`~edgedb.Client.terminate`. + :py:meth:`~gel.Client.terminate`. :param float timeout: Seconds to wait, ``None`` for wait forever. @@ -593,12 +593,12 @@ See also: Acquire a connection if the current transaction doesn't have one yet, and use it to run a query and return the results - as an :py:class:`edgedb.Set` instance. The temporary + as an :py:class:`gel.Set` instance. The temporary connection is automatically returned back to the pool when exiting the transaction block. See :py:meth:`Client.query() - ` for details. + ` for details. .. py:method:: query_single(query, *args, **kwargs) @@ -608,7 +608,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`Client.query_single() - ` for details. + ` for details. .. py:method:: query_required_single(query, *args, **kwargs) @@ -618,7 +618,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`Client.query_required_single() - ` for details. + ` for details. .. py:method:: query_json(query, *args, **kwargs) @@ -628,7 +628,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`Client.query_json() - ` for details. + ` for details. .. py:method:: query_single_json(query, *args, **kwargs) @@ -639,7 +639,7 @@ See also: block. See :py:meth:`Client.query_single_json() - ` for details. + ` for details. .. py:method:: query_required_single_json(query, *args, **kwargs) @@ -650,7 +650,7 @@ See also: block. See :py:meth:`Client.query_requried_single_json() - ` for details. + ` for details. .. py:method:: execute(query) @@ -660,7 +660,7 @@ See also: returned back to the pool when exiting the transaction block. See :py:meth:`Client.execute() - ` for details. + ` for details. .. py:class:: Retry @@ -676,4 +676,4 @@ See also: be repeated. -.. _RFC1004: https://github.com/edgedb/rfcs/blob/master/text/1004-transactions-api.rst +.. _RFC1004: https://github.com/gel/rfcs/blob/master/text/1004-transactions-api.rst diff --git a/docs/clients/python/api/codegen.rst b/docs/clients/python/api/codegen.rst index c62abccffb6..a24195bb532 100644 --- a/docs/clients/python/api/codegen.rst +++ b/docs/clients/python/api/codegen.rst @@ -4,21 +4,21 @@ Code Generation =============== -.. py:currentmodule:: edgedb +.. py:currentmodule:: gel -The ``edgedb-python`` package exposes a command-line tool to generate +The ``gel-python`` package exposes a command-line tool to generate typesafe functions from ``*.edgeql`` files, using :py:mod:`dataclasses` for objects primarily. .. code-block:: bash - $ edgedb-py + $ gel-py Or alternatively: .. code-block:: bash - $ python -m edgedb.codegen + $ python -m gel.codegen Consider a simple query that lives in a file called ``get_number.edgeql``: @@ -32,11 +32,11 @@ Running the code generator will generate a new file called .. code-block:: python from __future__ import annotations - import edgedb + import gel async def get_number( - client: edgedb.AsyncIOClient, + client: gel.AsyncIOClient, *, arg: int, ) -> int: @@ -55,8 +55,8 @@ additional targets via the ``--target`` flag. .. code-block:: bash - $ edgedb-py --target async # generate async function (default) - $ edgedb-py --target blocking # generate blocking code + $ gel-py --target async # generate async function (default) + $ gel-py --target blocking # generate blocking code The names of the generated files will differ accordingly: ``{query_filename}_{target}_edgeql.py``. @@ -69,7 +69,7 @@ functions. This can be done by passing the ``--file`` flag. .. code-block:: bash - $ edgedb-py --file + $ gel-py --file This generates a single file called ``generated_{target}_edgeql.py`` in the root of your project. @@ -77,8 +77,8 @@ root of your project. Connection ~~~~~~~~~~ -The ``edgedb-py`` command supports the same set of :ref:`connection options -` as the ``edgedb`` CLI. +The ``gel-py`` command supports the same set of :ref:`connection options +` as the ``gel`` CLI. .. code-block:: diff --git a/docs/clients/python/api/types.rst b/docs/clients/python/api/types.rst index c90710e5972..3598bce83ee 100644 --- a/docs/clients/python/api/types.rst +++ b/docs/clients/python/api/types.rst @@ -4,27 +4,27 @@ Datatypes ========= -.. py:currentmodule:: edgedb +.. py:currentmodule:: gel -edgedb-python automatically converts EdgeDB types to the corresponding Python +gel-python automatically converts |Gel| types to the corresponding Python types and vice versa. -The table below shows the correspondence between EdgeDB and Python types. +The table below shows the correspondence between Gel and Python types. +----------------------------+-----------------------------------------------------+ -| EdgeDB Type | Python Type | +| Gel Type | Python Type | +============================+=====================================================+ -| ``Set`` | :py:class:`edgedb.Set` | +| ``Set`` | :py:class:`gel.Set` | +----------------------------+-----------------------------------------------------+ -| ``array`` | :py:class:`edgedb.Array` | +| ``array`` | :py:class:`gel.Array` | +----------------------------+-----------------------------------------------------+ -| ``anytuple`` | :py:class:`edgedb.Tuple` or | -| | :py:class:`edgedb.NamedTuple` | +| ``anytuple`` | :py:class:`gel.Tuple` or | +| | :py:class:`gel.NamedTuple` | +----------------------------+-----------------------------------------------------+ -| ``anyenum`` | :py:class:`edgedb.EnumValue` | +| ``anyenum`` | :py:class:`gel.EnumValue` | +----------------------------+-----------------------------------------------------+ -| ``Object`` | :py:class:`edgedb.Object` | +| ``Object`` | :py:class:`gel.Object` | +----------------------------+-----------------------------------------------------+ | ``bool`` | :py:class:`bool ` | +----------------------------+-----------------------------------------------------+ @@ -40,9 +40,9 @@ The table below shows the correspondence between EdgeDB and Python types. | ``cal::local_datetime`` | offset-naive :py:class:`datetime.datetime \ | | | ` | +----------------------------+-----------------------------------------------------+ -| ``cal::relative_duration`` | :py:class:`edgedb.RelativeDuration` | +| ``cal::relative_duration`` | :py:class:`gel.RelativeDuration` | +----------------------------+-----------------------------------------------------+ -| ``cal::date_duration`` | :py:class:`edgedb.DateDuration` | +| ``cal::date_duration`` | :py:class:`gel.DateDuration` | +----------------------------+-----------------------------------------------------+ | ``datetime`` | offset-aware :py:class:`datetime.datetime \ | | | ` | @@ -95,24 +95,24 @@ Objects .. versionchanged:: 1.0 - ``edgedb.Object`` instances are dataclass-compatible since version 1.0, + ``gel.Object`` instances are dataclass-compatible since version 1.0, for example, ``dataclasses.is_dataclass()`` will return ``True``, and - ``dataclasses.asdict()`` will work on ``edgedb.Object`` instances. + ``dataclasses.asdict()`` will work on ``gel.Object`` instances. .. versionchanged:: 1.0 - ``edgedb.Object.__hash__`` is just ``object.__hash__`` in version 1.0. + ``gel.Object.__hash__`` is just ``object.__hash__`` in version 1.0. Similarly, ``==`` is equivalent to the ``is`` operator comparing - ``edgedb.Object`` instances, and ``<``, ``<=``, ``>``, ``>=`` are not - allowed on ``edgedb.Object`` instances. + ``gel.Object`` instances, and ``<``, ``<=``, ``>``, ``>=`` are not + allowed on ``gel.Object`` instances. The value of an object property or a link can be accessed through a corresponding attribute: .. code-block:: pycon - >>> import edgedb - >>> client = edgedb.create_client() + >>> import gel + >>> client = gel.create_client() >>> r = client.query_single(''' ... SELECT schema::ObjectType {name} ... FILTER .name = 'std::Object' @@ -124,7 +124,7 @@ Objects .. describe:: obj[linkname] - Return a :py:class:`edgedb.Link` or a :py:class:`edgedb.LinkSet` instance + Return a :py:class:`gel.Link` or a :py:class:`gel.LinkSet` instance representing the instance(s) of link *linkname* associated with *obj*. @@ -132,8 +132,8 @@ Objects .. code-block:: pycon - >>> import edgedb - >>> client = edgedb.create_client() + >>> import gel + >>> client = gel.create_client() >>> r = client.query_single(''' ... SELECT schema::Property {name, annotations: {name, @value}} ... FILTER .name = 'listen_port' @@ -163,7 +163,7 @@ Links An immutable representation of an object link. - Links are created when :py:class:`edgedb.Object` is accessed via + Links are created when :py:class:`gel.Object` is accessed via a ``[]`` operator. Using Link objects explicitly is useful for accessing link properties. @@ -172,7 +172,7 @@ Links An immutable representation of a set of Links. - LinkSets are created when a multi link on :py:class:`edgedb.Object` + LinkSets are created when a multi link on :py:class:`gel.Object` is accessed via a ``[]`` operator. @@ -189,21 +189,21 @@ Named Tuples .. py:class:: NamedTuple() - An immutable value representing an EdgeDB named tuple value. + An immutable value representing an Gel named tuple value. .. versionchanged:: 1.0 - ``edgedb.NamedTuple`` is a subclass of :py:class:`tuple ` + ``gel.NamedTuple`` is a subclass of :py:class:`tuple ` and is duck-type compatible with ``collections.namedtuple`` since version 1.0. - Instances of ``edgedb.NamedTuple`` generally behave similarly to + Instances of ``gel.NamedTuple`` generally behave similarly to :py:func:`namedtuple `: .. code-block:: pycon - >>> import edgedb - >>> client = edgedb.create_client() + >>> import gel + >>> client = gel.create_client() >>> r = client.query_single('''SELECT (a := 1, b := 'a', c := [3])''') >>> r (a := 1, b := 'a', c := [3]) @@ -230,15 +230,15 @@ RelativeDuration .. py:class:: RelativeDuration() - An immutable value representing an EdgeDB ``cal::relative_duration`` value. + An immutable value representing an Gel ``cal::relative_duration`` value. .. code-block:: pycon - >>> import edgedb - >>> client = edgedb.create_client() + >>> import gel + >>> client = gel.create_client() >>> r = client.query_single('''SELECT "1 year 2 days 3 seconds"''') >>> r - + >>> r.months 12 >>> r.days @@ -252,15 +252,15 @@ DateDuration .. py:class:: DateDuration() - An immutable value representing an EdgeDB ``cal::date_duration`` value. + An immutable value representing an Gel ``cal::date_duration`` value. .. code-block:: pycon - >>> import edgedb - >>> client = edgedb.create_client() + >>> import gel + >>> client = gel.create_client() >>> r = client.query_single('''SELECT "1 year 2 days"''') >>> r - + >>> r.months 12 >>> r.days @@ -272,22 +272,22 @@ EnumValue .. py:class:: EnumValue() - An immutable value representing an EdgeDB enum value. + An immutable value representing an Gel enum value. .. versionchanged:: 1.0 - Since version 1.0, ``edgedb.EnumValue`` is a subclass of + Since version 1.0, ``gel.EnumValue`` is a subclass of :py:class:`enum.Enum `. Actual enum values are instances of ad-hoc enum classes created by the codecs to represent - the actual members defined in your EdgeDB schema. + the actual members defined in your Gel schema. .. code-block:: pycon - >>> import edgedb - >>> client = edgedb.create_client() + >>> import gel + >>> client = gel.create_client() >>> r = client.query_single("""SELECT 'red'""") >>> r - + >>> str(r) 'red' >>> r.value # added in 1.0 diff --git a/docs/clients/python/index.rst b/docs/clients/python/index.rst index 7fb093b2c18..874d934117c 100644 --- a/docs/clients/python/index.rst +++ b/docs/clients/python/index.rst @@ -1,10 +1,10 @@ .. _edgedb-python-intro: -==================== -EdgeDB Python Driver -==================== +================= +Gel Python Driver +================= -**edgedb-python** is the official EdgeDB driver for Python. +**gel-python** is the official |Gel| driver for Python. It provides both :ref:`blocking IO ` and :ref:`asyncio ` implementations. @@ -12,7 +12,7 @@ and :ref:`asyncio ` implementations. * :ref:`edgedb-python-installation` - edgedb-python is installable via ``$ pip install edgedb``. Read + gel-python is installable via ``$ pip install gel``. Read the section for more information on how to install the library. * :ref:`edgedb-python-examples` @@ -30,7 +30,7 @@ and :ref:`asyncio ` implementations. * :ref:`edgedb-python-datatypes` - EdgeDB Python types documentation. + Gel Python types documentation. * :ref:`edgedb-python-codegen` diff --git a/docs/clients/python/installation.rst b/docs/clients/python/installation.rst index b22c81ff0bb..3f16c29aee8 100644 --- a/docs/clients/python/installation.rst +++ b/docs/clients/python/installation.rst @@ -4,11 +4,11 @@ Installation ============ -The recommended way to install the EdgeDB driver is to use **pip**: +The recommended way to install the |Gel| driver is to use **pip**: .. code-block:: bash - $ pip install edgedb + $ pip install gel .. note:: @@ -22,7 +22,7 @@ The recommended way to install the EdgeDB driver is to use **pip**: Building from source -------------------- -If you want to build the EdgeDB driver from a Git checkout you will need: +If you want to build the |Gel| driver from a Git checkout you will need: * A working C compiler. * CPython header files. These can usually be obtained by installing @@ -48,7 +48,7 @@ building: Running tests ------------- -The testsuite requires a working local installation of the EdgeDB server. +The testsuite requires a working local installation of the Gel server. To execute the testsuite run: .. code-block:: bash diff --git a/docs/clients/python/requirements.txt b/docs/clients/python/requirements.txt deleted file mode 100644 index 910cf81fc97..00000000000 --- a/docs/clients/python/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -sphinxcontrib-asyncio -sphinx_rtd_theme diff --git a/docs/clients/python/usage.rst b/docs/clients/python/usage.rst index e222ecad3b8..4619c380345 100644 --- a/docs/clients/python/usage.rst +++ b/docs/clients/python/usage.rst @@ -3,15 +3,15 @@ Basic Usage =========== -To start using EdgeDB in Python, create an :py:class:`edgedb.Client` instance -using :py:func:`edgedb.create_client`: +To start using Gel in Python, create an :py:class:`gel.Client` instance +using :py:func:`gel.create_client`: .. code-block:: python import datetime - import edgedb + import gel - client = edgedb.create_client() + client = gel.create_client() client.query(""" INSERT User { @@ -28,16 +28,16 @@ using :py:func:`edgedb.create_client`: client.close() When used with asyncio, this should be replaced with -:py:func:`edgedb.create_async_client` which creates an instance of the -:py:class:`~edgedb.AsyncIOClient`: +:py:func:`gel.create_async_client` which creates an instance of the +:py:class:`~gel.AsyncIOClient`: .. code-block:: python import asyncio import datetime - import edgedb + import gel - client = edgedb.create_async_client() + client = gel.create_async_client() async def main(): await client.query(""" @@ -57,10 +57,10 @@ When used with asyncio, this should be replaced with asyncio.run(main()) -Connect to EdgeDB ------------------ +Connect to Gel +-------------- -The examples above only work under an :ref:`EdgeDB project +The examples above only work under an :ref:`Gel project `. You could also provide your own connection parameters, refer to the :ref:`Client Library Connection ` docs for details. @@ -69,7 +69,7 @@ parameters, refer to the :ref:`Client Library Connection Type conversion --------------- -edgedb-python automatically converts EdgeDB types to the corresponding Python +gel-python automatically converts Gel types to the corresponding Python types and vice versa. See :ref:`edgedb-python-datatypes` for details. @@ -80,12 +80,12 @@ Client connection pools For server-type applications that handle frequent requests and need the database connection for a short period of time while handling a request, -the use of a connection pool is recommended. Both :py:class:`edgedb.Client` -and :py:class:`edgedb.AsyncIOClient` come with such a pool. +the use of a connection pool is recommended. Both :py:class:`gel.Client` +and :py:class:`gel.AsyncIOClient` come with such a pool. -For :py:class:`edgedb.Client`, all methods are thread-safe. You can share the +For :py:class:`gel.Client`, all methods are thread-safe. You can share the same client instance safely across multiple threads, and run queries -concurrently. Likewise, :py:class:`~edgedb.AsyncIOClient` is designed to be +concurrently. Likewise, :py:class:`~gel.AsyncIOClient` is designed to be shared among different :py:class:`asyncio.Task`/coroutines for concurrency. Below is an example of a web API server running `aiohttp @@ -94,7 +94,7 @@ Below is an example of a web API server running `aiohttp .. code-block:: python import asyncio - import edgedb + import gel from aiohttp import web @@ -118,7 +118,7 @@ Below is an example of a web API server running `aiohttp """Initialize the application server.""" app = web.Application() # Create a database client - app['client'] = edgedb.create_async_client( + app['client'] = gel.create_async_client( database='my_service', user='my_service') # Configure service routes @@ -146,8 +146,8 @@ The most robust way to create a :ref:`transaction ` is the ``transaction()`` method: -* :py:meth:`AsyncIOClient.transaction() ` -* :py:meth:`Client.transaction() ` +* :py:meth:`AsyncIOClient.transaction() ` +* :py:meth:`Client.transaction() ` Example: diff --git a/docs/cloud/cli.rst b/docs/cloud/cli.rst index 62ff9f31b6a..73e8742154f 100644 --- a/docs/cloud/cli.rst +++ b/docs/cloud/cli.rst @@ -6,15 +6,15 @@ CLI :edb-alt-title: Using Gel Cloud via the CLI -To use EdgeDB Cloud via the CLI, first log in using +To use |Gel| Cloud via the CLI, first log in using :ref:`ref_cli_edgedb_cloud_login`. .. note:: This is the way you'll log in interactively on your development machine, - but when interacting with EdgeDB Cloud via a script or in CI, you'll + but when interacting with Gel Cloud via a script or in CI, you'll instead set the ``EDGEDB_SECRET_KEY`` environment variable to your secret - key. Generate a secret key in the EdgeDB Cloud UI or by running + key. Generate a secret key in the Gel Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create`. The ``edgedb cloud login`` and ``edgedb cloud logout`` commands are not intended for use in this context. @@ -45,7 +45,7 @@ create a local project linked to your instance. .. note:: - Please be aware of the following restrictions on EdgeDB Cloud instance + Please be aware of the following restrictions on |Gel| Cloud instance names: * can contain only Latin alpha-numeric characters or ``-`` diff --git a/docs/cloud/deploy/index.rst b/docs/cloud/deploy/index.rst index 582c5329973..e4ed408fe36 100644 --- a/docs/cloud/deploy/index.rst +++ b/docs/cloud/deploy/index.rst @@ -12,7 +12,7 @@ instance with :ref:`ref_cli_edgedb_cloud_secretkey_create` or via the web UI's accessible to your production application: * ``EDGEDB_SECRET_KEY``- contains the secret key you generated -* ``EDGEDB_INSTANCE``- the name of your EdgeDB Cloud instance +* ``EDGEDB_INSTANCE``- the name of your Gel Cloud instance (``/``) If you use one of these platforms, try the platform's guide for diff --git a/docs/cloud/deploy/netlify.rst b/docs/cloud/deploy/netlify.rst index 19e96d2be61..e52f880803d 100644 --- a/docs/cloud/deploy/netlify.rst +++ b/docs/cloud/deploy/netlify.rst @@ -4,24 +4,24 @@ Netlify ======= -:edb-alt-title: Deploying applications built on Gel Cloud to Netlify +:edb-alt-title: Deploying applications built on |Gel| Cloud to Netlify .. note:: This guide assumes the Git deployment method on Netlify, but you may also - deploy your site using other methods. Just make sure the EdgeDB Cloud + deploy your site using other methods. Just make sure the Gel Cloud environment variables are set, and your app should have connectivity to your instance. 1. Push project to GitHub or some other Git remote repository -2. Create and make note of a secret key for your EdgeDB Cloud instance +2. Create and make note of a secret key for your Gel Cloud instance 3. On your Netlify Team Overview view under Sites, click Import from Git 4. Import your project's repository 5. Configure the build settings appropriately for your app 6. Click the Add environment variable button 7. Use the New variable button to add two variables: - - ``EDGEDB_INSTANCE`` containing your EdgeDB Cloud instance name (in + - ``EDGEDB_INSTANCE`` containing your Gel Cloud instance name (in ``/`` format) - ``EDGEDB_SECRET_KEY`` containing the secret key you created and noted previously. @@ -32,5 +32,5 @@ Netlify :width: 100% :alt: A screenshot of the Netlify deployment configuration view highlighting the environment variables section where a user will - need to set the necessary variables for EdgeDB Cloud instance + need to set the necessary variables for Gel Cloud instance connection. diff --git a/docs/cloud/deploy/railway.rst b/docs/cloud/deploy/railway.rst index 1ffef2fdd4f..b7b7c3a0cf6 100644 --- a/docs/cloud/deploy/railway.rst +++ b/docs/cloud/deploy/railway.rst @@ -4,15 +4,15 @@ Railway ======= -:edb-alt-title: Deploying applications built on Gel Cloud to Railway +:edb-alt-title: Deploying applications built on |Gel| Cloud to Railway 1. Push project to GitHub or some other Git remote repository -2. Create and make note of a secret key for your EdgeDB Cloud instance +2. Create and make note of a secret key for your Gel Cloud instance 3. From Railway's dashboard, click the "New Project" button 4. Select the repository you want to deploy 5. Click the "Add variables" button to add the following environment variables: - - ``EDGEDB_INSTANCE`` containing your EdgeDB Cloud instance name (in + - ``EDGEDB_INSTANCE`` containing your Gel Cloud instance name (in ``/`` format) - ``EDGEDB_SECRET_KEY`` containing the secret key you created and noted previously. @@ -23,5 +23,5 @@ Railway :width: 100% :alt: A screenshot of the Railway deployment configuration view highlighting the environment variables section where a user will - need to set the necessary variables for EdgeDB Cloud instance + need to set the necessary variables for Gel Cloud instance connection. diff --git a/docs/cloud/deploy/render.rst b/docs/cloud/deploy/render.rst index 9fadd22923b..eeb1382d327 100644 --- a/docs/cloud/deploy/render.rst +++ b/docs/cloud/deploy/render.rst @@ -4,16 +4,16 @@ Render ====== -:edb-alt-title: Deploying applications built on Gel Cloud to Render +:edb-alt-title: Deploying applications built on |Gel| Cloud to Render 1. Push project to GitHub or some other Git remote repository -2. Create and make note of a secret key for your EdgeDB Cloud instance +2. Create and make note of a secret key for your Gel Cloud instance 3. From Render's dashboard, click "New > Web Service" 4. Import your project's repository 5. In the setup page, scroll down to the "Environment Variables" section and add the following environment variables: - - ``EDGEDB_INSTANCE`` containing your EdgeDB Cloud instance name (in + - ``EDGEDB_INSTANCE`` containing your Gel Cloud instance name (in ``/`` format) - ``EDGEDB_SECRET_KEY`` containing the secret key you created and noted previously. @@ -24,5 +24,5 @@ Render :width: 100% :alt: A screenshot of the Render deployment configuration view highlighting the environment variables section where a user - will need to set the necessary variables for EdgeDB Cloud instance + will need to set the necessary variables for Gel Cloud instance connection. diff --git a/docs/cloud/deploy/vercel.rst b/docs/cloud/deploy/vercel.rst index 29f076a5288..a0d81e71b05 100644 --- a/docs/cloud/deploy/vercel.rst +++ b/docs/cloud/deploy/vercel.rst @@ -7,12 +7,12 @@ Vercel :edb-alt-title: Deploying applications built on Gel Cloud to Vercel 1. Push project to GitHub or some other Git remote repository -2. Create and make note of a secret key for your EdgeDB Cloud instance +2. Create and make note of a secret key for your Gel Cloud instance 3. From Vercel's Overview tab, click Add New > Project 4. Import your project's repository 5. In "Configure Project," expand "Environment Variables" to add two variables: - - ``EDGEDB_INSTANCE`` containing your EdgeDB Cloud instance name (in + - ``EDGEDB_INSTANCE`` containing your Gel Cloud instance name (in ``/`` format) - ``EDGEDB_SECRET_KEY`` containing the secret key you created and noted previously. @@ -23,4 +23,4 @@ Vercel :width: 100% :alt: A screenshot of the Vercel deployment configuration view highlighting the environment variables section where a user will need to set the - necessary variables for EdgeDB Cloud instance connection. + necessary variables for |Gel| Cloud instance connection. diff --git a/docs/cloud/http_gql.rst b/docs/cloud/http_gql.rst index dc87114de44..c6b88c3e711 100644 --- a/docs/cloud/http_gql.rst +++ b/docs/cloud/http_gql.rst @@ -6,7 +6,7 @@ HTTP & GraphQL APIs :edb-alt-title: Querying Gel Cloud over HTTP and GraphQL -Using EdgeDB Cloud via HTTP and GraphQL works the same as :ref:`using any other +Using |Gel| Cloud via HTTP and GraphQL works the same as :ref:`using any other EdgeDB instance `. The two differences are in **how to discover your instance's URL** and **authentication**. @@ -37,10 +37,10 @@ Your instance can now receive EdgeQL queries over HTTP at Instance URL ============ -To determine the URL of an EdgeDB Cloud instance, find the host by running +To determine the URL of an |Gel| Cloud instance, find the host by running ``edgedb instance credentials -I /``. Use the ``host`` and ``port`` from that table in the URL format above this note. -Change the protocol to ``https`` since EdgeDB Cloud instances are secured +Change the protocol to ``https`` since Gel Cloud instances are secured with TLS. Your instance can now receive EdgeQL queries over HTTP at @@ -51,8 +51,8 @@ Authentication ============== -To authenticate to your EdgeDB Cloud instance, first create a secret key using -the EdgeDB Cloud UI or :ref:`ref_cli_edgedb_cloud_secretkey_create`. Use the +To authenticate to your |Gel| Cloud instance, first create a secret key using +the Gel Cloud UI or :ref:`ref_cli_edgedb_cloud_secretkey_create`. Use the secret key as your token with the bearer authentication method. Here is an example showing how you might send the query ``select Person {*};`` using cURL: @@ -70,7 +70,7 @@ example showing how you might send the query ``select Person {*};`` using cURL: Usage ===== -Usage of the HTTP and GraphQL APIs is identical on an EdgeDB Cloud instance. +Usage of the HTTP and GraphQL APIs is identical on an |Gel| Cloud instance. Reference the HTTP and GraphQL documentation for more information. diff --git a/docs/cloud/index.rst b/docs/cloud/index.rst index 073e9489f12..0d9ae2b6996 100644 --- a/docs/cloud/index.rst +++ b/docs/cloud/index.rst @@ -6,9 +6,9 @@ Cloud :edb-alt-title: Using Gel Cloud -EdgeDB Cloud is the easiest way to host your EdgeDB instance. We offer two ways -to interact with EdgeDB Cloud: via our CLI or through a graphical web -interface nearly identical to the :ref:`EdgeDB UI `. +|Gel| Cloud is the easiest way to host your Gel instance. We offer two ways +to interact with Gel Cloud: via our CLI or through a graphical web +interface nearly identical to the :ref:`Gel UI `. .. edb:youtube-embed:: IG1MggUzzH4 @@ -31,7 +31,7 @@ interface nearly identical to the :ref:`EdgeDB UI `. Questions? Problems? Bugs? ========================== -Thank you for helping us make the best way to host your EdgeDB instances even +Thank you for helping us make the best way to host your Gel instances even better! * Please join us on `our Discord `_ to ask @@ -40,7 +40,7 @@ better! `_ for information on what may be causing it. * Report any bugs you find by `submitting a support ticket - `_. Note: when using EdgeDB Cloud + `_. Note: when using |Gel| Cloud through the CLI, setting the ``RUST_LOG`` environment variable to ``info``, ``debug``, or ``trace`` may provide additional debugging information which will be useful to include with your ticket. diff --git a/docs/cloud/web.rst b/docs/cloud/web.rst index 7a98158e80d..8d04db148b4 100644 --- a/docs/cloud/web.rst +++ b/docs/cloud/web.rst @@ -6,7 +6,7 @@ Web GUI :edb-alt-title: Using Gel Cloud via the web GUI -If you'd prefer, you can also manage your account via `the EdgeDB Cloud +If you'd prefer, you can also manage your account via `the |Gel| Cloud web-based GUI `_. The first time you access the web UI, you will be prompted to log in. Once you @@ -17,19 +17,19 @@ organization settings and billing. Instances --------- -If this is your first time accessing EdgeDB Cloud, this list will be empty. To +If this is your first time accessing Gel Cloud, this list will be empty. To create an instance, click "Create new instance." This will pop up a modal -allowing you to name your instance and specify the version of EdgeDB and the +allowing you to name your instance and specify the version of Gel and the region for the instance. Once the instance has been created, you'll see the instance dashboard which allows you to monitor your instance, navigate to the management page for its branches, and create secret keys. -You'll also see instructions in the bottom-right for linking your EdgeDB CLI to -your EdgeDB Cloud account. You do this by running the CLI command ``edgedb -cloud login``. This will make all of your EdgeDB Cloud instances accessible via -the CLI. You can manage them just as you would other remote EdgeDB instances. +You'll also see instructions in the bottom-right for linking your |Gel| CLI to +your Gel Cloud account. You do this by running the CLI command ``edgedb +cloud login``. This will make all of your Gel Cloud instances accessible via +the CLI. You can manage them just as you would other remote Gel instances. If you want to manage a branch of your database, click through on the instance's name from the top right of the instance dashboard. If you just @@ -46,7 +46,7 @@ Org Settings This tab allows you to add GitHub organizations for which you are an admin. If you don't see your organization's name here, you may need to update your -`org settings`_ in GitHub to allow EdgeDB Cloud to read your list of +`org settings`_ in GitHub to allow |Gel| Cloud to read your list of organizations, and then refresh the org list. .. lint-off diff --git a/docs/datamodel/access_policies.rst b/docs/datamodel/access_policies.rst index 8ba5e584146..7b9b7deb349 100644 --- a/docs/datamodel/access_policies.rst +++ b/docs/datamodel/access_policies.rst @@ -113,7 +113,7 @@ execute queries. The exact API depends on which client library you're using: .. code-tab:: typescript - import createClient from 'edgedb'; + import createClient from 'gel'; const client = createClient().withGlobals({ current_user: '2141a5b4-5634-4ccc-b835-437863534c51', @@ -123,7 +123,7 @@ execute queries. The exact API depends on which client library you're using: .. code-tab:: python - from edgedb import create_client + from gel import create_client client = create_client().with_globals({ 'current_user': '580cc652-8ab8-4a20-8db9-4c79a4b1fd81' @@ -142,23 +142,23 @@ execute queries. The exact API depends on which client library you're using: "fmt" "log" - "github.com/edgedb/edgedb-go" + "github.com/geldata/gel-go" ) func main() { ctx := context.Background() - client, err := edgedb.CreateClient(ctx, edgedb.Options{}) + client, err := gel.CreateClient(ctx, gel.Options{}) if err != nil { log.Fatal(err) } defer client.Close() - id, err := edgedb.ParseUUID("2141a5b4-5634-4ccc-b835-437863534c51") + id, err := gel.ParseUUID("2141a5b4-5634-4ccc-b835-437863534c51") if err != nil { log.Fatal(err) } - var result edgedb.UUID + var result gel.UUID err = client. WithGlobals(map[string]interface{}{"current_user": id}). QuerySingle(ctx, "SELECT global current_user;", &result) @@ -171,12 +171,12 @@ execute queries. The exact API depends on which client library you're using: .. code-tab:: rust - use edgedb_protocol::{ + use gel_protocol::{ model::Uuid, value::EnumValue }; - let client = edgedb_tokio::create_client() + let client = gel_tokio::create_client() .await .expect("Client should init") .with_globals_fn(|c| { @@ -228,7 +228,7 @@ Let's add two policies to our sample schema. + } + access policy author_has_read_access + allow select - + using (global current_user ?= .author.id + + using (global current_user ?= .author.id + and global current_country ?= Country.ReadOnly); } @@ -256,7 +256,7 @@ Let's add two policies to our sample schema. + } + access policy author_has_read_access + allow select - + using (global current_user ?= .author.id + + using (global current_user ?= .author.id + and global current_country ?= Country.ReadOnly); } @@ -290,9 +290,9 @@ Let's do some experiments. .. code-block:: edgeql-repl - db> insert User { email := "test@edgedb.com" }; + db> insert User { email := "test@geldata.com" }; {default::User {id: be44b326-03db-11ed-b346-7f1594474966}} - db> set global current_user := + db> set global current_user := ... "be44b326-03db-11ed-b346-7f1594474966"; OK: SET GLOBAL db> set global current_country := Country.Full; @@ -328,7 +328,7 @@ yet been given permission to roll out our service. ... title := "My second post", ... author := (select User filter .id = global current_user) ... }; - edgedb error: AccessPolicyError: access policy violation on + gel error: AccessPolicyError: access policy violation on insert of default::BlogPost (User does not have full access) db> set global current_country := Country.None; OK: SET GLOBAL @@ -341,7 +341,7 @@ operation, the operation may or may not work and thus we have provided a helpful error message in the access policy to give users a heads up on what went wrong. -Now let's move back to a country with full access, but set the +Now let's move back to a country with full access, but set the ``global current_user`` to some other id: a new user that has yet to write any blog posts. Now the number of ``BlogPost`` objects returned via the ``count`` function is zero: @@ -416,7 +416,7 @@ Resolution order ^^^^^^^^^^^^^^^^ An object type can contain an arbitrary number of access policies, including -several conflicting ``allow`` and ``deny`` policies. EdgeDB uses a particular +several conflicting ``allow`` and ``deny`` policies. |Gel| uses a particular algorithm for resolving these policies. .. figure:: images/ols.png @@ -426,7 +426,7 @@ algorithm for resolving these policies. 1. When no policies are defined on a given object type, all objects of that type can be read or modified by any appropriately authenticated connection. -2. EdgeDB then applies all ``allow`` policies. Each policy grants a +2. Gel then applies all ``allow`` policies. Each policy grants a *permission* that is scoped to a particular *set of objects* as defined by the ``using`` clause. Conceptually, these permissions are merged with the ``union`` / ``or`` operator to determine the set of allowable actions. @@ -508,7 +508,7 @@ making the current user able to see their own ``User`` record. .. note:: - Starting with EdgeDB 3.0, access policy restrictions will **not** apply to + Starting with |EdgeDB| 3.0, access policy restrictions will **not** apply to any access policy expression. This means that when reasoning about access policies it is no longer necessary to take other policies into account. Instead, all data is visible for the purpose of *defining* an access @@ -521,7 +521,7 @@ making the current user able to see their own ``User`` record. other's expressions. It is possible (and recommended) to enable this :ref:`future - ` behavior in EdgeDB 2.6 and later by adding the + ` behavior in |EdgeDB| 2.6 and later by adding the following to the schema: ``using future nonrecursive_access_policies;`` Custom error messages @@ -536,7 +536,7 @@ policy, you will get a generic error message. .. code-block:: - edgedb error: AccessPolicyError: access policy violation on insert of + gel error: AccessPolicyError: access policy violation on insert of .. note:: @@ -585,7 +585,7 @@ will receive this error: .. code-block:: - edgedb error: AccessPolicyError: access policy violation on insert of + gel error: AccessPolicyError: access policy violation on insert of default::User (Only admins may query Users) Disabling policies @@ -597,8 +597,8 @@ You may disable all access policies by setting the ``apply_access_policies`` :ref:`configuration parameter ` to ``false``. You may also toggle access policies using the "Disable Access Policies" -checkbox in the "Config" dropdown in the EdgeDB UI (accessible by running -the CLI command ``edgedb ui`` from inside your project). This is the most +checkbox in the "Config" dropdown in the Gel UI (accessible by running +the CLI command ``gel ui`` from inside your project). This is the most convenient way to temporarily disable access policies since it applies only to your UI session. diff --git a/docs/datamodel/comparison.rst b/docs/datamodel/comparison.rst index 55a7cf538fb..c6a14ee8e8d 100644 --- a/docs/datamodel/comparison.rst +++ b/docs/datamodel/comparison.rst @@ -33,7 +33,7 @@ to query across tables. director_id uuid REFERENCES people(id) ); -In EdgeDB, connections between tables are represented with :ref:`Links +In |Gel|, connections between tables are represented with :ref:`Links `. .. code-block:: sdl @@ -94,6 +94,6 @@ downsides too. the ORM library, not the maintainers of the database itself. Quality control and long-term maintenance is not always guaranteed. -From the beginning, EdgeDB was designed to incorporate the best aspects of ORMs +From the beginning, Gel was designed to incorporate the best aspects of ORMs — declarative modeling, object-oriented APIs, and intuitive querying — without the drawbacks. diff --git a/docs/datamodel/computeds.rst b/docs/datamodel/computeds.rst index ae547da2dd0..9cc0ba34aea 100644 --- a/docs/datamodel/computeds.rst +++ b/docs/datamodel/computeds.rst @@ -17,23 +17,8 @@ Object types can contain *computed* properties and links. Computed properties and links are not persisted in the database. Instead, they are evaluated *on the fly* whenever that field is queried. Computed properties must be declared with the ``property`` keyword and computed links must be declared with the -``link`` keyword in EdgeDB versions prior to 4.0. +``link`` keyword in |EdgeDB| versions prior to 4.0. -.. code-block:: sdl - :version-lt: 3.0 - - type Person { - property name -> str; - property all_caps_name := str_upper(__source__.name); - } - -.. code-block:: sdl - :version-lt: 4.0 - - type Person { - name: str; - property all_caps_name := str_upper(__source__.name); - } .. code-block:: sdl @@ -198,7 +183,7 @@ queries, consider defining a computed field that encapsulates the filter. Backlinks ^^^^^^^^^ -Backlinks are one of the most common use cases for computed links. In EdgeDB +Backlinks are one of the most common use cases for computed links. In |Gel| links are *directional*; they have a source and a target. Often it's convenient to traverse a link in the *reverse* direction. diff --git a/docs/datamodel/extensions.rst b/docs/datamodel/extensions.rst index 0a10d2e00e4..52662326e28 100644 --- a/docs/datamodel/extensions.rst +++ b/docs/datamodel/extensions.rst @@ -6,7 +6,7 @@ Extensions .. index:: using extension -Extensions are the way EdgeDB adds more functionality. In principle, +Extensions are the way |Gel| adds more functionality. In principle, extensions could add new types, scalars, functions, etc., but, more importantly, they can add new ways of interacting with the database. @@ -20,7 +20,7 @@ There are a few built-in extensions available: - ``edgeql_http``: enables :ref:`EdgeQL over HTTP `, - ``graphql``: enables :ref:`GraphQL `, -- ``auth``: enables :ref:`EdgeDB Auth `, +- ``auth``: enables :ref:`Gel Auth `, - ``ai``: enables :ref:`ext::ai module `, - ``pg_trgm``: enables ``ext::pg_trgm``, which re-exports diff --git a/docs/datamodel/functions.rst b/docs/datamodel/functions.rst index bba6de34d37..29dd47df487 100644 --- a/docs/datamodel/functions.rst +++ b/docs/datamodel/functions.rst @@ -8,7 +8,7 @@ Functions .. note:: - This page documents how to define custom functions, however EdgeDB provides a + This page documents how to define custom functions, however |Gel| provides a large library of built-in functions and operators. These are documented in :ref:`Standard Library `. diff --git a/docs/datamodel/future.rst b/docs/datamodel/future.rst index 81089f8d945..4128715577c 100644 --- a/docs/datamodel/future.rst +++ b/docs/datamodel/future.rst @@ -6,12 +6,12 @@ Future Behavior .. index:: future, nonrecursive_access_policies -Any time that we add new functionality to EdgeDB we strive to do it in the +Any time that we add new functionality to |Gel| we strive to do it in the least disruptive way possible. Deprecation warnings, documentation and guides can help make these transitions smoother, but sometimes the changes are just too big, especially if they affect already existing functionality. It is often inconvenient dealing with these changes at the same time as upgrading to a new -major version of EdgeDB. To help with this transition we introduce +major version of Gel. To help with this transition we introduce :ref:`future ` specification. The purpose of this specification is to provide a way to try out and ease into diff --git a/docs/datamodel/index.rst b/docs/datamodel/index.rst index e7de44af61a..aedc9a12a02 100644 --- a/docs/datamodel/index.rst +++ b/docs/datamodel/index.rst @@ -32,13 +32,13 @@ Schema introspection/index -EdgeDB schemas are declared using **SDL** (EdgeDB's Schema Definition +|Gel| schemas are declared using **SDL** (Gel's Schema Definition Language). SDL --- -Your schema is defined inside ``.esdl`` files. It's common to define your +Your schema is defined inside |.gel| files. It's common to define your entire schema in a single file called ``default.esdl``, but you can split it across multiple files if you wish. @@ -65,13 +65,13 @@ in the root of your project. itemdetails?itemName=magicstack.edgedb>`_, `Sublime Text `_, `Atom `_, and `Vim `_. + geldata/edgedb-vim>`_. Migrations ---------- -EdgeDB's baked-in migration system lets you painlessly evolve your schema over -time. Just update the contents of your ``.esdl`` file(s) and use the EdgeDB CLI +Gel's baked-in migration system lets you painlessly evolve your schema over +time. Just update the contents of your ``.esdl`` file(s) and use the |Gel| CLI to *create* and *apply* migrations. .. code-block:: bash @@ -102,8 +102,8 @@ Terminology Instance ^^^^^^^^ -An EdgeDB **instance** is a running EdgeDB process. Instances can be created, -started, stopped, and destroyed locally with the :ref:`EdgeDB CLI +An |Gel| **instance** is a running Gel process. Instances can be created, +started, stopped, and destroyed locally with the :ref:`Gel CLI `. .. _ref_datamodel_databases: @@ -128,7 +128,7 @@ Database .. versionadded:: 5.0 - In EdgeDB 5, databases were replaced by branches. + In |EdgeDB| 5, databases were replaced by branches. Each instance can contain several **databases**, each with a unique name. At the time of creation, all instances contain a single default database called @@ -178,9 +178,7 @@ schema inside a single module called ``default``. } Here we have a ``dracula`` module containing a ``Person`` type. Nested in - the ``dracula`` module we have a ``combat`` module which will be used for - all the combat functionality for our game based on Bram Stoker's Dracula we - built in the `Easy EdgeDB textbook `_. + the ``dracula`` module we have a ``combat`` module. .. _ref_name_resolution: @@ -189,7 +187,7 @@ schema inside a single module called ``default``. When referencing schema objects from another module, you must use a *fully-qualified* name in the form ``module_name::object_name``. -The following module names are reserved by EdgeDB and contain pre-defined +The following module names are reserved by |Gel| and contain pre-defined types, utility functions, and operators. * ``std``: standard types, functions, and operators in the :ref:`standard diff --git a/docs/datamodel/indexes.rst b/docs/datamodel/indexes.rst index 96516026116..1bc7413ad82 100644 --- a/docs/datamodel/indexes.rst +++ b/docs/datamodel/indexes.rst @@ -118,7 +118,7 @@ speed up queries that filter, order, or group on both properties. `_ to learn more about how the query planner uses these indexes. -In EdgeDB, this index is created by indexing on a ``tuple`` of properties. +In Gel, this index is created by indexing on a ``tuple`` of properties. .. code-block:: sdl :version-lt: 3.0 @@ -178,7 +178,7 @@ Specify a Postgres index type .. versionadded:: 3.0 -EdgeDB exposes Postgres indexes that you can use in your schemas. These are +Gel exposes Postgres indexes that you can use in your schemas. These are exposed through the ``pg`` module. * ``pg::hash``- Index based on a 32-bit hash derived from the indexed value @@ -237,8 +237,8 @@ Indexes can be augmented with annotations. **Foreign and primary keys** In SQL databases, indexes are commonly used to index *primary keys* and - *foreign keys*. EdgeDB's analog to SQL's primary key is the ``id`` field - that gets automatically created for each object, while a link in EdgeDB + *foreign keys*. Gel's analog to SQL's primary key is the ``id`` field + that gets automatically created for each object, while a link in Gel is the analog to SQL's foreign key. Both of these are automatically indexed. Moreover, any property with an :eql:constraint:`exclusive` constraint is also automatically indexed. diff --git a/docs/datamodel/inheritance.rst b/docs/datamodel/inheritance.rst index d30b1d48c4f..8fe23c8ff41 100644 --- a/docs/datamodel/inheritance.rst +++ b/docs/datamodel/inheritance.rst @@ -4,10 +4,10 @@ Inheritance =========== -.. index:: abstract, extending, extends, subtype, supertype, parent type, +.. index:: abstract, extending, extends, subtype, supertype, parent type, child type -Inheritance is a crucial aspect of schema modeling in EdgeDB. Schema items can +Inheritance is a crucial aspect of schema modeling in Gel. Schema items can *extend* one or more parent types. When extending, the child (subclass) inherits the definition of its parents (superclass). diff --git a/docs/datamodel/introspection/casts.rst b/docs/datamodel/introspection/casts.rst index 6bd45089114..acdb12529fd 100644 --- a/docs/datamodel/introspection/casts.rst +++ b/docs/datamodel/introspection/casts.rst @@ -4,7 +4,7 @@ Casts ===== -This section describes introspection of EdgeDB :eql:op:`type casts +This section describes introspection of Gel :eql:op:`type casts `. Features like whether the casts are implicit can be discovered by introspecting ``schema::Cast``. @@ -112,7 +112,7 @@ of those types: {1.0, 2.0} What happens if there's no implicit cast between a couple of scalars -in this type of example? EdgeDB checks whether there's a scalar type +in this type of example? Gel checks whether there's a scalar type such that all of the set elements can be implicitly cast into that: .. code-block:: edgeql-repl diff --git a/docs/datamodel/introspection/index.rst b/docs/datamodel/introspection/index.rst index bec1d7405e8..cd4bc14478c 100644 --- a/docs/datamodel/introspection/index.rst +++ b/docs/datamodel/introspection/index.rst @@ -5,7 +5,7 @@ Introspection .. index:: describe, introspect, typeof, schema module -All of the schema information in EdgeDB is stored in the ``schema`` +All of the schema information in Gel is stored in the ``schema`` :ref:`module ` and is accessible via *introspection queries*. @@ -26,7 +26,7 @@ to the ``schema::ObjectType``. For scalars there's the to get the type of an expression. Finally, the command :eql:stmt:`describe` can be used to get -information about EdgeDB types in a variety of human-readable formats. +information about Gel types in a variety of human-readable formats. .. toctree:: :maxdepth: 3 diff --git a/docs/datamodel/introspection/operators.rst b/docs/datamodel/introspection/operators.rst index 063c8df9d6e..4abca8080b1 100644 --- a/docs/datamodel/introspection/operators.rst +++ b/docs/datamodel/introspection/operators.rst @@ -4,7 +4,7 @@ Operators ========= -This section describes introspection of EdgeDB operators. Much like +This section describes introspection of Gel operators. Much like functions, operators have parameters and return types as well as a few other features. diff --git a/docs/datamodel/links.rst b/docs/datamodel/links.rst index 48f52b1f68f..292575d63f9 100644 --- a/docs/datamodel/links.rst +++ b/docs/datamodel/links.rst @@ -575,7 +575,7 @@ Link properties .. index:: linkprops, metadata, link table -Like object types, links in EdgeDB can contain **properties**. Link properties +Like object types, links in Gel can contain **properties**. Link properties can be used to store metadata about links, such as *when* they were created or the *nature/strength* of the relationship. diff --git a/docs/datamodel/objects.rst b/docs/datamodel/objects.rst index d84d17b03db..a55864a9c14 100644 --- a/docs/datamodel/objects.rst +++ b/docs/datamodel/objects.rst @@ -6,7 +6,7 @@ Object Types .. index:: type, tables, models -*Object types* are the primary components of an EdgeDB schema. They are +*Object types* are the primary components of an Gel schema. They are analogous to SQL *tables* or ORM *models*, and consist of :ref:`properties ` and :ref:`links `. diff --git a/docs/datamodel/primitives.rst b/docs/datamodel/primitives.rst index 1f6d39c9ece..10fbe8c0cae 100644 --- a/docs/datamodel/primitives.rst +++ b/docs/datamodel/primitives.rst @@ -5,7 +5,7 @@ Primitives ========== EdgeDB has a robust type system consisting of primitive and object types. -Below is a review of EdgeDB's primitive types; later, these will be used to +Below is a review of Gel's primitive types; later, these will be used to declare *properties* on object types. diff --git a/docs/datamodel/properties.rst b/docs/datamodel/properties.rst index c3935542b1b..1f1470c7ed5 100644 --- a/docs/datamodel/properties.rst +++ b/docs/datamodel/properties.rst @@ -171,7 +171,7 @@ Constraints .. index:: constraint Properties can be augmented wth constraints. The example below showcases a -subset of EdgeDB's built-in constraints. +subset of Gel's built-in constraints. .. code-block:: sdl :version-lt: 3.0 diff --git a/docs/datamodel/triggers.rst b/docs/datamodel/triggers.rst index c2186643e83..ea5b7b8a345 100644 --- a/docs/datamodel/triggers.rst +++ b/docs/datamodel/triggers.rst @@ -153,9 +153,9 @@ Now, whenever we run a query, we get a log entry as well: .. note:: In some cases, a trigger can cause another trigger to fire. When this - happens, EdgeDB completes all the triggers fired by the initial query + happens, Gel completes all the triggers fired by the initial query before kicking off a new "stage" of triggers. In the second stage, any - triggers fired by the initial stage of triggers will fire. EdgeDB will + triggers fired by the initial stage of triggers will fire. Gel will continue adding trigger stages until all triggers are complete. The exception to this is when triggers would cause a loop or would cause @@ -347,7 +347,7 @@ both a friend and an enemy of any other person. ... select detached Person filter .name = 'Dracula' ... ) ... }; - edgedb error: EdgeDBError: Invalid frenemies + edgedb error: GelError: Invalid frenemies .. list-table:: diff --git a/docs/edgeql/for.rst b/docs/edgeql/for.rst index 62167e6bbbf..bb85ff52be1 100644 --- a/docs/edgeql/for.rst +++ b/docs/edgeql/for.rst @@ -24,7 +24,7 @@ are merged into a single output set. .. note:: - The ``union`` keyword is required prior to EdgeDB 5.0 and is intended to + The ``union`` keyword is required prior to |EdgeDB| 5.0 and is intended to indicate explicitly that the results of each loop execution are ultimately merged. @@ -95,11 +95,7 @@ Conditional DML .. versionadded:: 4.0 - DML is now supported in ``if..else``. The method of achieving conditional - DML demonstrated below is a workaround for earlier versions of EdgeDB - before this support was introduced in EdgeDB 4.0. If you're on EdgeDB 4.0 - or higher, use :eql:op:`if..else` for a cleaner way to achieve conditional - DML. + DML is now supported in ``if..else``. DML (i.e., :ref:`insert `, :ref:`update `, :ref:`delete `) is not supported in :eql:op:`if..else`. If you diff --git a/docs/edgeql/index.rst b/docs/edgeql/index.rst index 2b07a9d0379..50051df00ff 100644 --- a/docs/edgeql/index.rst +++ b/docs/edgeql/index.rst @@ -42,12 +42,12 @@ mind. spent attempting to `bridge the gap `_ between the *relational* paradigm of SQL and the *object-oriented* nature of modern programming -languages. EdgeDB sidesteps this problem by modeling data in an +languages. Gel sidesteps this problem by modeling data in an *object-relational* way. -**Strongly typed**. EdgeQL is *inextricably tied* to EdgeDB's rigorous +**Strongly typed**. EdgeQL is *inextricably tied* to Gel's rigorous object-oriented type system. The type of all expressions is statically -inferred by EdgeDB. +inferred by Gel. **Designed for programmers**. EdgeQL prioritizes syntax over keywords; It uses ``{ curly braces }`` to define scopes/structures and the *assignment @@ -62,7 +62,7 @@ like code and less like word soup. .. single PostgreSQL query under the hood. With the exception of ``group by``, .. EdgeQL is equivalent to SQL in terms of power and expressivity. -**Easy deep querying**. EdgeDB's object-relational nature makes it painless +**Easy deep querying**. Gel's object-relational nature makes it painless to write deep, performant queries that traverse links, no ``JOINs`` required. **Composable**. `Unlike SQL @@ -74,12 +74,12 @@ worrying about Cartesian explosion. For a detailed writeup on the design of SQL, see `We Can Do Better Than SQL `_ - on the EdgeDB blog. + on the Gel blog. Follow along ------------ The best way to learn EdgeQL is to play with it! Use the `online EdgeQL shell `_ to execute any and all EdgeQL snippets in the following pages. Or -follow the :ref:`Quickstart ` to spin up an EdgeDB instance on +follow the :ref:`Quickstart ` to spin up an Gel instance on your computer, then open an :ref:`interactive shell `. diff --git a/docs/edgeql/literals.rst b/docs/edgeql/literals.rst index 9163d5f9ced..cefdce6fb92 100644 --- a/docs/edgeql/literals.rst +++ b/docs/edgeql/literals.rst @@ -5,7 +5,7 @@ Literals .. index:: primitive types -EdgeQL is *inextricably tied* to EdgeDB's rigorous type system. Below is an +EdgeQL is *inextricably tied* to Gel's rigorous type system. Below is an overview of how to declare a literal value of each *primitive type*. Click a link in the left column to jump to the associated section. @@ -180,7 +180,7 @@ EdgeDB provides a set of operators that operate on boolean values. Numbers ------- -There are several numerical types in EdgeDB's type system. +There are several numerical types in Gel's type system. .. list-table:: diff --git a/docs/edgeql/parameters.rst b/docs/edgeql/parameters.rst index 9798356e5f9..ddf92cccd46 100644 --- a/docs/edgeql/parameters.rst +++ b/docs/edgeql/parameters.rst @@ -17,7 +17,7 @@ parameters are supplied externally. select BlogPost filter .id = $blog_id; Note that we provided an explicit type cast before the parameter. This is -required, as it enables EdgeDB to enforce the provided types at runtime. +required, as it enables Gel to enforce the provided types at runtime. .. versionadded:: 3.0 @@ -36,14 +36,14 @@ Usage with clients REPL ^^^^ -When you include a parameter reference in an EdgeDB REPL, you'll be prompted +When you include a parameter reference in an Gel REPL, you'll be prompted interactively to provide a value or values. .. code-block:: edgeql-repl db> select 'I ❤️ ' ++ $var ++ '!'; - Parameter $var: EdgeDB - {'I ❤️ EdgeDB!'} + Parameter $var: Gel + {'I ❤️ Gel!'} Python @@ -89,7 +89,7 @@ Go Refer to the Datatypes page of your preferred :ref:`client library -` to learn more about mapping between EdgeDB types and +` to learn more about mapping between Gel types and language-native types. @@ -100,10 +100,8 @@ Parameter types and JSON .. index:: complex parameters -Prior to EdgeDB 3.0, parameters can be only :ref:`scalars -` or arrays of scalars. In EdgeDB 3.0, parameters -can also be tuples. If you need to pass complex structures as parameters, use -EdgeDB's built-in :ref:`JSON ` functionality. +In Gel, parameters can also be tuples. If you need to pass complex structures +as parameters, use Gel's built-in :ref:`JSON ` functionality. .. code-block:: edgeql-repl @@ -165,8 +163,8 @@ in case the parameter is not passed. You can do this by using the .. code-block:: edgeql-repl db> select 'Hello ' ++ $name ?? 'there'; - Parameter $name (Ctrl+D for empty set `{}`): EdgeDB - {'Hello EdgeDB'} + Parameter $name (Ctrl+D for empty set `{}`): Gel + {'Hello Gel'} db> select 'Hello ' ++ $name ?? 'there'; Parameter $name (Ctrl+D for empty set `{}`): {'Hello there'} @@ -179,7 +177,7 @@ What can be parameterized? Any data manipulation language (DML) statement can be parameterized: ``select``, ``insert``, ``update``, and ``delete``. Since -parameters can only be scalars, arrays of scalars, and, as of EdgeDB 3.0, +parameters can only be scalars, arrays of scalars, and tuples of scalars, only parts of the query that would be one of those types can be parameterized. This excludes parts of the query like the type being queried and the property to order by. diff --git a/docs/edgeql/path_resolution.rst b/docs/edgeql/path_resolution.rst index 8917d1f6a20..3ed1eefe99d 100644 --- a/docs/edgeql/path_resolution.rst +++ b/docs/edgeql/path_resolution.rst @@ -6,13 +6,13 @@ Path scoping .. index:: using future simple_scoping, using future warn_old_scoping -Beginning with EdgeDB 6.0, we are phasing out our historical (and +Beginning with Gel 6.0, we are phasing out our historical (and somewhat notorious) :ref:`"path scoping" algorithm ` in favor of a much simpler algorithm that nevertheless behaves identically on *most* idiomatic EdgeQL queries. -EdgeDB 6.0 will contain features to support migration to and testing +Gel 6.0 will contain features to support migration to and testing of the new semantics. We expect the migration to be relatively painless for most users. @@ -86,7 +86,7 @@ Path scoping configuration .. versionadded:: 6.0 -EdgeDB 6.0 introduces a new +Gel 6.0 introduces a new :ref:`future feature ` named ``simple_scoping`` alongside a configuration setting also named ``simple_scoping``. The future @@ -186,10 +186,10 @@ Legacy path scoping =================== This section describes the path scoping algorithm used exclusively -until EdgeDB 5.0 and by default in EdgeDB 6.0. -It will be removed in EdgeDB 7.0. +until |EdgeDB| 5.0 and by default in |Gel| 6.0. +It will be removed in Gel 7.0. -Element-wise operations with multiple arguments in EdgeDB are generally applied +Element-wise operations with multiple arguments in Gel are generally applied to the :ref:`cartesian product ` of all the input sets. @@ -199,7 +199,7 @@ the input sets. {'aaaccc', 'aaaddd', 'bbbccc', 'bbbddd'} However, in cases where multiple element-wise arguments share a common path -(``User.`` in this example), EdgeDB factors out the common path rather than +(``User.`` in this example), Gel factors out the common path rather than using cartesian multiplication. .. code-block:: edgeql-repl @@ -213,7 +213,7 @@ product, you can accomplish it one of three ways. You could use .. code-block:: edgeql-repl - edgedb> select User.first_name ++ ' ' ++ detached User.last_name; + gel> select User.first_name ++ ' ' ++ detached User.last_name; { 'Mina Murray', 'Mina Harker', @@ -238,8 +238,8 @@ your set of ``User`` objects. .. code-block:: edgeql-repl - edgedb> with U := User - ....... select U.first_name ++ ' ' ++ User.last_name; + gel> with U := User + .... select U.first_name ++ ' ' ++ User.last_name; { 'Mina Murray', 'Mina Harker', @@ -271,7 +271,7 @@ cartesian multiplication. That may leave you still wondering why ``U`` and ``User`` did not get a common path factored. ``U`` is just an alias of ``select User`` and ``User`` is the -same symbol that we use in our name query. That's true, but EdgeDB doesn't +same symbol that we use in our name query. That's true, but |Gel| doesn't factor in this case because of the queries' scopes. .. _ref_eql_path_resolution_scopes: @@ -285,7 +285,7 @@ use a common symbol. .. code-block:: edgeql-repl - edgedb> select ((select User.first_name), (select User.last_name)); + gel> select ((select User.first_name), (select User.last_name)); { ('Mina', 'Murray'), ('Mina', 'Harker'), @@ -312,9 +312,9 @@ object because it has been factored. .. code-block:: edgeql-repl - edgedb> select User { - ....... name:= (select User.first_name) ++ ' ' ++ (select User.last_name) - ....... }; + gel> select User { + .... name:= (select User.first_name) ++ ' ' ++ (select User.last_name) + .... }; { default::User {name: 'Mina Murray'}, default::User {name: 'Jonathan Harker'}, @@ -327,7 +327,7 @@ paths are still factored. .. code-block:: edgeql-repl - edgedb> select (Person.name, count(Person.friends)); + gel> select (Person.name, count(Person.friends)); {('Fran', 3), ('Bam', 2), ('Emma', 3), ('Geoff', 1), ('Tyra', 1)} In this example, ``count``, like all aggregate function, creates a nested @@ -343,10 +343,10 @@ are *not* factored. .. code-block:: edgeql-repl - edgedb> select (array_agg(distinct Person.name), count(Person.friends)); + gel> select (array_agg(distinct Person.name), count(Person.friends)); {(['Fran', 'Bam', 'Emma', 'Geoff'], 3)} -This query selects a tuple containing two nested scopes. Here, EdgeDB assumes +This query selects a tuple containing two nested scopes. Here, |Gel| assumes you want an array of all unique names and a count of the total number of people who are anyone's friend. @@ -364,4 +364,4 @@ The :ref:`offset ` and because they need to be applied globally to the entire result set of your query. -.. _rfc: https://github.com/edgedb/rfcs/blob/master/text/1027-no-factoring.rst +.. _rfc: https://github.com/geldata/rfcs/blob/master/text/1027-no-factoring.rst diff --git a/docs/edgeql/paths.rst b/docs/edgeql/paths.rst index 2ee1b8de913..756e7928956 100644 --- a/docs/edgeql/paths.rst +++ b/docs/edgeql/paths.rst @@ -75,12 +75,12 @@ The first user reciprocates, adding the new user as a friend: ... friends += (select detached User filter .email = "user2@me.com") ... }; -The second user writes a blog post about how nice EdgeDB is: +The second user writes a blog post about how nice Gel is: .. code-block:: edgeql-repl db> insert BlogPost { - ... title := "EdgeDB is awesome", + ... title := "Gel is awesome", ... author := assert_single((select User filter .email = "user2@me.com")) ... }; @@ -150,7 +150,7 @@ database. However, we can't impose a shape on it: select User. with edgedb_lovers := ( - ... select BlogPost filter .title ilike "EdgeDB is awesome" + ... select BlogPost filter .title ilike "Gel is awesome" ... ) ... select edgedb_lovers.author; This expression returns a set of all ``Users`` who have written a blog post -titled "EdgeDB is awesome". +titled "Gel is awesome". For a full syntax definition, see the :ref:`Reference > Paths `. diff --git a/docs/edgeql/select.rst b/docs/edgeql/select.rst index 06a4132a4d7..d5b1a7db068 100644 --- a/docs/edgeql/select.rst +++ b/docs/edgeql/select.rst @@ -648,8 +648,8 @@ links to a second object without a link back to the first. Spider-Man's villains always have a grudging respect for him, and their names can be displayed to reflect that if we know the ID of a movie that they -starred in. (Note the ability to :ref:`cast from a uuid ` -to an object type, which was added in EdgeDB 3.0!) +starred in. Note the ability to :ref:`cast from a uuid ` +to an object type. .. code-block:: edgeql-repl @@ -706,7 +706,7 @@ expression, primitive or otherwise. .. note:: - In EdgeDB all values are orderable. Objects are compared using their ``id``; + In Gel all values are orderable. Objects are compared using their ``id``; tuples and arrays are compared element-by-element from left to right. By extension, the generic comparison operators :eql:op:`= `, :eql:op:`\< `, :eql:op:`\> `, etc. can be used with any two @@ -928,7 +928,7 @@ common to add them directly into your schema as computed links. .. note:: In the example above, the ``Person.movies`` is a ``multi`` link. Including - these keywords is optional, since EdgeDB can infer this from the assigned + these keywords is optional, since Gel can infer this from the assigned expression ``. select 1 union (2 union (3 union 4)); {1, 2, 3, 4} -All values in a set must have the same type. For convenience, EdgeDB will +All values in a set must have the same type. For convenience, Gel will *implicitly cast* values to other types, as long as there is no loss of information (e.g. converting a ``int16`` to an ``int64``). For a full reference, see the casting table in :ref:`Standard Library > Casts @@ -129,7 +129,7 @@ Empty sets The reason EdgeQL introduced the concept of *sets* is to eliminate the concept of ``null``. In SQL databases ``null`` is a special value denoting the absence -of data; in EdgeDB the absence of data is just an empty set. +of data; in Gel the absence of data is just an empty set. .. note:: @@ -138,7 +138,7 @@ of data; in EdgeDB the absence of data is just an empty set. circumstances. A number of specific inconsistencies are documented in detail in the `We Can Do Better Than SQL `_ - post on the EdgeDB blog. For broader context, see Tony Hoare's talk + post on the Gel blog. For broader context, see Tony Hoare's talk `"The Billion Dollar Mistake" `_. @@ -217,7 +217,7 @@ Multisets .. index:: multisets, distinct, duplicates -Technically sets in EdgeDB are actually *multisets*, because they can contain +Technically sets in Gel are actually *multisets*, because they can contain duplicates of the same element. To eliminate duplicates, use the :eql:op:`distinct` set operator. @@ -441,7 +441,7 @@ handling data structures. It's useful to consider functions/operators as either .. note:: This is an over-simplification, but it's a useful mental model when just - starting out with EdgeDB. For a more complete guide, see :ref:`Reference > + starting out with Gel. For a more complete guide, see :ref:`Reference > Cardinality `. *Aggregate* operations are applied to the set *as a whole*; they diff --git a/docs/edgeql/transactions.rst b/docs/edgeql/transactions.rst index 25c4bd197c0..624e7b85cce 100644 --- a/docs/edgeql/transactions.rst +++ b/docs/edgeql/transactions.rst @@ -38,13 +38,13 @@ of several commands: Client libraries ---------------- -There is rarely a reason to use these commands directly. All EdgeDB client +There is rarely a reason to use these commands directly. All Gel client libraries provide dedicated transaction APIs that handle transaction creation under the hood. Examples below show a transaction that sends 10 cents from the account of a ``BankCustomer`` called ``'Customer1'`` to ``BankCustomer`` called -``'Customer2'``. The equivalent EdgeDB schema and queries are: +``'Customer2'``. The equivalent Gel schema and queries are: .. code-block:: diff --git a/docs/edgeql/types.rst b/docs/edgeql/types.rst index c33be16e33b..20f8012a9aa 100644 --- a/docs/edgeql/types.rst +++ b/docs/edgeql/types.rst @@ -5,7 +5,7 @@ Types ===== -The foundation of EdgeQL is EdgeDB's rigorous type system. There is a set of +The foundation of EdgeQL is Gel's rigorous type system. There is a set of EdgeQL operators and functions for changing, introspecting, and filtering by types. @@ -199,7 +199,7 @@ operator. This can be used in any expression that expects a type. Introspection ------------- -The entire type system of EdgeDB is *stored inside EdgeDB*. All types are +The entire type system of Gel is *stored inside Gel*. All types are introspectable as instances of the ``schema::Type`` type. For a set of introspection examples, see :ref:`Guides > Introspection `. To try introspection for yourself, see `our diff --git a/docs/edgeql/with.rst b/docs/edgeql/with.rst index 451187957f8..e67d7867f56 100644 --- a/docs/edgeql/with.rst +++ b/docs/edgeql/with.rst @@ -122,7 +122,7 @@ module* on a per-query basis. ... select ObjectType; This ``with module`` clause changes the default module to schema, so we can -refer to ``schema::ObjectType`` (a built-in EdgeDB type) as simply +refer to ``schema::ObjectType`` (a built-in Gel type) as simply ``ObjectType``. As with module aliases, if the active module does not exist at the top level, diff --git a/docs/guides/auth/built_in_ui.rst b/docs/guides/auth/built_in_ui.rst index 7cf1596f915..999ff1d8a4c 100644 --- a/docs/guides/auth/built_in_ui.rst +++ b/docs/guides/auth/built_in_ui.rst @@ -6,14 +6,14 @@ Built-in UI :edb-alt-title: Integrating Gel Auth's built-in UI -To use the built-in UI for EdgeDB Auth, enable the built-in Auth UI by clicking +To use the built-in UI for Gel Auth, enable the built-in Auth UI by clicking the "Enable UI" button under "Login UI" in the configuration section of the EdgeDB UI. Set these configuration values: -- ``redirect_to``: Once the authentication flow is complete, EdgeDB will +- ``redirect_to``: Once the authentication flow is complete, Gel will redirect the user's browser back to this URL in your application's backend. -- ``redirect_to_on_signup``: If this is a new user, EdgeDB will redirect +- ``redirect_to_on_signup``: If this is a new user, Gel will redirect the user's browser back to this URL in your application's backend. - ``app_name``: Used in the built-in UI to show the user the application's name in a few important places. @@ -111,7 +111,7 @@ base64url encode the resulting string. This new string is called the .. note:: - For EdgeDB versions before 5.0, the value for ``EDGEDB_AUTH_BASE_URL`` + For |EdgeDB| versions before 5.0, the value for ``EDGEDB_AUTH_BASE_URL`` in the above snippet should have the form: ``${protocol}://${host}:${port}/db/${database}/ext/auth/`` @@ -174,7 +174,7 @@ to the built-in UI with the ``challenge`` in the search parameters. }); /** - * Redirects browser requests to EdgeDB Auth UI sign in page with the + * Redirects browser requests to Gel Auth UI sign in page with the * PKCE challenge, and saves PKCE verifier in an HttpOnly cookie. * * @param {Request} req @@ -194,7 +194,7 @@ to the built-in UI with the ``challenge`` in the search parameters. }; /** - * Redirects browser requests to EdgeDB Auth UI sign up page with the + * Redirects browser requests to Gel Auth UI sign up page with the * PKCE challenge, and saves PKCE verifier in an HttpOnly cookie. * * @param {Request} req @@ -224,11 +224,11 @@ to the built-in UI with the ``challenge`` in the search parameters. Retrieve ``auth_token`` ----------------------- -At the very end of the flow, the EdgeDB server will redirect the user's browser +At the very end of the flow, the Gel server will redirect the user's browser to the ``redirect_to`` address with a single query parameter: ``code``. This route should be a server route that has access to the ``verifier``. You then take that ``code`` and look up the ``verifier`` in the ``edgedb-pkce-verifier`` -cookie, and make a request to the EdgeDB Auth extension to exchange these two +cookie, and make a request to the Gel Auth extension to exchange these two pieces of data for an ``auth_token``. .. lint-off @@ -293,4 +293,4 @@ pieces of data for an ``auth_token``. .. lint-on -:ref:`Back to the EdgeDB Auth guide ` +:ref:`Back to the Gel Auth guide ` diff --git a/docs/guides/auth/email_password.rst b/docs/guides/auth/email_password.rst index 105372caadb..96a3fc423c3 100644 --- a/docs/guides/auth/email_password.rst +++ b/docs/guides/auth/email_password.rst @@ -119,7 +119,7 @@ base64url encode the resulting string. This new string is called the .. note:: - For EdgeDB versions before 5.0, the value for ``EDGEDB_AUTH_BASE_URL`` + For |EdgeDB| versions before 5.0, the value for ``EDGEDB_AUTH_BASE_URL`` in the above snippet should have the form: ``${protocol}://${host}:${port}/db/${database}/ext/auth/`` @@ -589,7 +589,7 @@ that updates the password and logs in the user. }; /** - * Send new password with reset token to EdgeDB Auth. + * Send new password with reset token to Gel Auth. * * @param {Request} req * @param {Response} res @@ -661,4 +661,4 @@ that updates the password and logs in the user. .. lint-on -:ref:`Back to the EdgeDB Auth guide ` +:ref:`Back to the Gel Auth guide ` diff --git a/docs/guides/auth/index.rst b/docs/guides/auth/index.rst index d7f3d50e268..0b8c1d59dfb 100644 --- a/docs/guides/auth/index.rst +++ b/docs/guides/auth/index.rst @@ -17,13 +17,13 @@ Auth :edb-alt-title: Using Gel Auth EdgeDB Auth is a batteries-included authentication solution for your app built -into the EdgeDB server. Here's how you can integrate it with your app. +into the Gel server. Here's how you can integrate it with your app. Enable extension in your schema =============================== -Auth is an EdgeDB extension. To enable it, you will need to add the extension +Auth is an Gel extension. To enable it, you will need to add the extension to your app's schema: .. code-block:: sdl @@ -40,7 +40,7 @@ extension enabled in your schema as shown above and have migrated that schema change, you will see the "Auth Admin" icon in the left-hand toolbar. .. image:: images/ui-auth.png - :alt: The EdgeDB local development server UI highlighting the auth admin + :alt: The Gel local development server UI highlighting the auth admin icon in the left-hand toolbar. The icon is two nested shield outlines, the inner being a light pink color and the outer being a light blue when selected. @@ -513,7 +513,7 @@ WebAuthn - ``relying_party_origin``: This is the URL of the web application handling the WebAuthn request. If you're using the built-in UI, it's the origin of - the EdgeDB web server. + the Gel web server. - ``require_verification``: (Default: ``true``) If ``true``, your application will not be able to retrieve an authentication token until the user has @@ -533,8 +533,8 @@ WebAuthn .. note:: You will need to configure CORS to allow the client-side script to call the - EdgeDB Auth extension's endpoints from the web browser. You can do this by - updating the ``cors_allow_origins`` configuration in the EdgeDB server + Gel Auth extension's endpoints from the web browser. You can do this by + updating the ``cors_allow_origins`` configuration in the Gel server configuration. Here is an example of setting a local SMTP server, in this case using a @@ -567,7 +567,7 @@ Integrating your application ============================ In the end, what we want to end up with is an authentication token -created by EdgeDB that we can set as a global in any authenticated +created by Gel that we can set as a global in any authenticated queries executed from our application, which will set a computed global linked to an ``ext::auth::Identity``. @@ -596,7 +596,7 @@ Example usage ============= Here's an example schema that we can use to show how you would use the -``auth_token`` you get back from EdgeDB to make queries against a +``auth_token`` you get back from Gel to make queries against a protected resource, in this case being able to insert a ``Post``. .. code-block:: sdl diff --git a/docs/guides/auth/magic_link.rst b/docs/guides/auth/magic_link.rst index 1af2fca9a42..8afe5751371 100644 --- a/docs/guides/auth/magic_link.rst +++ b/docs/guides/auth/magic_link.rst @@ -6,23 +6,23 @@ Magic Link Auth :edb-alt-title: Integrating Gel Auth's Magic Link provider -Magic Link is a passwordless authentication method that allows users to log in via a unique, time-sensitive link sent to their email. This guide will walk you through integrating Magic Link authentication with your application using EdgeDB Auth. +Magic Link is a passwordless authentication method that allows users to log in via a unique, time-sensitive link sent to their email. This guide will walk you through integrating Magic Link authentication with your application using Gel Auth. Enable Magic Link provider ========================== -Before you can use Magic Link authentication, you need to enable the Magic Link provider in your EdgeDB Auth configuration. This can be done through the EdgeDB UI under the "Providers" section. +Before you can use Magic Link authentication, you need to enable the Magic Link provider in your Gel Auth configuration. This can be done through the Gel UI under the "Providers" section. Magic Link flow =============== The Magic Link authentication flow involves three main steps: -1. **Sending a Magic Link Email**: Your application requests EdgeDB Auth to send a magic link to the user's email. +1. **Sending a Magic Link Email**: Your application requests Gel Auth to send a magic link to the user's email. 2. **User Clicks Magic Link**: The user receives the email and clicks on the magic link. -3. **Authentication and Token Retrieval**: The magic link directs the user to your application, which then authenticates the user and retrieves an authentication token from EdgeDB Auth. +3. **Authentication and Token Retrieval**: The magic link directs the user to your application, which then authenticates the user and retrieves an authentication token from Gel Auth. UI considerations ================= @@ -212,7 +212,7 @@ Sign up Sign in ------- -Signing in with a magic link simply involves telling the EdgeDB Auth server to +Signing in with a magic link simply involves telling the Gel Auth server to send a magic link to the user's email. The user will then click on the link to authenticate. @@ -403,4 +403,4 @@ object: .. lint-on -:ref:`Back to the EdgeDB Auth guide ` +:ref:`Back to the Gel Auth guide ` diff --git a/docs/guides/auth/oauth.rst b/docs/guides/auth/oauth.rst index 5b201c8719e..4ef014cd9bc 100644 --- a/docs/guides/auth/oauth.rst +++ b/docs/guides/auth/oauth.rst @@ -114,7 +114,7 @@ base64url encode the resulting string. This new string is called the .. note:: - For EdgeDB versions before 5.0, the value for ``EDGEDB_AUTH_BASE_URL`` + For |EdgeDB| versions before 5.0, the value for ``EDGEDB_AUTH_BASE_URL`` in the above snippet should have the form: ``${protocol}://${host}:${port}/db/${database}/ext/auth/`` @@ -154,7 +154,7 @@ the end user's browser to the Identity Provider with the proper setup. }); /** - * Redirects OAuth requests to EdgeDB Auth OAuth authorize redirect + * Redirects OAuth requests to Gel Auth OAuth authorize redirect * with the PKCE challenge, and saves PKCE verifier in an HttpOnly * cookie for later retrieval. * @@ -197,11 +197,11 @@ the end user's browser to the Identity Provider with the proper setup. Retrieve ``auth_token`` ----------------------- -At the very end of the flow, the EdgeDB server will redirect the user's browser +At the very end of the flow, the Gel server will redirect the user's browser to the ``redirect_to`` address with a single query parameter: ``code``. This route should be a server route that has access to the ``verifier``. You then take that ``code`` and look up the ``verifier`` in the ``edgedb-pkce-verifier`` -cookie, and make a request to the EdgeDB Auth extension to exchange these two +cookie, and make a request to the Gel Auth extension to exchange these two pieces of data for an ``auth_token``. .. lint-off @@ -417,4 +417,4 @@ it: "Set-Cookie": `edgedb-auth-token=${auth_token}; HttpOnly; Path=/; Secure; SameSite=Strict`, }); -:ref:`Back to the EdgeDB Auth guide ` +:ref:`Back to the Gel Auth guide ` diff --git a/docs/guides/auth/webauthn.rst b/docs/guides/auth/webauthn.rst index f020d91db6f..149af912d6f 100644 --- a/docs/guides/auth/webauthn.rst +++ b/docs/guides/auth/webauthn.rst @@ -10,7 +10,7 @@ WebAuthn, short for Web Authentication, is a web standard published by the World Wide Web Consortium (W3C) for secure and passwordless authentication on the web. It allows users to log in using biometrics, mobile devices, or FIDO2 security keys instead of traditional passwords. This guide will walk you -through integrating WebAuthn authentication with your application using EdgeDB +through integrating WebAuthn authentication with your application using Gel Auth. Why choose WebAuthn? @@ -33,14 +33,14 @@ and aim to simplify the user experience further by leveraging cloud synchronization of credentials. Many operating systems and password managers have added support for Passkeys, -making it easier for users to manage their credentials across devices. EdgeDB +making it easier for users to manage their credentials across devices. Gel Auth's WebAuthn provider supports Passkeys, allowing users to log in to your application using their Passkeys. Security considerations ======================= -For maximum flexibility, EdgeDB Auth's WebAuthn provider allows multiple +For maximum flexibility, Gel Auth's WebAuthn provider allows multiple WebAuthn credentials per email. This means that it's very important to verify the email before trusting a WebAuthn credential. This can be done by setting the ``require_verification`` option to ``true`` (which is the default) in your @@ -191,7 +191,7 @@ Handle register and authenticate options The first step in the WebAuthn flow is to get the options for registering a new credential or authenticating an existing credential. The server generates a JSON object that is used to configure the WebAuthn registration or -authentication ceremony. The EdgeDB Auth extension provides these endpoints +authentication ceremony. The Gel Auth extension provides these endpoints directly, so you can either proxy the request to the Auth extension or redirect the user to the Auth extension's URL. We'll show the proxy option here. @@ -274,7 +274,7 @@ Register a new credential The client script will call the Web Authentication API to create a new credential payload and send it to this endpoint. This endpoints job will be to -forward the serialized credential payload to the EdgeDB Auth extension for +forward the serialized credential payload to the Gel Auth extension for verification, and then associate the credential with the user's email address. .. lint-off @@ -358,7 +358,7 @@ Authenticate with an existing credential The client script will call the Web Authentication API to authenticate with an existing credential and send the assertion to this endpoint. This endpoint's -job will be to forward the serialized assertion to the EdgeDB Auth extension +job will be to forward the serialized assertion to the Gel Auth extension for verification. .. lint-off @@ -529,7 +529,7 @@ Client-side script ------------------ On the client-side, you will need to write a script that retrieves the options -from the EdgeDB Auth extension, calls the Web Authentication API, and sends the +from the Gel Auth extension, calls the Web Authentication API, and sends the resulting credential or assertion to the server. Writing out the low-level handling of serialization and deserialization of the WebAuthn data is beyond the scope of this guide, but we publish a WebAuthn client library that you can use diff --git a/docs/guides/contributing/code.rst b/docs/guides/contributing/code.rst index 3ed656dfeae..12001a91cab 100644 --- a/docs/guides/contributing/code.rst +++ b/docs/guides/contributing/code.rst @@ -6,7 +6,7 @@ Code :edb-alt-title: Developing Gel -This section describes how to build EdgeDB locally, how to use its +This section describes how to build Gel locally, how to use its internal tools, and how to contribute to it. .. warning:: @@ -146,7 +146,7 @@ Python "venv" with all dependencies and commands installed into it. $ cd edgedb $ pip install -v -e ".[test]" - In addition to compiling EdgeDB and all dependencies, this will also + In addition to compiling Gel and all dependencies, this will also install the ``edb`` and ``edgedb`` command line tools into the current Python virtual environment. @@ -165,7 +165,7 @@ activated at any time. Running Tests ============= -To run all EdgeDB tests simply use the ``$ edb test`` command without +To run all Gel tests simply use the ``$ edb test`` command without arguments. The command also supports running a few selected tests. To run all diff --git a/docs/guides/contributing/documentation.rst b/docs/guides/contributing/documentation.rst index 8230cd582b5..ed4b8e9c8b0 100644 --- a/docs/guides/contributing/documentation.rst +++ b/docs/guides/contributing/documentation.rst @@ -8,7 +8,7 @@ Documentation We pride ourselves on having some of the best documentation around, but we want you to help us make it even better. Documentation is a great way to get started -contributing to EdgeDB. Improvements to our documentation create a better +contributing to Gel. Improvements to our documentation create a better experience for every developer coming through the door behind you. Follow our general and style guidelines to make for a smooth contributing @@ -54,8 +54,8 @@ Style that in favor of ``*param*``, in order to distinguish between parameter references and inline code (which *should* be surrounded by double backticks). -- **EdgeDB is singular.** Choose "EdgeDB is" over "EdgeDB are" and "EdgeDB - does" over "EdgeDB do." +- **Gel is singular.** Choose "Gel is" over "Gel are" and "Gel + does" over "Gel do." - **Use American English spellings.** Choose "color" over "colour" and "organize" over "organise." - **Use the Oxford comma.** When delineating a series, place a comma between @@ -66,45 +66,38 @@ Style like a computer science textbook. Sometimes that's necessary, but in most cases, it isn't. Prioritize accuracy first and accessibility a close second. - **Be careful using words that have a special meaning in the context of - EdgeDB.** In casual speech or writing, you might talk about a "set" of - something in a generic sense. Using the word this way in EdgeDB documentation - might easily be interpreted as a reference to EdgeDB's :ref:`sets + Gel.** In casual speech or writing, you might talk about a "set" of + something in a generic sense. Using the word this way in Gel documentation + might easily be interpreted as a reference to Gel's :ref:`sets `. Avoid this kind of casual usage of key terms. Where to Find It ================ -Most of our documentation (including this guide) lives in `the edgedb -repository `_ in `the docs directory -`_. +Most of our documentation (including this guide) lives in `the geldata +repository `_ in `the docs directory +`_. Documentation for some of our client libraries lives inside the client's repo. -If you don't find it in the edgedb repo at ``docs/clients``, you'll probably +If you don't find it in the geldata repo at ``docs/clients``, you'll probably find it alongside the client itself. These clients will also have documentation -stubs inside the edgedb repository directing you to the documentation's +stubs inside the geldata repository directing you to the documentation's location. -The `EdgeDB tutorial `_ is part of `our web -site repository `_. You'll find it in `the -tutorial directory `_. - -Finally, our book for beginners titled `Easy EdgeDB `_ lives in -`its own repo `_. - How to Build It =============== -edgedb/edgedb -------------- +geldata/gel +----------- -The ``edgedb`` repository contains all of its documentation in the ``docs/`` -directory. Run ``make docs`` to build the documentation in the edgedb repo. The +The ``geldata`` repository contains all of its documentation in the ``docs/`` +directory. Run ``make docs`` to build the documentation in the geldata repo. The repository contains a ``Makefile`` for all of Sphinx's necessary build options. The documentation will be built to ``docs/build``. -To run tests, first :ref:`build EdgeDB locally +To run tests, first :ref:`build Gel locally `. Then run ``edb test -k doc``. Building the docs from this repo will not give you a high-fidelity @@ -119,9 +112,9 @@ your documentation exactly as the user will see it. This is not required, but it can be useful to help us review and approve your request more quickly by avoiding mistakes that would be easier to spot when they are fully rendered. -To build, clone `our website repository `_ +To build, clone `our website repository `_ and `follow the installation instructions -`_. Then run ``yarn dev`` to +`_. Then run ``yarn dev`` to start a development server which also triggers a build of all the documentation. @@ -494,13 +487,13 @@ Below are the most common languages used in our docs: .. code-block:: bash - $ edgedb configure set listen_addresses 127.0.0.1 ::1 + $ gel configure set listen_addresses 127.0.0.1 ::1 **Rendered** .. code-block:: bash - $ edgedb configure set listen_addresses 127.0.0.1 ::1 + $ gel configure set listen_addresses 127.0.0.1 ::1 * ``edgeql``- Used for queries. @@ -895,13 +888,13 @@ Document a CLI command using the ``cli:synopsis`` directive like this: .. cli:synopsis:: - edgedb dump [] + gel dump [] **Rendered** .. cli:synopsis:: - edgedb dump [] + gel dump [] The synopsis should follow the format used in the PostgreSQL documentation. See `the PostgreSQL SELECT statement reference page @@ -925,7 +918,7 @@ You can then document arguments and options using the ``:cli:synopsis:`` role. Documentation Versioning ======================== -Since EdgeDB functionality is mostly consistent across versions, we offer a +Since |Gel| functionality is mostly consistent across versions, we offer a simple method of versioning documentation using two directives. .. warning:: @@ -957,15 +950,15 @@ The directive behaves differently depending on the context. .. code-block:: - .. versionadded:: 2.0 + .. versionadded:: 7.0 - This is a new feature that was added in EdgeDB 2.0. + This is a new feature that was added in Gel 7.0. **Rendered** -.. versionadded:: 2.0 +.. versionadded:: 7.0 - This is a new feature that was added in EdgeDB 2.0. + This is a new feature that was added in Gel 7.0. .. note:: @@ -979,7 +972,7 @@ The directive behaves differently depending on the context. Source deletion ^^^^^^^^^^^^^^^ - .. versionadded:: 2.0 + .. versionadded:: 7.0 Source deletion policies determine what action should be taken when the *source* of a given link is deleted. They are declared with the ``on source @@ -1029,7 +1022,7 @@ Changed in Version ------------------ Use the ``versionchanged`` directive to mark content related to a change in -existing functionality across EdgeDB versions. Provide the applicable version +existing functionality across |Gel| versions. Provide the applicable version as an argument by placing it just after the directive on the same line. Unlike ``versionadded``, ``versionchanged`` is always used with content to show @@ -1041,9 +1034,9 @@ or hide that content based on the user's selection in the version dropdown. .. code-block:: - .. versionchanged:: 3.0 + .. versionchanged:: 8.0 - Starting with the upcoming EdgeDB 3.0, access policy restrictions will + Starting with the upcoming Gel 8.0, access policy restrictions will **not** apply to any access policy expression. This means that when reasoning about access policies it is no longer necessary to take other policies into account. Instead, all data is visible for the purpose of @@ -1053,9 +1046,9 @@ or hide that content based on the user's selection in the version dropdown. **Rendered** -.. versionchanged:: 3.0 +.. versionchanged:: 8.0 - Starting with the upcoming EdgeDB 3.0, access policy restrictions will + Starting with the upcoming Gel 8.0, access policy restrictions will **not** apply to any access policy expression. This means that when reasoning about access policies it is no longer necessary to take other policies into account. Instead, all data is visible for the purpose of @@ -1111,7 +1104,7 @@ before the offending block and back on with ``.. lint-on`` after the block. Embedding a YouTube Video ------------------------- -Embed only videos from `the EdgeDB YouTube channel +Embed only videos from `the Gel YouTube channel `_ .. code-block:: @@ -1139,8 +1132,8 @@ placing it after the directive on the same line. .. lint-off See `the list of illustration names -`_ +`_ and `view the images they map to -`_. +`_. .. lint-on diff --git a/docs/guides/contributing/index.rst b/docs/guides/contributing/index.rst index a75faee6867..c4c715e78ae 100644 --- a/docs/guides/contributing/index.rst +++ b/docs/guides/contributing/index.rst @@ -43,7 +43,7 @@ General Guidelines Thank You! ========== -Thank you for contributing to EdgeDB! We love our open source community and +Thank you for contributing to Gel! We love our open source community and want to foster a healthy contributor ecosystem. We're happy to have you as a part of it. diff --git a/docs/guides/datamigrations/index.rst b/docs/guides/datamigrations/index.rst index 5771b26b1fc..c82cfc702e2 100644 --- a/docs/guides/datamigrations/index.rst +++ b/docs/guides/datamigrations/index.rst @@ -1,10 +1,10 @@ .. _ref_guide_data_migrations: -=================== -Switching to EdgeDB -=================== +================ +Switching to Gel +================ -Perhaps you like EdgeDB so much you want to migrate an existing project to it. +Perhaps you like Gel so much you want to migrate an existing project to it. Here we'll show you some possible ways to import your data. .. toctree:: diff --git a/docs/guides/datamigrations/postgres.rst b/docs/guides/datamigrations/postgres.rst index 3d36feb026c..648c8c810ed 100644 --- a/docs/guides/datamigrations/postgres.rst +++ b/docs/guides/datamigrations/postgres.rst @@ -4,7 +4,7 @@ Postgres ======== -In this guide, we show how to move your data from Postgres to EdgeDB. However, +In this guide, we show how to move your data from Postgres to Gel. However, most of the approaches covered here should be applicable to other SQL databases as well. @@ -12,8 +12,8 @@ As an example we'll use an app that allowed users to chat. The main features of this app revolve around posting and responding to messages. Once the data is moved, it is then possible to use :ref:`EdgeQL ` instead of SQL to fetch the desired data in the app's API calls. Here we'll mainly focus on -the data structures used for that, how to reflect them into EdgeDB, and how to -script moving the data across to the EdgeDB database. +the data structures used for that, how to reflect them into Gel, and how to +script moving the data across to the Gel database. Schema modeling @@ -77,7 +77,7 @@ are such tables, so let's look at them: .. lint-on The ``badges`` table uses the ``name`` of the badge as a primary key as -opposed to a separate ``id``. In order to reflect that properly in EdgeDB, we +opposed to a separate ``id``. In order to reflect that properly in Gel, we would have to add an :eql:constraint:`exclusive` constraint to this property. Meanwhile ``not null`` makes the property ``required``, leaving us with a type like this: @@ -92,7 +92,7 @@ like this: } The ``statuses`` table has a dedicated ``id`` column in addition to ``title``. -However, the automatic ``id`` in EdgeDB is a :eql:type:`uuid`, whereas in our +However, the automatic ``id`` in Gel is a :eql:type:`uuid`, whereas in our original dataset it is an ``integer``. Let's assume that for this table we never actually use the ``id`` in our code, relying instead on the fact that ``title`` is ``UNIQUE`` and serves as a much more descriptive identifier. We @@ -140,8 +140,8 @@ Next, we can look at the ``users`` table: The ``users`` table, like ``statuses``, has an ``id`` column, which is not a :eql:type:`uuid`. Instead of omitting the ``id`` data, we'll record it as -``app_id`` in EdgeDB to facilitate the transition. We may still want to -eventually drop it in favor of the built-in ``id`` from EdgeDB, but we need it +``app_id`` in Gel to facilitate the transition. We may still want to +eventually drop it in favor of the built-in ``id`` from Gel, but we need it for now. Incidentally, even if the ``id`` was specified as a ``uuid`` value the recommended process is to record it as ``app_id`` as opposed to try and replicate it as the main object ``id``. It is, however, also possible to bring @@ -149,7 +149,7 @@ it over as the main ``id`` by adjusting certain :ref:`client connection settings `. The column ``client_settings`` would become a :eql:type:`json` property. The columns ``badge_name`` and ``status_id`` reference ``badges`` and ``statuses`` respectively and will -become *links* in EdgeDB instead of *properties*, even though a property would +become *links* in Gel instead of *properties*, even though a property would more closely mirror how they are stored in Postgres: .. code-block:: sql @@ -361,11 +361,11 @@ Copying the data ---------------- Now that we have a schema, we can use :ref:`ref_cli_edgedb_project_init` to -set up our new EdgeDB database. A new schema migration is added via +set up our new Gel database. A new schema migration is added via :ref:`ref_cli_edgedb_migration_create` and then :ref:`edgedb migrate ` applies the schema changes to the database. After the schema migration, we'll still need to copy the existing data from -Postgres. JSON is a pretty good intermediate format for this operation. EdgeDB +Postgres. JSON is a pretty good intermediate format for this operation. Gel can cast data from :eql:type:`json` to all of the built-in scalar types, so we should be able to use a JSON dump with minimal additional processing when importing all the data. @@ -396,7 +396,7 @@ We will dump ``badges`` and ``statuses`` first: These tables can be dumped directly to a file using a ``COPY ... TO `` command. We can then read the files and use a simple loop to -import the data into EdgeDB. +import the data into Gel. .. note:: @@ -430,7 +430,7 @@ import the data into EdgeDB. The ``threads`` table can likewise be dumped directly as JSON with the only minor difference being that we want to change the ``id`` to ``app_id`` when we -move the data to EdgeDB: +move the data to Gel: .. code-block:: python @@ -486,7 +486,7 @@ The ``posts`` table can be dumped as JSON directly, but we'll need to write sub-queries in the import script to correctly link ``Post`` objects. In order to make this simpler, we can order the original data by ``creation_time`` so we know any ``Post`` object that is referenced by the ``reply_to_id`` has -already been re-created in EdgeDB. +already been re-created in Gel. .. code-block:: python @@ -514,7 +514,7 @@ already been re-created in EdgeDB. Finally, we can deal with the bookmarks since we've imported both the users and the posts. The ``bookmarks`` table can be dumped as JSON directly, and -then we can write appropriate ``update`` query to add this data to EdgeDB: +then we can write appropriate ``update`` query to add this data to Gel: .. code-block:: python @@ -569,7 +569,7 @@ this: @note: 'rendering glitch', }, default::Post { - body: 'Funny you ask, Alice. I actually work at EdgeDB!', + body: 'Funny you ask, Alice. I actually work at Gel!', user: default::User {name: 'Dana'}, @note: 'follow-up', }, @@ -582,4 +582,4 @@ this: }, } -.. lint-on \ No newline at end of file +.. lint-on diff --git a/docs/guides/deployment/aws_aurora_ecs.rst b/docs/guides/deployment/aws_aurora_ecs.rst index d30cd29d441..0c675244858 100644 --- a/docs/guides/deployment/aws_aurora_ecs.rst +++ b/docs/guides/deployment/aws_aurora_ecs.rst @@ -6,7 +6,7 @@ AWS :edb-alt-title: Deploying Gel to AWS -In this guide we show how to deploy EdgeDB on AWS using Amazon Aurora and +In this guide we show how to deploy Gel on AWS using Amazon Aurora and Elastic Container Service. Prerequisites @@ -24,7 +24,7 @@ Quick Install with CloudFormation ================================= We maintain a `CloudFormation template `_ for easy automated -deployment of EdgeDB in your AWS account. The template deploys EdgeDB +deployment of Gel in your AWS account. The template deploys Gel to a new ECS service and connects it to a newly provisioned Aurora PostgreSQL cluster. The created instance has a public IP address with TLS configured and is protected by a password you provide. @@ -41,11 +41,11 @@ following parameters: `_. - ``InstanceName``: ⚠️ Due to limitations with AWS, this must be 22 characters or less! -- ``SuperUserPassword``: this will be used as the password for the new EdgeDB +- ``SuperUserPassword``: this will be used as the password for the new Gel instance. Keep track of the value you provide. Once the deployment is complete, follow these steps to find the host name that -has been assigned to your EdgeDB instance: +has been assigned to your Gel instance: .. lint-off @@ -54,7 +54,7 @@ has been assigned to your EdgeDB instance: 2. Wait for the status to read ``CREATE_COMPLETE``—it can take 15 minutes or more. 3. Once deployment is complete, click the ``Outputs`` tab. The value of - ``PublicHostname`` is the hostname at which your EdgeDB instance is + ``PublicHostname`` is the hostname at which your Gel instance is publicly available. 4. Copy the hostname and run the following command to open a REPL to your instance. @@ -62,7 +62,7 @@ has been assigned to your EdgeDB instance: .. code-block:: bash $ edgedb --dsn edgedb://edgedb:@ --tls-security insecure - EdgeDB 3.x + Gel x.x Type \help for help, \quit to quit. edgedb> @@ -90,16 +90,16 @@ against this instance, as with local instances. .. code-block:: bash $ edgedb -I my_aws_instance - EdgeDB 3.x + Gel x.x Type \help for help, \quit to quit. edgedb> -To make changes to your EdgeDB deployment like upgrading the EdgeDB version or +To make changes to your Gel deployment like upgrading the Gel version or enabling the UI you can follow the CloudFormation `Updating a stack `_ instructions. Search for -``ContainerDefinitions`` in the template and you will find where EdgeDB's +``ContainerDefinitions`` in the template and you will find where Gel's :ref:`environment variables ` are -defined. To upgrade the EdgeDB version specify a +defined. To upgrade the Gel version specify a `docker image tag `_ with the image name ``edgedb/edgedb`` in the second step of the update workflow. @@ -112,7 +112,7 @@ your terminal: .. code-block:: bash $ aws cloudformation create-stack \ - --stack-name EdgeDB \ + --stack-name Gel \ --template-url \ https://edgedb-deploy.s3.us-east-2.amazonaws.com/edgedb-aurora.yml \ --capabilities CAPABILITY_NAMED_IAM \ @@ -122,7 +122,7 @@ your terminal: .. _cf-template: https://github.com/edgedb/edgedb-deploy/tree/dev/aws-cf .. _cf-deploy: https://console.aws.amazon.com - /cloudformation/home#/stacks/new?stackName=EdgeDB&templateURL= + /cloudformation/home#/stacks/new?stackName=Gel&templateURL= https%3A%2F%2Fedgedb-deploy.s3.us-east-2.amazonaws.com%2Fedgedb-aurora.yml .. _aws_console: https://console.aws.amazon.com @@ -551,7 +551,7 @@ Create an RDS Security Group $ aws rds create-db-subnet-group \ --region $REGION \ --db-subnet-group-name "$RDS_SUBNET_GROUP_NAME" \ - --db-subnet-group-description "EdgeDB RDS subnet group for ${NAME}" \ + --db-subnet-group-description "Gel RDS subnet group for ${NAME}" \ --subnet-ids $SUBNET_A_PRIVATE_ID $SUBNET_B_PRIVATE_ID Create an RDS Cluster @@ -627,7 +627,7 @@ Then use this password to create an AWS `secret Create a Load Balancer ---------------------- -Adding a load balancer will facilitate scaling the EdgeDB cluster. +Adding a load balancer will facilitate scaling the Gel cluster. .. code-block:: bash @@ -673,7 +673,7 @@ Adding a load balancer will facilitate scaling the EdgeDB cluster. Create an ECS Cluster --------------------- -The only thing left to do is create and ECS cluster and deploy the EdgeDB +The only thing left to do is create and ECS cluster and deploy the Gel container in it. .. code-block:: bash @@ -829,10 +829,10 @@ container in it. containerPort=5656, \ targetGroupArn=$TARGET_GROUP_ARN" -Create a local link to the new EdgeDB instance ----------------------------------------------- +Create a local link to the new Gel instance +------------------------------------------- -Create an local alias to the remote EdgeDB instance with ``edgedb instance +Create an local alias to the remote Gel instance with ``edgedb instance link``: .. code-block:: bash @@ -862,5 +862,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/azure_flexibleserver.rst b/docs/guides/deployment/azure_flexibleserver.rst index 2ef651f1d40..5d83ef37789 100644 --- a/docs/guides/deployment/azure_flexibleserver.rst +++ b/docs/guides/deployment/azure_flexibleserver.rst @@ -6,7 +6,7 @@ Azure :edb-alt-title: Deploying Gel to Azure -In this guide we show how to deploy EdgeDB using Azure's `Postgres +In this guide we show how to deploy Gel using Azure's `Postgres Flexible Server `_ as the backend. @@ -22,8 +22,8 @@ Prerequisites .. _azure-install: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli -Provision an EdgeDB instance -============================ +Provision an Gel instance +========================= Login to your Microsoft Azure account. @@ -98,7 +98,7 @@ EdgeDB requires Postgres' ``uuid-ossp`` extension which needs to be enabled. --name azure.extensions \ --value uuid-ossp -Start an EdgeDB container. +Start an Gel container. .. code-block:: bash @@ -121,7 +121,7 @@ Start an EdgeDB container. --environment-variables \ EDGEDB_SERVER_TLS_CERT_MODE=generate_self_signed \ -Persist the SSL certificate. We have configured EdgeDB to generate a self +Persist the SSL certificate. We have configured Gel to generate a self signed SSL certificate when it starts. However, if the container is restarted a new certificate would be generated. To preserve the certificate across failures or reboots copy the certificate files and use their contents in the @@ -157,7 +157,7 @@ or reboots copy the certificate files and use their contents in the "EDGEDB_SERVER_TLS_CERT=$cert" -To access the EdgeDB instance you've just provisioned on Azure from your local +To access the Gel instance you've just provisioned on Azure from your local machine link the instance. .. code-block:: bash @@ -188,5 +188,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/bare_metal.rst b/docs/guides/deployment/bare_metal.rst index 2fa9af1da62..220f03c0bee 100644 --- a/docs/guides/deployment/bare_metal.rst +++ b/docs/guides/deployment/bare_metal.rst @@ -6,21 +6,21 @@ Bare Metal :edb-alt-title: Deploying Gel to a Bare Metal Server -In this guide we show how to deploy EdgeDB to bare metal using your system's +In this guide we show how to deploy Gel to bare metal using your system's package manager and systemd. -Install the EdgeDB Package +Install the Gel Package ========================== -The steps for installing the EdgeDB package will be slightly different +The steps for installing the Gel package will be slightly different depending on your Linux distribution. Once you have the package installed you can jump to :ref:`ref_guide_deployment_bare_metal_enable_unit`. Debian/Ubuntu LTS ----------------- -Import the EdgeDB packaging key. +Import the Gel packaging key. .. code-block:: bash @@ -29,7 +29,7 @@ Import the EdgeDB packaging key. -o /usr/local/share/keyrings/edgedb-keyring.gpg \ https://packages.edgedb.com/keys/edgedb-keyring.gpg -Add the EdgeDB package repository. +Add the Gel package repository. .. code-block:: bash @@ -38,7 +38,7 @@ Add the EdgeDB package repository. $(grep "VERSION_CODENAME=" /etc/os-release | cut -d= -f2) main \ | sudo tee /etc/apt/sources.list.d/edgedb.list -Install the EdgeDB package. +Install the Gel package. .. code-block:: bash @@ -47,7 +47,7 @@ Install the EdgeDB package. CentOS/RHEL 7/8 --------------- -Add the EdgeDB package repository. +Add the Gel package repository. .. code-block:: bash @@ -55,7 +55,7 @@ Add the EdgeDB package repository. https://packages.edgedb.com/rpm/edgedb-rhel.repo \ > /etc/yum.repos.d/edgedb.repo -Install the EdgeDB package. +Install the Gel package. .. code-block:: bash @@ -67,7 +67,7 @@ Install the EdgeDB package. Enable a systemd unit ===================== -The EdgeDB package comes bundled with a systemd unit that is disabled by +The Gel package comes bundled with a systemd unit that is disabled by default. You can start the server by enabling the unit. .. code-block:: bash @@ -84,7 +84,7 @@ This will start the server on port 5656, and the data directory will be Set environment variables ========================= -To set environment variables when running EdgeDB with ``systemctl``, +To set environment variables when running Gel with ``systemctl``, .. code-block:: bash @@ -174,8 +174,8 @@ This allows connecting to the instance with its name. $ edgedb -I bare_metal_instance -Upgrading EdgeDB -================ +Upgrading Gel +============= .. note:: @@ -207,5 +207,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/digitalocean.rst b/docs/guides/deployment/digitalocean.rst index dcf34cfd414..867606816f0 100644 --- a/docs/guides/deployment/digitalocean.rst +++ b/docs/guides/deployment/digitalocean.rst @@ -6,7 +6,7 @@ DigitalOcean :edb-alt-title: Deploying Gel to DigitalOcean -In this guide we show how to deploy EdgeDB to DigitalOcean either with a +In this guide we show how to deploy Gel to DigitalOcean either with a One-click Deploy option or a :ref:`managed PostgreSQL ` database as the backend. @@ -21,7 +21,7 @@ Prerequisites * DigitalOcean account Click the button below and follow the droplet creation workflow on -DigitalOcean to deploy an EdgeDB instance. +DigitalOcean to deploy an Gel instance. .. image:: https://www.deploytodo.com/do-btn-blue.svg :target: 1-click-button_ @@ -170,7 +170,7 @@ add one now. --wait )" Configure the backend Postgres DSN. To simplify the initial deployment, let's -instruct EdgeDB to run in insecure mode (with password authentication off and +instruct Gel to run in insecure mode (with password authentication off and an autogenerated TLS certificate). We will secure the instance once things are up and running. @@ -205,7 +205,7 @@ Set the security policy to strict. .. note:: - To upgrade an existing EdgeDB droplet to the latest point release, ``ssh`` + To upgrade an existing Gel droplet to the latest point release, ``ssh`` into your droplet and run the following. .. code-block:: bash @@ -226,5 +226,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/docker.rst b/docs/guides/deployment/docker.rst index 87f7cb2687b..d7bbd813df2 100644 --- a/docs/guides/deployment/docker.rst +++ b/docs/guides/deployment/docker.rst @@ -15,7 +15,7 @@ This image is primarily intended to be used directly when there is a requirement to use Docker containers, such as in production, or in a development setup that involves multiple containers orchestrated by Docker Compose or a similar tool. Otherwise, using the :ref:`ref_cli_edgedb_server` -CLI on the host system is the recommended way to install and run EdgeDB +CLI on the host system is the recommended way to install and run Gel servers. @@ -33,7 +33,7 @@ The simplest way to run the image (without data persistence) is this: See the :ref:`ref_guides_deployment_docker_customization` section below for the meaning of the ``EDGEDB_SERVER_SECURITY`` variable and other options. -Then, to authenticate to the EdgeDB instance and store the credentials in a +Then, to authenticate to the Gel instance and store the credentials in a Docker volume, run: .. code-block:: bash @@ -119,7 +119,7 @@ migration can be created with: $ edgedb --tls-security=insecure -P 5656 migration create -Alternatively, if you don't have the EdgeDB CLI installed on your host +Alternatively, if you don't have the Gel CLI installed on your host machine, you can use the CLI bundled with the server container: .. code-block:: bash @@ -132,7 +132,7 @@ machine, you can use the CLI bundled with the server container: Configuration ============= -The Docker image supports the same set of enviroment variables as the EdgeDB +The Docker image supports the same set of enviroment variables as the Gel server process, which are documented under :ref:`Reference > Environment Variables `. @@ -149,7 +149,7 @@ and some Docker-specific environment variables, documented below. Initial configuration --------------------- -When an EdgeDB container starts on the specified data directory or remote +When an Gel container starts on the specified data directory or remote Postgres cluster for the first time, initial instance setup is performed. This is called the *bootstrap phase*. @@ -271,5 +271,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/fly_io.rst b/docs/guides/deployment/fly_io.rst index a94859ee5e0..9064d8f6422 100644 --- a/docs/guides/deployment/fly_io.rst +++ b/docs/guides/deployment/fly_io.rst @@ -6,9 +6,9 @@ Fly.io :edb-alt-title: Deploying Gel to Fly.io -In this guide we show how to deploy EdgeDB using a `Fly.io `_ +In this guide we show how to deploy Gel using a `Fly.io `_ PostgreSQL cluster as the backend. The deployment consists of two apps: one -running Postgres and the other running EdgeDB. +running Postgres and the other running Gel. Prerequisites @@ -20,11 +20,11 @@ Prerequisites .. _flyctl-install: https://fly.io/docs/getting-started/installing-flyctl/ -Provision a Fly.io app for EdgeDB -================================= +Provision a Fly.io app for Gel +============================== Every Fly.io app must have a globally unique name, including service VMs like -Postgres and EdgeDB. Pick a name and assign it to a local environment variable +Postgres and Gel. Pick a name and assign it to a local environment variable called ``EDB_APP``. In the command below, replace ``myorg-edgedb`` with a name of your choosing. @@ -59,20 +59,20 @@ we'll need. There are a couple more environment variables we need to set: Let's discuss what's going on with all these secrets. -- The ``EDGEDB_SERVER_BACKEND_DSN_ENV`` tells the EdgeDB container where to +- The ``EDGEDB_SERVER_BACKEND_DSN_ENV`` tells the Gel container where to look for the PostgreSQL connection string (more on that below) -- The ``EDGEDB_SERVER_TLS_CERT_MODE`` tells EdgeDB to auto-generate a +- The ``EDGEDB_SERVER_TLS_CERT_MODE`` tells Gel to auto-generate a self-signed TLS certificate. You may instead choose to provision a custom TLS certificate. In this case, you should instead create two other secrets: assign your certificate to ``EDGEDB_SERVER_TLS_CERT`` and your private key to ``EDGEDB_SERVER_TLS_KEY``. -- Lastly, ``EDGEDB_SERVER_PORT`` tells EdgeDB to listen on port 8080 instead +- Lastly, ``EDGEDB_SERVER_PORT`` tells Gel to listen on port 8080 instead of the default 5656, because Fly.io prefers ``8080`` for its default health checks. -Finally, let's configure the VM size as EdgeDB requires a little bit more than +Finally, let's configure the VM size as Gel requires a little bit more than the default Fly.io VM side provides. Put this in a file called ``fly.toml`` in your current directory.: @@ -90,7 +90,7 @@ your current directory.: Create a PostgreSQL cluster =========================== -Now we need to provision a PostgreSQL cluster and attach it to the EdgeDB app. +Now we need to provision a PostgreSQL cluster and attach it to the Gel app. .. note:: @@ -141,7 +141,7 @@ this command: ... With the VM scaled sufficiently, we can now attach the PostgreSQL cluster to -the EdgeDB app: +the Gel app: .. code-block:: bash @@ -153,8 +153,8 @@ the EdgeDB app: The following secret was added to myorg-edgedb: DATABASE_URL=postgres://... -Lastly, EdgeDB needs the ability to create Postgres databases and roles, -so let's adjust the permissions on the role that EdgeDB will use to connect +Lastly, Gel needs the ability to create Postgres databases and roles, +so let's adjust the permissions on the role that Gel will use to connect to Postgres: .. code-block:: bash @@ -166,10 +166,10 @@ to Postgres: .. _ref_guide_deployment_fly_io_start_edgedb: -Start EdgeDB -============ +Start Gel +========= -Everything is set! Time to start EdgeDB. +Everything is set! Time to start Gel. .. code-block:: bash @@ -180,7 +180,7 @@ Everything is set! Time to start EdgeDB. ✔ Machine e286630dce9638 [app] was created ------- -That's it! You can now start using the EdgeDB instance located at +That's it! You can now start using the Gel instance located at ``edgedb://myorg-edgedb.internal`` in your Fly.io apps. @@ -194,7 +194,7 @@ Persist the generated TLS certificate ===================================== Now we need to persist the auto-generated TLS certificate to make sure it -survives EdgeDB app restarts. (If you've provided your own certificate, +survives Gel app restarts. (If you've provided your own certificate, skip this step). .. code-block:: bash @@ -214,7 +214,7 @@ can construct the DSN with the following components: - ````: the default value — ``edgedb`` - ````: the value we assigned to ``$PASSWORD`` -- ````: the name of your EdgeDB app (stored in the +- ````: the name of your Gel app (stored in the ``$EDB_APP`` environment variable) suffixed with ``.internal``. Fly uses this synthetic TLD to simplify inter-app communication. Ex: ``myorg-edgedb.internal``. @@ -249,7 +249,7 @@ API server) set the value of the ``EDGEDB_DSN`` secret inside that app. EDGEDB_DSN=$DSN \ --app my-other-fly-app -We'll also set another variable that will disable EdgeDB's TLS checks. +We'll also set another variable that will disable Gel's TLS checks. Inter-application communication is secured by Fly so TLS isn't vital in this case; configuring TLS certificates is also beyond the scope of this guide. @@ -266,7 +266,7 @@ You can also set these values as environment variables inside your From external application ------------------------- -If you need to access EdgeDB from outside the Fly.io network, you'll need to +If you need to access Gel from outside the Fly.io network, you'll need to configure the Fly.io proxy to let external connections in. Let's make sure the ``[[services]]`` section in our ``fly.toml`` looks @@ -294,8 +294,8 @@ something like this: restart_limit = 0 timeout = "2s" -In the same directory, :ref:`redeploy the EdgeDB app -`. This makes the EdgeDB port +In the same directory, :ref:`redeploy the Gel app +`. This makes the Gel port available to the outside world. You can now access the instance from any host via the following public DSN: ``edgedb://edgedb:$PASSWORD@$EDB_APP.fly.dev``. @@ -311,7 +311,7 @@ You can securely obtain the certificate content by running: From your local machine ----------------------- -To access the EdgeDB instance from local development machine/laptop, install +To access the Gel instance from local development machine/laptop, install the Wireguard `VPN `_ and create a tunnel, as described on Fly's `Private Networking `_ @@ -349,5 +349,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/gcp.rst b/docs/guides/deployment/gcp.rst index 7f6e8a9829b..a5bf7f842c0 100644 --- a/docs/guides/deployment/gcp.rst +++ b/docs/guides/deployment/gcp.rst @@ -6,7 +6,7 @@ Google Cloud :edb-alt-title: Deploying Gel to Google Cloud -In this guide we show how to deploy EdgeDB on GCP using Cloud SQL and +In this guide we show how to deploy Gel on GCP using Cloud SQL and Kubernetes. Prerequisites @@ -130,13 +130,13 @@ Then use this ``credentials.json`` to authenticate the Kubernetes CLI tool --from-literal=password=$PASSWORD \ --from-literal=instance=${INSTANCE_CONNECTION_NAME}=tcp:5432 -Deploy EdgeDB -============= +Deploy Gel +========== -Download the starter EdgeDB Kubernetes configuration file. This file specifies +Download the starter Gel Kubernetes configuration file. This file specifies a persistent volume, a container running a `Cloud SQL authorization proxy `_, and a container to -run `EdgeDB itself `_. It relies on +run `Gel itself `_. It relies on the secrets we declared in the previous step. .. code-block:: bash @@ -157,7 +157,7 @@ Ensure the pods are running. The ``READY 0/2`` tells us neither of the two pods have finished booting. Re-run the command until ``2/2`` pods are ``READY``. -If there were errors you can check EdgeDB's logs with: +If there were errors you can check Gel's logs with: .. code-block:: bash @@ -166,7 +166,7 @@ If there were errors you can check EdgeDB's logs with: Persist TLS Certificate ======================= -Now that our EdgeDB instance is up and running, we need to download a local +Now that our Gel instance is up and running, we need to download a local copy of its self-signed TLS certificate (which it generated on startup) and pass it as a secret into Kubernetes. Then we'll redeploy the pods. @@ -186,8 +186,8 @@ pass it as a secret into Kubernetes. Then we'll redeploy the pods. $ kubectl apply -f deployment.yaml -Expose EdgeDB -============= +Expose Gel +========== .. code-block:: bash @@ -229,7 +229,7 @@ To test it, try opening a REPL: .. code-block:: bash $ edgedb --dsn $EDGEDB_DSN --tls-security insecure - EdgeDB 3.x (repl 3.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> select "hello world!"; @@ -271,7 +271,7 @@ In production ------------- To connect to this instance in production, set the ``EDGEDB_DSN`` environment -variable wherever you deploy your application server; EdgeDB's client +variable wherever you deploy your application server; Gel's client libraries read the value of this variable to know how to connect to your instance. @@ -279,5 +279,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/health_checks.rst b/docs/guides/deployment/health_checks.rst index 2f9fda44bef..39de345e77e 100644 --- a/docs/guides/deployment/health_checks.rst +++ b/docs/guides/deployment/health_checks.rst @@ -4,7 +4,7 @@ Health Checks ============= -You may want to monitor the status of your EdgeDB instance. Is it up? Is it +You may want to monitor the status of your Gel instance. Is it up? Is it ready to take queries? This guide will show you to perform health checks using HTTP and the ``alive`` and ``ready`` endpoints. diff --git a/docs/guides/deployment/heroku.rst b/docs/guides/deployment/heroku.rst index 82ca9bebd1b..23b41f2ac40 100644 --- a/docs/guides/deployment/heroku.rst +++ b/docs/guides/deployment/heroku.rst @@ -6,10 +6,10 @@ Heroku :edb-alt-title: Deploying Gel to Heroku -In this guide we show how to deploy EdgeDB to Heroku using a Heroku PostgreSQL +In this guide we show how to deploy Gel to Heroku using a Heroku PostgreSQL add-on as the backend. -Because of Heroku's architecture EdgeDB must be deployed with a web app on +Because of Heroku's architecture Gel must be deployed with a web app on Heroku. For this guide we will use a `todo app written in Node `_. .. _todo-repo: https://github.com/edgedb/simpletodo/tree/main @@ -42,7 +42,7 @@ First copy the code, initialize a new git repo, and create a new heroku app. $ heroku apps:create --buildpack heroku/nodejs $ edgedb project init --non-interactive -If you are using the :ref:`JS query builder for EdgeDB ` then +If you are using the :ref:`JS query builder for Gel ` then you will need to check the ``dbschema/edgeql-js`` directory in to your git repo after running ``yarn edgeql-js``. The ``edgeql-js`` command cannot be run during the build step on Heroku because it needs access to a running @@ -64,7 +64,7 @@ Create a PostgreSQL Add-on ========================== Heroku's smallest PostgreSQL plan, Hobby Dev, limits the number of rows to -10,000, but EdgeDB's standard library uses more than 20,000 rows so we need to +10,000, but Gel's standard library uses more than 20,000 rows so we need to use a different plan. We'll use the `Standard 0 plan `_ for this guide. @@ -75,10 +75,10 @@ this guide. $ heroku addons:create --wait heroku-postgresql:standard-0 -Add the EdgeDB Buildpack -======================== +Add the Gel Buildpack +===================== -To run EdgeDB on Heroku we'll add the `EdgeDB buildpack `_. +To run Gel on Heroku we'll add the `Gel buildpack `_. .. _buildpack: https://github.com/edgedb/heroku-buildpack-edgedb @@ -92,8 +92,8 @@ To run EdgeDB on Heroku we'll add the `EdgeDB buildpack `_. Use ``start-edgedb`` in the Procfile ==================================== -To make EdgeDB available to a process prepend the command with ``start-edgedb`` -which is provided by the EdgeDB buildpack. For the sample application in this +To make Gel available to a process prepend the command with ``start-edgedb`` +which is provided by the Gel buildpack. For the sample application in this guide, the web process is started with the command ``npm start``. If you have other processes in your application besides/instead of web that need to access EdgeDB those process commands should be prepended with ``start-edgedb`` too. @@ -119,7 +119,7 @@ Scale the web dyno ================== The default dyno size has 512MB of memory which is a little under powered to -run EdgeDB. Scale the dyno so that it has 1GB of memory available. +run Gel. Scale the dyno so that it has 1GB of memory available. .. code-block:: bash @@ -129,5 +129,5 @@ Health Checks ============= Using an HTTP client, you can perform health checks to monitor the status of -your EdgeDB instance. Learn how to use them with our :ref:`health checks guide +your Gel instance. Learn how to use them with our :ref:`health checks guide `. diff --git a/docs/guides/deployment/index.rst b/docs/guides/deployment/index.rst index 6d6a225758c..e549fcc3e0d 100644 --- a/docs/guides/deployment/index.rst +++ b/docs/guides/deployment/index.rst @@ -7,11 +7,11 @@ Deployment EdgeDB can be hosted on all major cloud hosting platforms. The guides below demonstrate how to spin up both a managed PostgreSQL instance and a container -running EdgeDB `in Docker `_. +running Gel `in Docker `_. .. note:: Minimum requirements - As a rule of thumb, the EdgeDB Docker container requires 1GB RAM! Images + As a rule of thumb, the Gel Docker container requires 1GB RAM! Images with insufficient RAM may experience unexpected issues during startup. .. toctree:: diff --git a/docs/guides/index.rst b/docs/guides/index.rst index f1f8f642cc1..dc6ce617117 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -6,11 +6,11 @@ Guides ====== -These guides contain tutorials introducing EdgeDB to newcomers and +These guides contain tutorials introducing Gel to newcomers and how-tos providing more experienced users with examples and advice for tackling some common tasks. -If you are new to EdgeDB check out our :ref:`Quickstart ` +If you are new to Gel check out our :ref:`Quickstart ` guide! diff --git a/docs/guides/migrations/guide.rst b/docs/guides/migrations/guide.rst index cff239554ba..6e8bd3d370c 100644 --- a/docs/guides/migrations/guide.rst +++ b/docs/guides/migrations/guide.rst @@ -8,7 +8,7 @@ Basics EdgeQL is a strongly-typed language, which means that it moves checks and verification of your code to compile time as much as possible -instead of performing them at run time. EdgeDB's view is that a schema +instead of performing them at run time. Gel's view is that a schema should allow you to set types, constraints, expressions, and more so that you can confidently know what sort of behavior to expect from your data. Laying a type-safe foundation means a bit more thinking up front, but saves @@ -20,7 +20,7 @@ When you *do* eventually need to make a change, you'll need to migrate your schema from its current state to a new state. The basics of creating a project, modifying its schema, and migrating -it in EdgeDB are pretty easy: +it in Gel are pretty easy: - Type ``edgedb project init`` to start a project, - Open the newly created empty schema at ``dbschema/default.esdl`` and add @@ -40,11 +40,11 @@ it in EdgeDB are pretty easy: ``edgedb migration create``). Once that is done, you will have a blank slate on which to start over again. -But many EdgeDB users have needs that go beyond these basics. In addition, +But many Gel users have needs that go beyond these basics. In addition, schema migrations are pretty interesting and teach you a lot about -what EdgeDB does behind the scenes. This guide will turn you from +what Gel does behind the scenes. This guide will turn you from a casual migration user into one with a lot more tools at hand, along -with a deeper understanding of the internals of EdgeDB at the same +with a deeper understanding of the internals of Gel at the same time. EdgeDB's built-in tools are what make schema migrations easy, and @@ -57,7 +57,7 @@ SDL: For humans =============== SDL, not DDL, is the primary way for you to create and migrate your -schema in EdgeDB. You don't need to work with DDL to use EdgeDB any +schema in Gel. You don't need to work with DDL to use Gel any more than you need to know how to change a tire to drive a car. SDL is built for humans to read, which is why it is said to be *declarative*. @@ -68,7 +68,7 @@ real life would be telling a friend to show up at your house at 6416 Riverside Way. You've declared what the final result should be, but it's up to your friend to find how to achieve it. -Now let's look at some real SDL and think about its role in EdgeDB. +Now let's look at some real SDL and think about its role in Gel. Here is a simple example of a schema: .. code-block:: sdl @@ -79,18 +79,18 @@ Here is a simple example of a schema: } } -If you have EdgeDB installed and want to follow along, type ``edgedb +If you have Gel installed and want to follow along, type ``edgedb project init`` and copy the above schema into your ``default.esdl`` file inside the ``/dbschema`` folder it creates. Then save the file. .. note:: While schema is usually contained inside the ``default.esdl`` file, - you can divide a schema over multiple files if you like. EdgeDB will + you can divide a schema over multiple files if you like. Gel will combine all ``.esdl`` files inside the ``/dbschema`` folder into a single schema. -Type ``edgedb`` to start the EdgeDB REPL, and, into the REPL, type +Type ``edgedb`` to start the Gel REPL, and, into the REPL, type ``describe schema as sdl``. The output will be ``{'module default{};'}`` — nothing more than the empty ``default`` module. What happened? Our ``type User`` is nowhere to be found. @@ -100,12 +100,12 @@ person's house, it doesn't *do* anything on its own. With SDL you are declaring what you want the final result to be: a schema containing a single type called ``User``, with a property of type ``str`` called ``name``. -In order for a migration to happen, the EdgeDB server needs to receive +In order for a migration to happen, the Gel server needs to receive DDL statements telling it what changes to make, in the exact same way that you give instructions like "turn right at the next intersection" -to your friend who is trying to find your house. In EdgeDB's case, +to your friend who is trying to find your house. In Gel's case, these commands will start with words like ``create`` and ``drop`` -and ``alter`` to tell it what changes to make. EdgeDB accomplishes +and ``alter`` to tell it what changes to make. Gel accomplishes these changes by knowing how to turn your declarative SDL into a schema migration file that contains the DDL statements to accomplish the necessary changes. @@ -174,7 +174,7 @@ a property called ``name`` anymore. .. note:: - EdgeDB commands inside the REPL use a backslash instead of the ``edgedb`` + Gel commands inside the REPL use a backslash instead of the ``edgedb`` command, so you can migrate your schema inside the REPL by typing ``\migration create`` , followed by ``\migrate``. Not only are the comands shorter, but they also execute faster. This is because the database client @@ -194,7 +194,7 @@ Similarly, if you want add a property to an existing type and the property's type is a new scalar type, the database will need to create the new scalar type first. -Let's take a look at this by first getting EdgeDB to describe our +Let's take a look at this by first getting Gel to describe our schema to us. Typing ``describe schema;`` inside the REPL will display the following DDL statements: @@ -325,7 +325,7 @@ a single letter. Can you spot the difference? } The only difference from the current schema is that we would like -to change the property name ``name`` to ``nam``, but this time EdgeDB isn't +to change the property name ``name`` to ``nam``, but this time Gel isn't sure what change we wanted to make. Did we intend to: - Change ``name`` to ``nam`` and keep the existing data? @@ -338,14 +338,14 @@ some pretty helpful output: .. code-block:: edgeql-repl db> \migration create --non-interactive - EdgeDB intended to apply the following migration: + Gel intended to apply the following migration: ALTER TYPE default::User { ALTER PROPERTY name { RENAME TO nam; }; }; But confidence is 0.67, below minimum threshold of 0.99999 - Error executing command: EdgeDB is unable to make a decision. + Error executing command: Gel is unable to make a decision. Please run in interactive mode to confirm changes, or use `--allow-unsafe` @@ -456,7 +456,7 @@ Here is what that would look like: > y did you create object type 'default::User'? [y,n,l,c,b,s,q,?] > n - Error executing command: EdgeDB could not resolve migration with + Error executing command: Gel could not resolve migration with the provided answers. Please retry with different answers. ``l`` (or ``list``) @@ -594,7 +594,7 @@ will need to be: If you change the statement to read in exactly the way the output suggests, the migration will now work. -That's the manual way to do a data migration, but EdgeDB also has an +That's the manual way to do a data migration, but Gel also has an ``edgedb migration edit`` command that will automate the process for you. Using ``edgedb migration edit`` will open up the most recent migration for you to change, and update the migration hash when you close the window. @@ -612,7 +612,7 @@ require appending ``--allow-empty`` to the command. Just do the following: schema file manually, copy the suggested name into the migration hash and type ``edgedb migrate`` again. -The `EdgeDB tutorial `_ is a good example of a database +The `Gel tutorial `_ is a good example of a database set up with both a schema migration and a data migration. Setting up a database with `schema changes in one file and default data in a second file `_ is a nice way to separate the two operations @@ -637,7 +637,7 @@ to work through: .. code-block:: - Initializing EdgeDB instance... + Initializing Gel instance... Applying migrations... Applied m13brvdizqpva6icpcvmsc3fee2yt5j267uba6jugy6iugcbs2djkq (00001.edgeql) @@ -678,7 +678,7 @@ a single file. Fixups during a squash ---------------------- -If your schema doesn't match the schema in the database, EdgeDB will +If your schema doesn't match the schema in the database, Gel will prompt you to create a *fixup* file, which can be useful to, as the CLI says, "automate upgrading other instances to a squashed revision". You'll see fixups inside ``/dbschema/fixups``. Their file names @@ -815,8 +815,8 @@ command itself will show the following input when the process starts up: .. code-block:: - Connecting to EdgeDB instance 'anything' at localhost:10700... - EdgeDB Watch initialized. + Connecting to Gel instance 'anything' at localhost:10700... + Gel Watch initialized. Hint: Use `edgedb migration create` and `edgedb migrate --dev-mode` to apply changes once done. Monitoring "/home/instancename". @@ -933,7 +933,7 @@ creating a new branch, you can isolate schema changes from your other branches. Imagine a scenario in which your main branch is called ``main`` (which is the default name for the initial branch) and your feature branch is called -``feature``. This is the ideal workflow for using an EdgeDB branch alongside a feature +``feature``. This is the ideal workflow for using an Gel branch alongside a feature branch in your VCS to develop a new feature: 1. Create a new feature branch with :ref:`ref_cli_edgedb_branch_create` @@ -972,7 +972,7 @@ So, you really want to use DDL? =============================== You might have a good reason to use a direct DDL statement or two -to change your schema. How do you make that happen? EdgeDB disables +to change your schema. How do you make that happen? Gel disables the usage of DDL by default if you have already carried out a migration through the recommended migration commands, so this attempt to use DDL will not work: @@ -1154,7 +1154,7 @@ make each ``User`` friends with all of the others: Now what happens if we now want to change ``multi friends`` to an ``array``? If we were simply changing a scalar property to another -property it would be easy, because EdgeDB would prompt us for a conversion +property it would be easy, because Gel would prompt us for a conversion expression, but a change from a link to a property is different: .. code-block:: sdl @@ -1242,13 +1242,13 @@ We've now reached the most optional part of the migrations tutorial, but an interesting one for those curious about what goes on behind the scenes during a migration. -Migrations in EdgeDB before the advent of the ``edgedb project`` flow +Migrations in Gel before the advent of the ``edgedb project`` flow were still automated but required more manual work if you didn't want to accept all of the suggestions provided by the server. This process is in fact still used to migrate even today; the CLI just facilitates it by making it easy to respond to the generated suggestions. -:ref:`Early EdgeDB migrations took place inside a transaction ` +:ref:`Early Gel migrations took place inside a transaction ` handled by the user that essentially went like this: .. code-block:: @@ -1268,7 +1268,7 @@ are going to have some fun with. You can see It is *very* finicky compared to the CLI, resulting in a failed transaction if any step along the way is different from the expected behavior, but is an entertaining challenge to try to get right if you want to -truly understand how migrations work in EdgeDB. +truly understand how migrations work in Gel. This process requires looking at the server's proposed solutions every step of the way, and these steps are best seen in JSON format. We can make diff --git a/docs/guides/migrations/index.rst b/docs/guides/migrations/index.rst index d822aa11265..602f1a31d83 100644 --- a/docs/guides/migrations/index.rst +++ b/docs/guides/migrations/index.rst @@ -4,7 +4,7 @@ Schema migrations ================= -Welcome to the guide to EdgeDB migrations! Let's get started. +Welcome to the guide to Gel migrations! Let's get started. .. toctree:: :maxdepth: 2 diff --git a/docs/guides/migrations/tips.rst b/docs/guides/migrations/tips.rst index d2fe5500689..86965b5a792 100644 --- a/docs/guides/migrations/tips.rst +++ b/docs/guides/migrations/tips.rst @@ -291,7 +291,7 @@ different from specifying a ``default`` value since it will be applied to *existing* objects, whereas the ``default`` applies to *new ones*. Unseen to us (unless we take a look at the automatically generated -``.edgeql`` files inside our ``/dbschema`` folder), EdgeDB has created +``.edgeql`` files inside our ``/dbschema`` folder), Gel has created a migration script that includes the following command to make our schema change happen. @@ -404,7 +404,7 @@ Since we've done this on purpose, we can update the file and run Finally, we evolved our schema all the way from having an optional property ``name`` all the way to making it both *required* and -*exclusive*. We've worked with the EdgeDB :ref:`migration tools +*exclusive*. We've worked with the Gel :ref:`migration tools ` to iron out the kinks throughout the migration process. At this point we take a quick look at the way duplicate ``User`` objects were resolved to decide whether we need to diff --git a/docs/guides/tutorials/chatgpt_bot.rst b/docs/guides/tutorials/chatgpt_bot.rst index 06cac65cb6b..afff2e53e04 100644 --- a/docs/guides/tutorials/chatgpt_bot.rst +++ b/docs/guides/tutorials/chatgpt_bot.rst @@ -8,7 +8,7 @@ ChatGPT *For additional context, check out* `our blog post about why and how we use ChatGPT via embeddings`_ *to create our “Ask AI” bot which answers questions -related to the EdgeDB docs.* +related to the Gel docs.* .. lint-off @@ -18,7 +18,7 @@ related to the EdgeDB docs.* .. lint-on In this tutorial we're going to build a documentation chatbot with -`Next.js `_, `OpenAI `_, and EdgeDB. +`Next.js `_, `OpenAI `_, and Gel. .. warning:: @@ -81,7 +81,7 @@ Embedding generation requires two steps: 1. create embeddings for each section using `OpenAI's embeddings API `_ -2. store the embeddings data in EdgeDB using pgvector +2. store the embeddings data in Gel using pgvector Each time a user asks a question, our app will: @@ -185,16 +185,16 @@ will make it easier to import them. .. lint-on -Now, we'll create an instance of EdgeDB for our project, but first, we need to -install EdgeDB! +Now, we'll create an instance of Gel for our project, but first, we need to +install Gel! -Install the EdgeDB CLI ----------------------- +Install the Gel CLI +------------------- -*If you already have EdgeDB installed, you can skip to creating an instance.* +*If you already have Gel installed, you can skip to creating an instance.* -Before we can create an instance for our project, we need to install the EdgeDB +Before we can create an instance for our project, we need to install the Gel CLI. On Linux or MacOS, run the following in your terminal and follow the on-screen instructions: @@ -212,10 +212,10 @@ For other installation scenarios, see the "Additional installation methods" section of `our "Install" page `_. -Create a local EdgeDB instance ------------------------------- +Create a local Gel instance +--------------------------- -To create our instance, let's initialize our project as an EdgeDB project. Run +To create our instance, let's initialize our project as an Gel project. Run the following in the root of the project: .. code-block:: bash @@ -227,16 +227,16 @@ the following in the root of the project: Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: docs_chatbot]: > docs_chatbot - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project + Checking Gel versions... + Specify the version of Gel to use with this project [default: 3.2]: > 3.2 -The CLI should set up an EdgeDB project, an instance, and a default branch on +The CLI should set up an Gel project, an instance, and a default branch on that instance. - Confirm project creation by checking for an ``edgedb.toml`` file and a @@ -260,7 +260,7 @@ Create a ``.env.local`` file in the root of your new Next.js project. $ touch .env.local -We're going to add a couple of variables to that file to configure the EdgeDB +We're going to add a couple of variables to that file to configure the Gel client. We'll need to run a command on our new instance to get the value for one of those. Since we'll be using the `Edge runtime `_ in our Next.js project, the @@ -282,7 +282,7 @@ this to it: Replace ```` with the value you copied earlier. -We're going to be using the EdgeDB HTTP client a bit later to connect to our +We're going to be using the Gel HTTP client a bit later to connect to our database, but it requires a trusted TLS/SSL certificate. Local development instances use self signed certificates, and using HTTPS with these certificates will result in an error. To work around this error, we allow the client to @@ -439,7 +439,7 @@ gives us context to feed to the LLM. We will need the token count later when calculating how many related sections fit inside the prompt context while staying under the model's token limit. -Open the empty schema file that was generated when we initialized the EdgeDB +Open the empty schema file that was generated when we initialized the Gel project (located at ``dbschema/default.esdl`` from the project directory). We'll walk through what we'll add to it, one step at a time. First, add this at the top of the file (above ``module default {``): @@ -452,7 +452,7 @@ the top of the file (above ``module default {``): # Schema will go here } -We are able to store embeddings and find similar embeddings in the EdgeDB +We are able to store embeddings and find similar embeddings in the Gel database because of the ``pgvector`` extension. In order to use it in our schema, we have to activate the ``ext::pgvector`` module with ``using extension pgvector`` at the beginning of the schema file. This module gives us access to @@ -602,7 +602,7 @@ libraries that will help us. --save-dev The ``@edgedb/generate`` package provides a set of code generation tools that -are useful when developing an EdgeDB-backed applications with +are useful when developing an Gel-backed applications with TypeScript/JavaScript. We're going to write queries using our :ref:`query builder `, but before we can, we need to run the query builder generator. @@ -811,7 +811,7 @@ Now, we generate embeddings from the content. We need to be careful about how we approach the API calls to generate the embeddings since they could have a big impact on how long generation takes, especially as your documentation grows. The simplest solution would be to make a single request to the API for -each section, but in the case of EdgeDB's documentation, which has around 3,000 +each section, but in the case of Gel's documentation, which has around 3,000 pages, this would take about half an hour. Since OpenAI's embeddings API can take not only a *single* string but also an @@ -929,7 +929,7 @@ Again, we'll break the ``storeEmbeddings`` function apart and walk through it. } // … -We create our EdgeDB client and get our documentation paths by calling +We create our Gel client and get our documentation paths by calling ``walk``. We also log out some debug information showing how many sections were discovered. Then, we prep our ``Section`` objects by calling the ``prepareSectionsData`` function we just walked through and passing in the @@ -947,7 +947,7 @@ Next, we'll store this data. // Delete old data from the DB. await e.delete(e.Section).run(client); - // Bulk-insert all data into EdgeDB database. + // Bulk-insert all data into Gel database. const query = e.params({ sections: e.json }, ({ sections }) => { return e.for(e.json_array_unpack(sections), (section) => { return e.insert(e.Section, { @@ -987,7 +987,7 @@ Here's what the whole function looks like: // Delete old data from the DB. await e.delete(e.Section).run(client); - // Bulk-insert all data into EdgeDB database. + // Bulk-insert all data into Gel database. const query = e.params({ sections: e.json }, ({ sections }) => { return e.for(e.json_array_unpack(sections), (section) => { return e.insert(e.Section, { @@ -1090,7 +1090,7 @@ into your ``generate-embeddings.ts`` file. // Delete old data from the DB. await e.delete(e.Section).run(client); - // Bulk-insert all data into EdgeDB database. + // Bulk-insert all data into Gel database. const query = e.params({ sections: e.json }, ({ sections }) => { return e.for(e.json_array_unpack(sections), (section) => { return e.insert(e.Section, { @@ -1161,7 +1161,7 @@ a simple command: $ npm run embeddings -After the script finishes, open the EdgeDB UI. +After the script finishes, open the Gel UI. .. code-block:: bash @@ -1234,7 +1234,7 @@ when we create the prompt from user's question and related sections. Let's talk briefly about runtimes. In the context of Next.js, "runtime" refers to the set of libraries, APIs, and general functionality available to your code during execution. Next.js supports `Node.js and Edge runtimes`_. (The "Edge" -runtime is coincidentally named but is not related to EdgeDB.) +runtime is coincidentally named but is not related to Gel.) Streaming is supported within both runtimes, but the implementation is a bit simpler when using Edge, so that's what we will use here. The Edge runtime is @@ -1279,7 +1279,7 @@ writing some configuration. The first imports are templates from the ``common-tags`` library we installed -earlier. Then, we import the EdgeDB binding. The third import is the query +earlier. Then, we import the Gel binding. The third import is the query builder we described previously. We also import our errors and our OpenAI API client initializer function. @@ -1342,7 +1342,7 @@ build toward an answer. 2. If the query fails, we throw. If it passes, we generate embeddings for it using the OpenAI embedding API. This is handled by our ``getEmbedding`` function. -3. We get related documentation sections from the EdgeDB database. This is +3. We get related documentation sections from the Gel database. This is handled by ``getContext``. 4. We create the full prompt as our input to the chat completions API by combining the question, related documentation sections, and a system @@ -1473,7 +1473,7 @@ a variable named ``getSectionsQuery``. } ); -In the above code, we use EdgeDB's TypeScript query builder to create a query. +In the above code, we use Gel's TypeScript query builder to create a query. The query takes a few parameters: * ``target``: Embedding array to compare against to find related sections. In @@ -1494,7 +1494,7 @@ ordering, and limit clause. We use the ``cosine_distance`` function to calculate the similarity between the user's question and our documentation sections. We have access to this function -through EdgeDB's pgvector extension. We then filter on that property by +through Gel's pgvector extension. We then filter on that property by comparing it to the ``matchThreshold`` value we will pass when executing the query. @@ -1580,8 +1580,8 @@ response. We'll combine all of these parts in a function called function createFullPrompt(query: string, context: string) { const systemMessage = ` - As an enthusiastic EdgeDB expert keen to assist, - respond to questions referencing the given EdgeDB + As an enthusiastic Gel expert keen to assist, + respond to questions referencing the given Gel sections. If unable to help based on documentation, respond @@ -1590,7 +1590,7 @@ response. We'll combine all of these parts in a function called return stripIndents` ${oneLineTrim`${systemMessage}`} - EdgeDB sections: """ + Gel sections: """ ${context} """ @@ -1795,8 +1795,8 @@ Now, let's take a look at the whole thing. Copy and paste this into your function createFullPrompt(query: string, context: string) { const systemMessage = ` - As an enthusiastic EdgeDB expert keen to assist, - respond to questions referencing the given EdgeDB + As an enthusiastic Gel expert keen to assist, + respond to questions referencing the given Gel sections. If unable to help based on documentation, respond @@ -1805,7 +1805,7 @@ Now, let's take a look at the whole thing. Copy and paste this into your return stripIndents` ${oneLineTrim`${systemMessage}`} - EdgeDB sections: """ + Gel sections: """ ${context} """ @@ -2303,6 +2303,6 @@ embedding generation happens and `components/gpt `_, which contains most of the UI for our chatbot. -If you have trouble with the build or just want to hang out with other EdgeDB +If you have trouble with the build or just want to hang out with other Gel users, please join `our awesome community on Discord `_! diff --git a/docs/guides/tutorials/cloudflare_workers.rst b/docs/guides/tutorials/cloudflare_workers.rst index 705ae3ab71a..9820d575385 100644 --- a/docs/guides/tutorials/cloudflare_workers.rst +++ b/docs/guides/tutorials/cloudflare_workers.rst @@ -7,17 +7,17 @@ Cloudflare Workers :edb-alt-title: Using Gel in Cloudflare Workers -This guide demonstrates how to integrate EdgeDB with Cloudflare Workers to -build serverless applications that can interact with EdgeDB. +This guide demonstrates how to integrate Gel with Cloudflare Workers to +build serverless applications that can interact with Gel. It covers the following: - Setting up a new Cloudflare Worker project -- Configuring EdgeDB -- Using EdgeDB in a Cloudflare Worker +- Configuring Gel +- Using Gel in a Cloudflare Worker - Deploying the Worker to Cloudflare -You can use this project as a reference: `EdgeDB Cloudflare Workers Example`_. +You can use this project as a reference: `Gel Cloudflare Workers Example`_. Prerequisites ------------- @@ -27,7 +27,7 @@ Prerequisites Ensure you have the following installed: - `Node.js`_ -- :ref:`EdgeDB CLI ` +- :ref:`Gel CLI ` .. _Sign up for a Cloudflare account: https://dash.cloudflare.com/sign-up .. _Node.js: https://nodejs.org/en/ @@ -66,16 +66,16 @@ project on Cloudflare, you can run ``npx wrangler deploy`` to push it. .. _Wrangler: https://developers.cloudflare.com/workers/cli-wrangler -Configure EdgeDB -================= +Configure Gel +============= -You can use `EdgeDB Cloud`_ for a managed service or run EdgeDB locally. +You can use `Gel Cloud`_ for a managed service or run Gel locally. -.. _`EdgeDB Cloud`: https://www.edgedb.com/cloud +.. _`Gel Cloud`: https://www.edgedb.com/cloud -**Local EdgeDB Setup (Optional for EdgeDB Cloud Users)** +**Local Gel Setup (Optional for Gel Cloud Users)** -If you're running EdgeDB locally, you can use the following command +If you're running Gel locally, you can use the following command to create a new instance: .. code-block:: bash @@ -85,7 +85,7 @@ to create a new instance: It creates an :code:`edgedb.toml` config file and a schema file :code:`dbschema/default.esdl`. -It also spins up an EdgeDB instance and associates it with the current +It also spins up an Gel instance and associates it with the current directory. As long as you're inside the project directory, all CLI commands will be executed against this instance. @@ -97,7 +97,7 @@ interactive REPL to your instance. $ edgedb -**Install the EdgeDB npm package** +**Install the Gel npm package** .. code-block:: bash @@ -125,20 +125,20 @@ Add new types to the schema file: } } -Then apply the schema schema to your EdgeDB instance: +Then apply the schema schema to your Gel instance: .. code-block:: bash $ edgedb migration create $ edgedb migrate -Using EdgeDB in a Cloudflare Worker -==================================== +Using Gel in a Cloudflare Worker +================================ Open the :code:`index.ts` file from the :code:`src` directory in your project, and remove the default code. -To interact with your **local EdgeDB instance**, use the following code: +To interact with your **local Gel instance**, use the following code: .. code-block:: typescript @@ -164,10 +164,10 @@ To interact with your **local EdgeDB instance**, use the following code: } satisfies ExportedHandler; -.. note:: EdgeDB DSN +.. note:: Gel DSN - Replace :code:`` with your EdgeDB DSN. - You can obtain your EdgeDB DSN from the command line by running: + Replace :code:`` with your Gel DSN. + You can obtain your Gel DSN from the command line by running: .. code-block:: bash @@ -176,12 +176,12 @@ To interact with your **local EdgeDB instance**, use the following code: .. note:: tlsSecurity The :code:`tlsSecurity` option is set to :code:`insecure` to allow - connections to a local EdgeDB instance. This lets you test your + connections to a local Gel instance. This lets you test your Cloudflare Worker locally. **Don't use this option in production.** -**Client Setup with EdgeDB Cloud** +**Client Setup with Gel Cloud** -If you're using EdgeDB Cloud, you can instead use the following code to +If you're using Gel Cloud, you can instead use the following code to set up the client: .. code-block:: typescript @@ -194,7 +194,7 @@ set up the client: .. note:: Environment variables You can obtain :code:`EDGEDB_INSTANCE` and :code:`EDGEDB_SECRET_KEY` - values from the EdgeDB Cloud dashboard. + values from the Gel Cloud dashboard. You will need to set the :code:`EDGEDB_INSTANCE` and :code:`EDGEDB_SECRET` environment variables in your Cloudflare Worker project. @@ -246,16 +246,16 @@ to access your worker. Wrapping up =========== -Congratulations! You have successfully integrated EdgeDB with +Congratulations! You have successfully integrated Gel with Cloudflare Workers. Here's a minimal starter project that you can use as a -reference: `EdgeDB Cloudflare Workers Example`_. +reference: `Gel Cloudflare Workers Example`_. Check out the `Cloudflare Workers documentation`_ for more information and to learn about the various features and capabilities of Cloudflare Workers. -.. _`EdgeDB Cloudflare Workers Example`: +.. _`Gel Cloudflare Workers Example`: https://github.com/edgedb/edgedb-examples/tree/main/cloudflare-workers .. _`Cloudflare Workers documentation`: https://developers.cloudflare.com/workers diff --git a/docs/guides/tutorials/graphql_apis_with_strawberry.rst b/docs/guides/tutorials/graphql_apis_with_strawberry.rst index aa32a608cbb..86e1c20be6f 100644 --- a/docs/guides/tutorials/graphql_apis_with_strawberry.rst +++ b/docs/guides/tutorials/graphql_apis_with_strawberry.rst @@ -11,8 +11,8 @@ creating backend-less applications where the users will directly communicate with the database. You can learn more about that in the :ref:`GraphQL ` section of the docs. -However, as of now, EdgeDB is not ready to be used as a standalone backend. You -shouldn't expose your EdgeDB instance directly to the application's frontend; +However, as of now, Gel is not ready to be used as a standalone backend. You +shouldn't expose your Gel instance directly to the application's frontend; this is insecure and will give all users full read/write access to your database. So, in this tutorial, we'll see how you can quickly create a simple GraphQL API without using the built-in extension, which will give the users @@ -28,9 +28,9 @@ and expose the objects and relationships as a GraphQL API. Using the GraphQL interface, you'll be able to fetch, create, update, and delete movie and actor objects in the database. `Strawberry `_ is a Python library that takes a code-first approach where you'll write your object schema -as Python classes. This allows us to focus more on how you can integrate EdgeDB +as Python classes. This allows us to focus more on how you can integrate Gel into your workflow and less on the idiosyncrasies of GraphQL itself. We'll also -use the EdgeDB client to communicate with the database, +use the Gel client to communicate with the database, `FastAPI `_ to build the authentication layer, and Uvicorn as the webserver. @@ -69,22 +69,22 @@ dependencies with this command: Initialize the database ^^^^^^^^^^^^^^^^^^^^^^^ -Now, let's initialize an EdgeDB project. From the project's root directory: +Now, let's initialize an Gel project. From the project's root directory: .. code-block:: bash $ edgedb project init Initializing project... - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: strawberry_crud]: > strawberry_crud Do you want to start instance automatically on login? [y/n] > y - Checking EdgeDB versions... + Checking Gel versions... -Once you've answered the prompts, a new EdgeDB instance called +Once you've answered the prompts, a new Gel instance called ``strawberry_crud`` will be created and started. @@ -102,7 +102,7 @@ similar to this: :: - EdgeDB 2.x (repl 2.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> @@ -161,7 +161,7 @@ This is how our datatypes look: Here, we've defined an ``abstract`` type called ``Auditable`` to take advantage -of EdgeDB's schema mixin system. This allows us to add a ``created_at`` +of Gel's schema mixin system. This allows us to add a ``created_at`` property to multiple types without repeating ourselves. The ``Actor`` type extends ``Auditable`` and inherits the ``created_at`` @@ -200,7 +200,7 @@ layer, and exposes the API to the webserver. Write the GraphQL schema ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Along with the database schema, to expose EdgeDB's object relational model as a +Along with the database schema, to expose Gel's object relational model as a GraphQL API, you'll also have to define a GraphQL schema that mirrors the object structure in the database. Strawberry allows us to express this schema via type annotated Python classes. We define the Strawberry schema in the @@ -233,7 +233,7 @@ via type annotated Python classes. We define the Strawberry schema in the actors: list[Actor] | None = None Here, the GraphQL schema mimics our database schema. Similar to the ``Actor`` -and ``Movie`` types in the EdgeDB schema, here, both the ``Actor`` and +and ``Movie`` types in the Gel schema, here, both the ``Actor`` and ``Movie`` models have three attributes. Likewise, the ``actors`` attribute in the ``Movie`` model represents the link between movies and actors. @@ -290,7 +290,7 @@ is built in the ``schema.py`` file as follows: Here, the ``get_actors`` resolver method accepts an optional ``filter_name`` parameter and returns a list of ``Actor`` type objects. The optional ``filter_name`` parameter allows us to build the capability of filtering the -actor objects by name. Inside the method, we use the EdgeDB client to +actor objects by name. Inside the method, we use the Gel client to asynchronously query the data. The ``client.query_json`` method returns JSON serialized data which we use to create the ``Actor`` instances. Finally, we return the list of actor instances and the rest of the work is done by @@ -738,7 +738,7 @@ more insights into the implementation details of those mutations. Conclusion ========== -In this tutorial, you've seen how can use Strawberry and EdgeDB together to +In this tutorial, you've seen how can use Strawberry and Gel together to quickly build a fully-featured GraphQL API. Also, you have seen how FastAPI allows you add an authentication layer and serve the API in a secure manner. One thing to keep in mind here is, ideally, you'd only use GraphQL if you're diff --git a/docs/guides/tutorials/index.rst b/docs/guides/tutorials/index.rst index 6b7ce2f1a8d..ddfa22782a1 100644 --- a/docs/guides/tutorials/index.rst +++ b/docs/guides/tutorials/index.rst @@ -1,9 +1,9 @@ .. _ref_guide_tutorials: -==================== -Using EdgeDB with... -==================== +================= +Using Gel with... +================= .. toctree:: :maxdepth: 1 diff --git a/docs/guides/tutorials/jupyter_notebook.rst b/docs/guides/tutorials/jupyter_notebook.rst index c7bbeb64010..36e1b87d471 100644 --- a/docs/guides/tutorials/jupyter_notebook.rst +++ b/docs/guides/tutorials/jupyter_notebook.rst @@ -9,13 +9,13 @@ Jupyter Notebook 1. `Install Jupyter Notebook `__ -2. Install the EdgeDB Python library with ``pip install edgedb`` +2. Install the Gel Python library with ``pip install edgedb`` 3. Set the appropriate `connection environment variables `__ required for your - EdgeDB instance + Gel instance - **For EdgeDB Cloud instances** + **For Gel Cloud instances** - ``EDGEDB_INSTANCE``- your instance name (``/``) - ``EDGEDB_SECRET_KEY``- a secret key with permissions for the selected instance. @@ -23,7 +23,7 @@ Jupyter Notebook .. note:: You may create a secret key with the CLI by running ``edgedb cloud - secretkey create`` or in the `EdgeDB Cloud UI + secretkey create`` or in the `Gel Cloud UI `__. **For other remote instances** @@ -34,18 +34,18 @@ Jupyter Notebook DSNs take the following format: ``edgedb://:@:/``. - Omit any segment, and EdgeDB will fall back to a default value listed + Omit any segment, and Gel will fall back to a default value listed in `our DSN specification `__ - **For local EdgeDB instances** + **For local Gel instances** - ``EDGEDB_INSTANCE``- your instance name - ``EDGEDB_USER`` & ``EDGEDB_PASSWORD`` .. note :: Usernames and passwords - EdgeDB creates an ``edgedb`` user by default, but the password is + Gel creates an ``edgedb`` user by default, but the password is randomized. You may set the password for this role by running ``alter role edgedb { set password := ''; };`` or you may create a new role using ``create superuser role { set password := ''; @@ -56,7 +56,7 @@ Jupyter Notebook 5. Create a new notebook. -6. In one of your notebook's blocks, import the EdgeDB library and run a query. +6. In one of your notebook's blocks, import the Gel library and run a query. .. code-block:: python diff --git a/docs/guides/tutorials/nextjs_app_router.rst b/docs/guides/tutorials/nextjs_app_router.rst index 0cdb309ab30..ba25c8032ae 100644 --- a/docs/guides/tutorials/nextjs_app_router.rst +++ b/docs/guides/tutorials/nextjs_app_router.rst @@ -5,10 +5,10 @@ Next.js (App Router) ==================== :edb-alt-title: Building a simple blog application with - EdgeDB and Next.js (App Router) + Gel and Next.js (App Router) We're going to build a simple blog application with -`Next.js `_ and EdgeDB. Let's start by scaffolding our +`Next.js `_ and Gel. Let's start by scaffolding our app with Next.js's ``create-next-app`` tool. You'll be prompted to provide a name (we'll use ``nextjs-blog``) for your @@ -64,7 +64,7 @@ rendering/server-components>`_ which lets you integrate server-side logic directly into your React components. Server Components are executed on the server and can fetch data from a database or an API. We'll use this feature to load blog -posts from an EdgeDB database. +posts from an Gel database. Updating the homepage --------------------- @@ -88,11 +88,11 @@ static data. Replace the contents of ``app/page.tsx`` with the following. { id: 'post1', title: 'This one weird trick makes using databases fun', - content: 'Use EdgeDB', + content: 'Use Gel', }, { id: 'post2', - title: 'How to build a blog with EdgeDB and Next.js', + title: 'How to build a blog with Gel and Next.js', content: "Let's start by scaffolding our app with `create-next-app`.", }, ] @@ -124,14 +124,14 @@ After saving, you can refresh the page to see the blog posts. Clicking on a post title will take you to a page that doesn't exist yet. We'll create that page later in the tutorial. -Initializing EdgeDB -------------------- +Initializing Gel +---------------- Now let's spin up a database for the app. You have two options to initialize -an EdgeDB project: using ``npx edgedb`` without installing the CLI, or +an Gel project: using ``npx edgedb`` without installing the CLI, or installing the edgedb CLI directly. In this tutorial, we'll use the first option. If you prefer to install the CLI, see the -`EdgeDB CLI installation guide `_ +`Gel CLI installation guide `_ for more information. From the application's root directory, run the following command: @@ -142,11 +142,11 @@ From the application's root directory, run the following command: No `edgedb.toml` found in `~/nextjs-blog` or above Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project [default: + Specify the name of Gel instance to use with this project [default: nextjs_blog]: > nextjs_blog - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: x.x]: + Checking Gel versions... + Specify the version of Gel to use with this project [default: x.x]: > ┌─────────────────────┬──────────────────────────────────────────────┐ │ Project directory │ ~/nextjs-blog │ @@ -157,12 +157,12 @@ From the application's root directory, run the following command: │ Version │ x.x │ │ Instance name │ nextjs_blog │ └─────────────────────┴──────────────────────────────────────────────┘ - Initializing EdgeDB instance... + Initializing Gel instance... Applying migrations... Everything is up to date. Revision initial. Project initialized. -This process has spun up an EdgeDB instance called ``nextjs_blog`` and +This process has spun up an Gel instance called ``nextjs_blog`` and associated it with your current directory. As long as you're inside that directory, CLI commands and client libraries will be able to connect to the linked instance automatically, without additional configuration. @@ -172,7 +172,7 @@ To test this, run the ``edgedb`` command to open a REPL to the linked instance. .. code-block:: bash $ edgedb - EdgeDB x.x (repl x.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> select 2 + 2; {4} @@ -184,7 +184,7 @@ change that. The project initialization process also created a new subdirectory in our project called ``dbschema``. This is folder that contains everything -pertaining to EdgeDB. Currently it looks like this: +pertaining to Gel. Currently it looks like this: .. code-block:: @@ -210,7 +210,7 @@ update the contents of ``default.esdl`` with the following simple blog schema. .. note:: - EdgeDB lets you split up your schema into different ``modules`` but it's + Gel lets you split up your schema into different ``modules`` but it's common to keep your entire schema in the ``default`` module. Save the file, then let's create our first migration. @@ -238,7 +238,7 @@ the REPL. .. code-block:: bash $ edgedb - EdgeDB 4.x (repl 4.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> @@ -249,11 +249,11 @@ Then execute the following ``insert`` statements. edgedb> insert BlogPost { ....... title := "This one weird trick makes using databases fun", - ....... content := "Use EdgeDB" + ....... content := "Use Gel" ....... }; {default::BlogPost {id: 7f301d02-c780-11ec-8a1a-a34776e884a0}} edgedb> insert BlogPost { - ....... title := "How to build a blog with EdgeDB and Next.js", + ....... title := "How to build a blog with Gel and Next.js", ....... content := "Let's start by scaffolding our app..." ....... }; {default::BlogPost {id: 88c800e6-c780-11ec-8a1a-b3a3020189dd}} @@ -275,7 +275,7 @@ NPM: Then go to the ``app/page.tsx`` file to replace the static data with the blogposts fetched from the database. -To fetch these from the homepage, we'll create an EdgeDB client and use the +To fetch these from the homepage, we'll create an Gel client and use the ``.query()`` method to fetch all the posts in the database with a ``select`` statement. @@ -297,11 +297,11 @@ To fetch these from the homepage, we'll create an EdgeDB client and use the - { - id: 'post1', - title: 'This one weird trick makes using databases fun', - - content: 'Use EdgeDB', + - content: 'Use Gel', - }, - { - id: 'post2', - - title: 'How to build a blog with EdgeDB and Next.js', + - title: 'How to build a blog with Gel and Next.js', - content: "Start by scaffolding our app with `create-next-app`.", - }, - ] @@ -339,7 +339,7 @@ When you refresh the page, you should see the blog posts. Generating the query builder ---------------------------- -Since we're using TypeScript, it makes sense to use EdgeDB's powerful query +Since we're using TypeScript, it makes sense to use Gel's powerful query builder. This provides a schema-aware client API that makes writing strongly typed EdgeQL queries easy and painless. The result type of our queries will be automatically inferred, so we won't need to manually type something like @@ -507,21 +507,21 @@ you to ``/post/``. Deploying to Vercel ------------------- -You can deploy an EdgeDB instance on the EdgeDB Cloud or +You can deploy an Gel instance on the Gel Cloud or on your preferred cloud provider. We'll cover both options here. -With EdgeDB Cloud -================= +With Gel Cloud +============== -**#1 Deploy EdgeDB** +**#1 Deploy Gel** First, sign up for an account at `cloud.edgedb.com `_ and create a new instance. -Create and make note of a secret key for your EdgeDB Cloud instance. You -can create a new secret key from the "Secret Keys" tab in the EdgeDB Cloud +Create and make note of a secret key for your Gel Cloud instance. You +can create a new secret key from the "Secret Keys" tab in the Gel Cloud console. We'll need this later to connect to the database from Vercel. -Run the following command to migrate the project to the EdgeDB Cloud: +Run the following command to migrate the project to the Gel Cloud: .. code-block:: bash @@ -575,7 +575,7 @@ this app to Vercel with the button below. In "Configure Project," expand "Environment Variables" to add two variables: -- ``EDGEDB_INSTANCE`` containing your EdgeDB Cloud instance name (in +- ``EDGEDB_INSTANCE`` containing your Gel Cloud instance name (in ``/`` format) - ``EDGEDB_SECRET_KEY`` containing the secret key you created and noted previously. @@ -588,9 +588,9 @@ supplied by Vercel. With other cloud providers =========================== -**#1 Deploy EdgeDB** +**#1 Deploy Gel** -Check out the following guides for deploying EdgeDB to your preferred cloud +Check out the following guides for deploying Gel to your preferred cloud provider: - `AWS `_ @@ -617,7 +617,7 @@ Use the DSN to apply migrations against your remote instance. .. note:: - You have to disable TLS checks with ``--tls-security insecure``. All EdgeDB + You have to disable TLS checks with ``--tls-security insecure``. All Gel instances use TLS by default, but configuring it is out of scope of this project. @@ -627,7 +627,7 @@ database. Open a REPL and ``insert`` some blog posts: .. code-block:: bash $ npx edgedb --dsn --tls-security insecure - EdgeDB x.x (repl x.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> insert BlogPost { title := "Test post" }; {default::BlogPost {id: c00f2c9a-cbf5-11ec-8ecb-4f8e702e5789}} @@ -668,7 +668,7 @@ When prompted: - Set ``EDGEDB_DSN`` to your database's DSN - Set ``EDGEDB_CLIENT_TLS_SECURITY`` to ``insecure``. This will disable - EdgeDB's default TLS checks; configuring TLS is beyond the scope of this + Gel's default TLS checks; configuring TLS is beyond the scope of this tutorial. .. image:: @@ -685,14 +685,14 @@ supplied by Vercel. Wrapping up ----------- -This tutorial demonstrates how to work with EdgeDB in a +This tutorial demonstrates how to work with Gel in a Next.js app, using the App Router. We've created a simple blog application that loads posts from a database and displays them on the homepage. We've also created a dynamic route that fetches a single post from the database and displays it on a separate page. The next step is to add a ``/newpost`` page with a form for writing new blog -posts and saving them into EdgeDB. That's left as an exercise for the reader. +posts and saving them into Gel. That's left as an exercise for the reader. To see the final code for this tutorial, refer to `github.com/edgedb/edgedb-examples/tree/main/nextjs-blog diff --git a/docs/guides/tutorials/nextjs_pages_router.rst b/docs/guides/tutorials/nextjs_pages_router.rst index 5a94ef49b1d..5cbfdc222d7 100644 --- a/docs/guides/tutorials/nextjs_pages_router.rst +++ b/docs/guides/tutorials/nextjs_pages_router.rst @@ -8,7 +8,7 @@ Next.js (Pages Router) Next.js (Pages Router) We're going to build a simple blog application with -`Next.js `_ and EdgeDB. Let's start by scaffolding our +`Next.js `_ and Gel. Let's start by scaffolding our app with Next.js's ``create-next-app`` tool. We'll be using TypeScript for this tutorial. @@ -77,11 +77,11 @@ static data. Replace the contents of ``pages/index.tsx`` with the following. { id: 'post1', title: 'This one weird trick makes using databases fun', - content: 'Use EdgeDB', + content: 'Use Gel', }, { id: 'post2', - title: 'How to build a blog with EdgeDB and Next.js', + title: 'How to build a blog with Gel and Next.js', content: "Let's start by scaffolding our app with `create-next-app`.", }, ]; @@ -122,14 +122,14 @@ something like this. :alt: Basic blog homepage with static content :width: 100% -Initializing EdgeDB -------------------- +Initializing Gel +---------------- Now let's spin up a database for the app. You have two options to initialize -an EdgeDB project: using ``npx edgedb`` without installing the CLI, or +an Gel project: using ``npx edgedb`` without installing the CLI, or installing the edgedb CLI directly. In this tutorial, we'll use the first option. If you prefer to install the CLI, see the -`EdgeDB CLI installation guide `_ +`Gel CLI installation guide `_ for more information. From the application's root directory, run the following command: @@ -140,11 +140,11 @@ From the application's root directory, run the following command: No `edgedb.toml` found in `~/nextjs-blog` or above Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project [default: + Specify the name of Gel instance to use with this project [default: nextjs_blog]: > nextjs_blog - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: x.x]: + Checking Gel versions... + Specify the version of Gel to use with this project [default: x.x]: > ┌─────────────────────┬──────────────────────────────────────────────┐ │ Project directory │ ~/nextjs-blog │ @@ -155,12 +155,12 @@ From the application's root directory, run the following command: │ Version │ x.x │ │ Instance name │ nextjs_blog │ └─────────────────────┴──────────────────────────────────────────────┘ - Initializing EdgeDB instance... + Initializing Gel instance... Applying migrations... Everything is up to date. Revision initial. Project initialized. -This process has spun up an EdgeDB instance called ``nextjs-blog`` and +This process has spun up an Gel instance called ``nextjs-blog`` and "linked" it with your current directory. As long as you're inside that directory, CLI commands and client libraries will be able to connect to the linked instance automatically, without additional configuration. @@ -170,7 +170,7 @@ To test this, run the ``edgedb`` command to open a REPL to the linked instance. .. code-block:: bash $ edgedb - EdgeDB 2.x (repl 2.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> select 2 + 2; {4} @@ -182,7 +182,7 @@ change that. The project initialization process also created a new subdirectory in our project called ``dbschema``. This is folder that contains everything -pertaining to EdgeDB. Currently it looks like this: +pertaining to Gel. Currently it looks like this: .. code-block:: @@ -209,7 +209,7 @@ update the contents of ``default.esdl`` with the following simple blog schema. .. note:: - EdgeDB lets you split up your schema into different ``modules`` but it's + Gel lets you split up your schema into different ``modules`` but it's common to keep your entire schema in the ``default`` module. Save the file, then let's create our first migration. @@ -237,7 +237,7 @@ the REPL. .. code-block:: bash $ edgedb - EdgeDB 2.x (repl 2.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> @@ -248,11 +248,11 @@ Then execute the following ``insert`` statements. edgedb> insert BlogPost { ....... title := "This one weird trick makes using databases fun", - ....... content := "Use EdgeDB" + ....... content := "Use Gel" ....... }; {default::BlogPost {id: 7f301d02-c780-11ec-8a1a-a34776e884a0}} edgedb> insert BlogPost { - ....... title := "How to build a blog with EdgeDB and Next.js", + ....... title := "How to build a blog with Gel and Next.js", ....... content := "Let's start by scaffolding our app..." ....... }; {default::BlogPost {id: 88c800e6-c780-11ec-8a1a-b3a3020189dd}} @@ -293,7 +293,7 @@ Then create a new file at ``pages/api/post.ts`` and copy in the following code. res.status(200).json(posts); } -This file initializes an EdgeDB client, which manages a pool of connections to +This file initializes an Gel client, which manages a pool of connections to the database and provides an API for executing queries. We're using the ``.query()`` method to fetch all the posts in the database with a simple ``select`` statement. @@ -322,11 +322,11 @@ the built-in ``fetch`` API. At the top of the ``HomePage`` component in - { - id: 'post1', - title: 'This one weird trick makes using databases fun', - - content: 'Use EdgeDB', + - content: 'Use Gel', - }, - { - id: 'post2', - - title: 'How to build a blog with EdgeDB and Next.js', + - title: 'How to build a blog with Gel and Next.js', - content: "Let's start by scaffolding our app...", - }, - ]; @@ -348,7 +348,7 @@ before the homepage renders the (dynamically loaded!) blog posts. Generating the query builder ---------------------------- -Since we're using TypeScript, it makes sense to use EdgeDB's powerful query +Since we're using TypeScript, it makes sense to use Gel's powerful query builder. This provides a schema-aware client API that makes writing strongly typed EdgeQL queries easy and painless. The result type of our queries will be automatically inferred, so we won't need to manually type something like @@ -545,9 +545,9 @@ you to ``/post/``, which should display something like this: Deploying to Vercel ------------------- -**#1 Deploy EdgeDB** +**#1 Deploy Gel** -First deploy an EdgeDB instance on your preferred cloud provider: +First deploy an Gel instance on your preferred cloud provider: - :ref:`AWS ` - :ref:`Azure ` @@ -577,7 +577,7 @@ Use the DSN to apply migrations against your remote instance. .. note:: - You have to disable TLS checks with ``--tls-security insecure``. All EdgeDB + You have to disable TLS checks with ``--tls-security insecure``. All Gel instances use TLS by default, but configuring it is out of scope of this project. @@ -587,7 +587,7 @@ database. Open a REPL and ``insert`` some blog posts: .. code-block:: bash $ npx edgedb --dsn --tls-security insecure - EdgeDB 2.x (repl 2.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> insert BlogPost { title := "Test post" }; {default::BlogPost {id: c00f2c9a-cbf5-11ec-8ecb-4f8e702e5789}} @@ -628,7 +628,7 @@ When prompted: - Set ``EDGEDB_DSN`` to your database's DSN - Set ``EDGEDB_CLIENT_TLS_SECURITY`` to ``insecure``. This will disable - EdgeDB's default TLS checks; configuring TLS is beyond the scope of this + Gel's default TLS checks; configuring TLS is beyond the scope of this tutorial. .. image:: @@ -646,12 +646,12 @@ Wrapping up ----------- Admittedly this isn't the prettiest blog of all time, or the most -feature-complete. But this tutorial demonstrates how to work with EdgeDB in a +feature-complete. But this tutorial demonstrates how to work with Gel in a Next.js app, including data fetching with API routes and ``getServerSideProps``. The next step is to add a ``/newpost`` page with a form for writing new blog -posts and saving them into EdgeDB. That's left as an exercise for the reader. +posts and saving them into Gel. That's left as an exercise for the reader. To see the final code for this tutorial, refer to `github.com/edgedb/edgedb-examples/tree/main/nextjs-blog diff --git a/docs/guides/tutorials/phoenix_github_oauth.rst b/docs/guides/tutorials/phoenix_github_oauth.rst index ae17abeb3a7..a7498c1d827 100644 --- a/docs/guides/tutorials/phoenix_github_oauth.rst +++ b/docs/guides/tutorials/phoenix_github_oauth.rst @@ -9,15 +9,15 @@ Phoenix In this tutorial, we'll look at how you can create an application with authorization through GitHub using -`Phoenix `_ and `the official EdgeDB Elixir +`Phoenix `_ and `the official Gel Elixir driver `_. This tutorial is a simplified version of the `LiveBeats `_ application from -`fly.io `_ with EdgeDB instead of PostgreSQL, which focuses on +`fly.io `_ with Gel instead of PostgreSQL, which focuses on implementing authorization via GitHub. The completed implementation of this example can be found `on GitHub `_. The full version -of LiveBeats version on EdgeDB can also be found `on GitHub +of LiveBeats version on Gel can also be found `on GitHub `_ .. _repository: @@ -30,7 +30,7 @@ Prerequisites For this tutorial we will need: -* EdgeDB CLI. +* Gel CLI. * Elixir version 1.13 or higher. * Phoenix framework version 1.6 or higher. * `GitHub OAuth application `_. @@ -41,7 +41,7 @@ For this tutorial we will need: Before discussing the project database schema, let's generate a skeleton for our application. We will make sure that it will use binary IDs for the Ecto -schemas because EdgeDB uses UUIDs as primary IDs, which in Elixir are +schemas because Gel uses UUIDs as primary IDs, which in Elixir are represented as strings, and since it is basically a plain JSON API application, we will disable all the built-in Phoenix integrations. @@ -60,7 +60,7 @@ won't be used by us. $ # because they will not be used $ rm -r lib/github_oauth/repo.ex priv/repo/ -And then add the EdgeDB driver, the ``Ecto`` helper for it and the ``Mint`` +And then add the Gel driver, the ``Ecto`` helper for it and the ``Mint`` HTTP client for GitHub OAuth client as project dependencies to ``mix.exs``. .. code-block:: elixir @@ -93,14 +93,14 @@ Now we need to download new dependencies. $ mix deps.get Next, we will create a module in ``lib/github_oauth/edgedb.ex`` which will -define a child specification for the EdgeDB driver and use the ``EdgeDBEcto`` +define a child specification for the Gel driver and use the ``GelEcto`` helper, which will inspect the queries that will be stored in the ``priv/edgeql/`` directory and generate Elixir code for them. .. code-block:: elixir - defmodule GitHubOAuth.EdgeDB do - use EdgeDBEcto, + defmodule GitHubOAuth.Gel do + use GelEcto, name: __MODULE__, queries: true, otp_app: :github_oauth @@ -108,12 +108,12 @@ helper, which will inspect the queries that will be stored in the def child_spec(_opts \\ []) do %{ id: __MODULE__, - start: {EdgeDB, :start_link, [[name: __MODULE__]]} + start: {Gel, :start_link, [[name: __MODULE__]]} } end end -Now we need to add ``GitHubOAuth.EdgeDB`` as a child for our application in +Now we need to add ``GitHubOAuth.Gel`` as a child for our application in ``lib/github_oauth/application.ex`` (at the same time removing the child definition for ``Ecto.Repo`` from there). @@ -125,8 +125,8 @@ definition for ``Ecto.Repo`` from there). @impl true def start(_type, _args) do children = [ - # Start the EdgeDB driver - GitHubOAuth.EdgeDB, + # Start the Gel driver + GitHubOAuth.Gel, # Start the Telemetry supervisor GitHubOAuthWeb.Telemetry, # Start the PubSub system @@ -144,7 +144,7 @@ definition for ``Ecto.Repo`` from there). end -Now we are ready to start working with EdgeDB! First, let's initialize a new +Now we are ready to start working with Gel! First, let's initialize a new project for this application. .. code-block:: bash @@ -155,13 +155,13 @@ project for this application. Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: phoenix_github_oauth]: > github_oauth - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: 2.x]: - > 2.x + Checking Gel versions... + Specify the version of Gel to use with this project [default: x.x]: + > x.x Do you want to start instance automatically on login? [y/n] > y @@ -176,7 +176,7 @@ This application will have 2 types: ``User`` and ``Identity``. The represents the way the user logs in to the application (in this example via GitHub OAuth). -This schema will be stored in a single EdgeDB module inside the +This schema will be stored in a single Gel module inside the ``dbschema/default.esdl`` file. .. code-block:: sdl @@ -262,7 +262,7 @@ Here is the definition for the user in the ``lib/accounts/user.ex`` file. defmodule GitHubOAuth.Accounts.User do use Ecto.Schema - use EdgeDBEcto.Mapper + use GelEcto.Mapper alias GitHubOAuth.Accounts.Identity @@ -287,7 +287,7 @@ And here for identity in ``lib/accounts/identity.ex``. defmodule GitHubOAuth.Accounts.Identity do use Ecto.Schema - use EdgeDBEcto.Mapper + use GelEcto.Mapper alias GitHubOAuth.Accounts.User @@ -312,12 +312,12 @@ User authentication via GitHub ================================== This part will be pretty big, as we'll talk about using ``Ecto.Changeset`` -with the EdgeDB driver, as well as modules and queries related to user +with the Gel driver, as well as modules and queries related to user registration via GitHub OAuth. ``Ecto`` provides "changesets" (via ``Ecto.Changeset``), which are convenient to use when working with ``Ecto.Schema`` to validate external parameters. We -could use them via ``EdgeDBEcto`` instead, though not quite as fully as we can +could use them via ``GelEcto`` instead, though not quite as fully as we can with the full-featured adapters for ``Ecto``. First, we will update the ``GitHubOAuth.Accounts.Identity`` module so that it @@ -466,7 +466,7 @@ itself will be used by Phoenix controllers. alias GitHubOAuth.Accounts.{User, Identity} def get_user(id) do - GitHubOAuth.EdgeDB.Accounts.get_user_by_id(id: id) + GitHubOAuth.Gel.Accounts.get_user_by_id(id: id) end def register_github_user(primary_email, info, emails, token) do @@ -475,15 +475,15 @@ itself will be used by Phoenix controllers. else info |> User.github_registration_changeset(primary_email, emails, token) - |> EdgeDBEcto.insert( - &GitHubOAuth.EdgeDB.Accounts.register_github_user/1, + |> GelEcto.insert( + &GitHubOAuth.Gel.Accounts.register_github_user/1, nested: true ) end end def get_user_by_provider(provider, email) when provider in [:github] do - GitHubOAuth.EdgeDB.Accounts.get_user_by_provider( + GitHubOAuth.Gel.Accounts.get_user_by_provider( provider: to_string(provider), email: String.downcase(email) ) @@ -491,7 +491,7 @@ itself will be used by Phoenix controllers. defp update_github_token(%User{} = user, new_token) do identity = - GitHubOAuth.EdgeDB.Accounts.get_identity_for_user( + GitHubOAuth.Gel.Accounts.get_identity_for_user( user_id: user.id, provider: "github" ) @@ -500,8 +500,8 @@ itself will be used by Phoenix controllers. identity |> change() |> put_change(:provider_token, new_token) - |> EdgeDBEcto.update( - &GitHubOAuth.EdgeDB.Accounts.update_identity_token/1 + |> GelEcto.update( + &GitHubOAuth.Gel.Accounts.update_identity_token/1 ) identity = %Identity{identity | provider_token: new_token} @@ -542,13 +542,13 @@ which defines a query to find an user with a specified email provider. It is worth noting the ``# edgedb = :query_single!`` and ``# mapper = GitHubOAuth.Accounts.User`` comments. Both are special comments -that will be used by ``EdgeDBEcto`` when generating query functions. The +that will be used by ``GelEcto`` when generating query functions. The ``edgedb`` comment defines the driver function for requesting data. Information on all supported features can be found in the driver -`documentation `_. +`documentation `_. The ``mapper`` comment is used to define the module that will be used to map -the result from EdgeDB to some other form. Our ``Ecto.Schema`` schemas support -this with ``use EdgeDBEcto.Mapper`` expression at the top of the module +the result from Gel to some other form. Our ``Ecto.Schema`` schemas support +this with ``use GelEcto.Mapper`` expression at the top of the module definition. The queries for `getting the identity `_ and @@ -586,7 +586,7 @@ separate parameters such as ``$id`` and ``$provider_token``. This is because to update our identity we use the changeset in the module ``GitHubOAuth.Accounts``, which automatically monitors changes to the schema and will not give back the parameters, which will not affect the state of the -schema in update. So ``EdgeDBEcto`` automatically converts data from +schema in update. So ``GelEcto`` automatically converts data from changesets when it is an update or insert operation into a named ``$params`` parameter of type JSON. It also helps to work with nested changesets, as we will see in the next query, which is defined in the diff --git a/docs/guides/tutorials/rest_apis_with_fastapi.rst b/docs/guides/tutorials/rest_apis_with_fastapi.rst index d1a12ea41a5..144b67b6b26 100644 --- a/docs/guides/tutorials/rest_apis_with_fastapi.rst +++ b/docs/guides/tutorials/rest_apis_with_fastapi.rst @@ -7,14 +7,14 @@ FastAPI :edb-alt-title: Building a REST API with Gel and FastAPI Because FastAPI encourages and facilitates strong typing, it's a natural -pairing with EdgeDB. Our Python code generation generates not only typed +pairing with Gel. Our Python code generation generates not only typed query functions but result types you can use to annotate your endpoint handler functions. EdgeDB can help you quickly build REST APIs in Python without getting into the rigmarole of using ORM libraries to handle your data effectively. Here, we'll be using `FastAPI `_ to expose the API endpoints -and EdgeDB to store the content. +and Gel to store the content. We'll build a simple event management system where you'll be able to fetch, create, update, and delete *events* and *event hosts* via RESTful API @@ -35,7 +35,7 @@ database more efficiently. You can use newer versions of Python if you prefer, but you may need to adjust the code accordingly. If you want to skip ahead, the completed source code for this API can be found `in our examples repo `_. If you -want to check out an example with EdgeDB Auth, you can find that in the same +want to check out an example with Gel Auth, you can find that in the same repo in the `fastapi-crud-auth folder `_. @@ -83,7 +83,7 @@ note for help with Windows): Initialize the database ^^^^^^^^^^^^^^^^^^^^^^^ -Now, let's initialize an EdgeDB project. From the project's root directory: +Now, let's initialize an Gel project. From the project's root directory: .. code-block:: bash @@ -91,14 +91,14 @@ Now, let's initialize an EdgeDB project. From the project's root directory: No `edgedb.toml` found in `` or above Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project [default: + Specify the name of Gel instance to use with this project [default: fastapi_crud]: > fastapi_crud - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: 2.7]: + Checking Gel versions... + Specify the version of Gel to use with this project [default: 2.7]: > 2.7 -Once you've answered the prompts, a new EdgeDB instance called ``fastapi_crud`` +Once you've answered the prompts, a new Gel instance called ``fastapi_crud`` will be created and started. If you see ``Project initialized``, you're ready. @@ -116,7 +116,7 @@ database instance: :: - EdgeDB 2.x (repl 2.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> @@ -133,7 +133,7 @@ and delete the entities while maintaining their relationships. EdgeDB allows us to declaratively define the structure of the entities. If you've worked with SQLAlchemy or Django ORM, you might refer to these -declarative schema definitions as *models*. In EdgeDB we call them +declarative schema definitions as *models*. In Gel we call them "object types". The schema lives inside ``.esdl`` files in the ``dbschema`` directory. It's @@ -172,7 +172,7 @@ datatypes look like: } Here, we've defined an ``abstract`` type called ``Auditable`` to take advantage -of EdgeDB's schema mixin system. This allows us to add a ``created_at`` +of Gel's schema mixin system. This allows us to add a ``created_at`` property to multiple types without repeating ourselves. Abstract types don't have any concrete footprints in the database, as they don't hold any actual data. Their only job is to propagate properties, links, and constraints @@ -211,7 +211,7 @@ Now run the migration we just created. Once this is done, you'll see ``Applied`` along with the migration's ID. I like to go one step further in verifying success and see the schema applied to my -database. To do that, first fire up the EdgeDB console: +database. To do that, first fire up the Gel console: .. code-block:: bash @@ -281,7 +281,7 @@ simple text files containing the queries we want our app to be able to run. The code generator will search through our project for all files with the ``.edgeql`` extension and generate those functions for us as individual Python -modules. When you installed the EdgeDB client (via ``pip install edgedb``), the +modules. When you installed the Gel client (via ``pip install edgedb``), the code generator was installed alongside it, so you're already ready to go. We just need to write those queries! @@ -311,7 +311,7 @@ Save that file and get ready to kick off the magic that is code generation! 🪄 .. code-block:: bash $ edgedb-py - Found EdgeDB project: + Found Gel project: Processing /app/queries/get_user_by_name.edgeql Processing /app/queries/get_users.edgeql Generating /app/queries/get_user_by_name.py @@ -369,7 +369,7 @@ We've imported the generated code and aliased it (using ``as ``) to make the module names we use in our code a bit neater. The ``APIRouter`` instance does the actual work of exposing the API. We also -create an async EdgeDB client instance to communicate with the database. +create an async Gel client instance to communicate with the database. By default, this API will return a list of all users, but you can also filter the user objects by name. We have the ``RequestData`` class to handle the data @@ -1170,18 +1170,18 @@ payload: You can do the same to test ``DELETE /events``, just make sure you give it whatever name you set for the event in your previous test of the PUT method. -Integrating EdgeDB Auth -======================= +Integrating Gel Auth +==================== EdgeDB Auth provides a built-in authentication solution that is deeply -integrated with the EdgeDB server. This section outlines how to enable and -configure EdgeDB Auth in your application schema, manage authentication +integrated with the Gel server. This section outlines how to enable and +configure Gel Auth in your application schema, manage authentication providers, and set key configuration parameters. -Setting up EdgeDB Auth -^^^^^^^^^^^^^^^^^^^^^^^ +Setting up Gel Auth +^^^^^^^^^^^^^^^^^^^ -To start using EdgeDB Auth, you must first enable it in your schema. Add the +To start using Gel Auth, you must first enable it in your schema. Add the following to your schema definition: .. code-block:: sdl @@ -1197,13 +1197,13 @@ schema. $ edgedb migrate -Configuring EdgeDB Auth ------------------------ +Configuring Gel Auth +-------------------- -The configuration of EdgeDB Auth involves setting various parameters to secure +The configuration of Gel Auth involves setting various parameters to secure and tailor authentication to your needs. For now, we'll focus on the essential parameters to get started. You can configure these settings through a Python -script, which is recommended for scalability, or you can use the EdgeDB UI for +script, which is recommended for scalability, or you can use the Gel UI for a more user-friendly approach. **Auth Signing Key** @@ -1226,7 +1226,7 @@ Using Python: import secrets print(secrets.token_urlsafe(32)) -Once you have generated your key, configure it in EdgeDB like this: +Once you have generated your key, configure it in Gel like this: .. code-block:: edgeql @@ -1253,8 +1253,8 @@ To configure this in your application: Enabling authentication providers --------------------------------- -You need to configure at least one authentication provider to use EdgeDB Auth. -This can be done via the EdgeDB UI or directly through queries. +You need to configure at least one authentication provider to use Gel Auth. +This can be done via the Gel UI or directly through queries. In this example, we'll configure a email and password provider. You can add it with the following query: @@ -1396,7 +1396,7 @@ Next, we're going to create endpoints in FastAPI to handle user registration response.set_cookie(key="edgedb-auth-token", value=auth_token, httponly=True, secure=True, samesite='strict') return response -The sign-up endpoint sends a POST request to the EdgeDB Auth server to register +The sign-up endpoint sends a POST request to the Gel Auth server to register a new user. It also sets the auth token as an HttpOnly cookie in the response. **Sign-in endpoint** @@ -1438,7 +1438,7 @@ a new user. It also sets the auth token as an HttpOnly cookie in the response. response.set_cookie(key="edgedb-auth-token", value=auth_token, httponly=True, secure=True, samesite='strict') return response -The sign-in endpoint sends a POST request to the EdgeDB Auth server to authenticate +The sign-in endpoint sends a POST request to the Gel Auth server to authenticate a user. It then retrieves the code from the response and exchanges it for an auth token. The token is set as an HttpOnly cookie in the response. @@ -1459,8 +1459,8 @@ We'll use the ``create_user_async_edgeql`` query we generated earlier to achieve this, but we'll need to modify it slightly to link it to the EdgeDB Auth identity. -First, let's update the EdgeDB schema to include a new field in the User type -to store the EdgeDB Auth identity and a new ``current_user`` type. +First, let's update the Gel schema to include a new field in the User type +to store the Gel Auth identity and a new ``current_user`` type. .. code-block:: sdl-diff :caption: dbschema/default.esdl @@ -1506,9 +1506,9 @@ endpoint to create a new user in the database. We need to do a few things: 1. Import ``edgedb``. -2. Create an EdgeDB client. +2. Create an Gel client. -3. Get the identity ID from the EdgeDB Auth server response. +3. Get the identity ID from the Gel Auth server response. 4. Create a new user in the database using the ``create_user_async_edgeql`` query. @@ -1583,11 +1583,11 @@ If the request is successful, you should see a response with the message Wrapping up =========== -Now you have a fully functioning events API in FastAPI backed by EdgeDB. If you +Now you have a fully functioning events API in FastAPI backed by Gel. If you want to see all the source code for the completed project, you'll find it in `our examples repo `_. We also -have a separate example that demonstrates how to integrate EdgeDB Auth with +have a separate example that demonstrates how to integrate Gel Auth with FastAPI in the same repo. Check it out `here `_. If you're stuck or if you just want to show off what you've built, come talk @@ -1595,7 +1595,7 @@ to us `on Discord `_. It's a great community of helpful folks, all passionate about being part of the next generation of databases. -If you like what you see and want to dive deeper into EdgeDB and what it can -do, check out our `Easy EdgeDB book `_. In -it, you'll get to learn more about EdgeDB as we build an imaginary role-playing +If you like what you see and want to dive deeper into Gel and what it can +do, check out our `Easy Gel book `_. In +it, you'll get to learn more about Gel as we build an imaginary role-playing game based on Bram Stoker's Dracula. diff --git a/docs/guides/tutorials/rest_apis_with_flask.rst b/docs/guides/tutorials/rest_apis_with_flask.rst index 5cc48b6ec73..7dfbcf719f2 100644 --- a/docs/guides/tutorials/rest_apis_with_flask.rst +++ b/docs/guides/tutorials/rest_apis_with_flask.rst @@ -6,7 +6,7 @@ Flask :edb-alt-title: Building a REST API with Gel and Flask -The EdgeDB Python client makes it easy to integrate EdgeDB into your preferred +The Gel Python client makes it easy to integrate Gel into your preferred web development stack. In this tutorial, we'll see how you can quickly start building RESTful APIs with `Flask `_ and EdgeDB. @@ -50,22 +50,22 @@ dependencies with this command: Initialize the database ^^^^^^^^^^^^^^^^^^^^^^^ -Now, let's initialize an EdgeDB project. From the project's root directory: +Now, let's initialize an Gel project. From the project's root directory: .. code-block:: bash $ edgedb project init Initializing project... - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: flask_crud]: > flask_crud Do you want to start instance automatically on login? [y/n] > y - Checking EdgeDB versions... + Checking Gel versions... -Once you've answered the prompts, a new EdgeDB instance called ``flask_crud`` +Once you've answered the prompts, a new Gel instance called ``flask_crud`` will be created and started. @@ -83,7 +83,7 @@ similar to this: :: - EdgeDB 2.x (repl 2.x) + Gel x.x (repl x.x) Type \help for help, \quit to quit. edgedb> @@ -142,7 +142,7 @@ This is how our datatypes look: Here, we've defined an ``abstract`` type called ``Auditable`` to take advantage -of EdgeDB's schema mixin system. This allows us to add a ``created_at`` +of Gel's schema mixin system. This allows us to add a ``created_at`` property to multiple types without repeating ourselves. The ``Actor`` type extends ``Auditable`` and inherits the ``created_at`` @@ -231,7 +231,7 @@ objects saved in the database. You can create the API in Flask like this: The ``Blueprint`` instance does the actual work of exposing the API. We also -create a blocking EdgeDB client instance to communicate with the database. By +create a blocking Gel client instance to communicate with the database. By default, this API will return a list of actors, but you can also filter the objects by name. @@ -796,7 +796,7 @@ That'll return: Conclusion ========== -While building REST APIs, the EdgeDB client allows you to leverage EdgeDB with +While building REST APIs, the Gel client allows you to leverage Gel with any microframework of your choice. Whether it's `FastAPI `_, `Flask `_, diff --git a/docs/guides/tutorials/trpc.rst b/docs/guides/tutorials/trpc.rst index 4014defb623..b9761e6a63d 100644 --- a/docs/guides/tutorials/trpc.rst +++ b/docs/guides/tutorials/trpc.rst @@ -6,7 +6,7 @@ tRPC :edb-alt-title: Integrating Gel with tRPC -This guide explains how to integrate **EdgeDB** with **tRPC** for a modern, +This guide explains how to integrate **Gel** with **tRPC** for a modern, type-safe API. We'll cover setting up database interactions, API routing, and implementing authentication, all while ensuring type safety across the client and server. @@ -15,29 +15,29 @@ You can reference the following repositories for more context: - `create-t3-turbo-edgedb `_ - A monorepo template using the `T3 stack `_, - `Turborepo `_, and EdgeDB. + `Turborepo `_, and Gel. - `LookFeel Project `_ - A real-world - example using **EdgeDB** and **tRPC**. + example using **Gel** and **tRPC**. -Step 1: EdgeDB setup -==================== +Step 1: Gel setup +================= EdgeDB will serve as the database layer for your application. -Install and initialize EdgeDB ------------------------------ +Install and initialize Gel +-------------------------- -To initialize **EdgeDB**, run the following command using your preferred +To initialize **Gel**, run the following command using your preferred package manager: .. code-block:: bash $ pnpm dlx edgedb project init # or npx edgedb project init -This will create an EdgeDB project and set up a schema to start with. +This will create an Gel project and set up a schema to start with. -Define the EdgeDB Schema ------------------------- +Define the Gel Schema +--------------------- The previous command generated a schema file in the ``dbschema`` directory. @@ -63,16 +63,16 @@ Once schema changes are made, apply migrations with: $ pnpm dlx edgedb migration create # or npx edgedb migration create $ pnpm dlx edgedb migration apply # or npx edgedb migration apply -Step 2: Configure EdgeDB Client -=============================== +Step 2: Configure Gel Client +============================ -To interact with **EdgeDB** from your application, you need to configure the +To interact with **Gel** from your application, you need to configure the client. -Install EdgeDB Client ---------------------- +Install Gel Client +------------------ -First, install the **EdgeDB** client using your package manager: +First, install the **Gel** client using your package manager: .. code-block:: bash @@ -124,7 +124,7 @@ install a wrapper around the `@tanstack/react-query { - const session = await auth.getSession(); // Retrieve session from EdgeDB Auth + const session = await auth.getSession(); // Retrieve session from Gel Auth return { session, // Pass the session to the context @@ -343,7 +343,7 @@ context that provides the user session and EdgeDB client to the tRPC API. 4. **Example tRPC Procedure** You can now write procedures in your tRPC router, making use of the - **EdgeDB Auth** session and the **EdgeDB** client: + **Gel Auth** session and the **Gel** client: .. code-block:: typescript @@ -352,7 +352,7 @@ context that provides the user session and EdgeDB client to the tRPC API. if (!(await ctx.session.isSignedIn())) { throw new Error("Not authenticated"); } - // Fetch data from EdgeDB using the authenticated client + // Fetch data from Gel using the authenticated client const userData = await ctx.session.client.query(` select User { name, email } `); @@ -367,20 +367,20 @@ EdgeDB Auth with tRPC and Context in Express In **Express**, the process involves setting up middleware to manage the authentication and context for tRPC procedures. -1. **Initialize EdgeDB Client and Auth for Express** +1. **Initialize Gel Client and Auth for Express** - Just like in **Next.js**, you first initialize the **EdgeDB** client and - **EdgeDB Auth**: + Just like in **Next.js**, you first initialize the **Gel** client and + **Gel Auth**: .. code-block:: typescript import { createClient } from "edgedb"; import createExpressAuth from "@edgedb/auth-express"; - // Initialize EdgeDB client + // Initialize Gel client const edgedbClient = createClient(); - // Initialize EdgeDB Auth for Express + // Initialize Gel Auth for Express export const auth = createExpressAuth(edgedbClient, { baseUrl: `http://localhost:${process.env.PORT || 3000}`, }); @@ -388,7 +388,7 @@ authentication and context for tRPC procedures. 2. **Create tRPC Context Middleware for Express** In **Express**, create middleware to pass the authenticated session and - EdgeDB client to the tRPC context: + Gel client to the tRPC context: .. code-block:: typescript @@ -403,7 +403,7 @@ authentication and context for tRPC procedures. const session = req.auth?.session(); // Get authenticated session req.context = { session, // Add session to context - edgedbClient, // Add EdgeDB client to context + edgedbClient, // Add Gel client to context }; next(); }; @@ -411,7 +411,7 @@ authentication and context for tRPC procedures. 3. **Set up tRPC Router in Express** Use the **tRPC router** in **Express** by including the context middleware - and **EdgeDB Auth** middleware: + and **Gel Auth** middleware: .. code-block:: typescript @@ -423,7 +423,7 @@ authentication and context for tRPC procedures. const app = express(); - // EdgeDB Auth middleware to handle sessions + // Gel Auth middleware to handle sessions app.use(auth.middleware); // Custom middleware to pass tRPC context @@ -445,7 +445,7 @@ authentication and context for tRPC procedures. 4. **Example tRPC Procedure in Express** Once the context is set, you can define tRPC procedures that use both the - session and EdgeDB client: + session and Gel client: .. code-block:: typescript @@ -454,7 +454,7 @@ authentication and context for tRPC procedures. if (!(await ctx.session.isSignedIn())) { throw new Error("Not authenticated"); } - // Fetch data from EdgeDB using the authenticated client + // Fetch data from Gel using the authenticated client const userData = await ctx.session.client.query(` select User { name, email } `); @@ -466,7 +466,7 @@ authentication and context for tRPC procedures. Conclusion ---------- -By integrating **EdgeDB Auth** into the tRPC context, you ensure that +By integrating **Gel Auth** into the tRPC context, you ensure that authenticated sessions are securely passed to API procedures, enabling user authentication and protecting routes. @@ -474,6 +474,6 @@ You can also reference these projects for further examples: - `create-t3-turbo-edgedb `_ - A monorepo template using the `T3 stack `_, - `Turborepo `_, and EdgeDB. + `Turborepo `_, and Gel. - `LookFeel Project `_ - A real-world - example using **EdgeDB** and **tRPC**. + example using **Gel** and **tRPC**. diff --git a/docs/index.rst b/docs/index.rst index 4fc6781909e..ce82fdc02ad 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,9 @@ .. _index_toplevel: -==================== +================= Gel documentation -==================== +================= Welcome to the |Gel| |version| documentation. diff --git a/docs/intro/branches.rst b/docs/intro/branches.rst index ffd288e5c4d..da753b3c133 100644 --- a/docs/intro/branches.rst +++ b/docs/intro/branches.rst @@ -6,7 +6,7 @@ Branches EdgeDB's branches make it easy to prototype app features that impact your database schema, even in cases where those features are never released. You can -create a branch in your EdgeDB database that corresponds to a feature branch in +create a branch in your Gel database that corresponds to a feature branch in your VCS. When you're done, either :ref:`merge ` that branch into your main branch or :ref:`drop ` it leaving your original schema intact. @@ -15,7 +15,7 @@ it leaving your original schema intact. The procedure we will describe should be adaptable to any VCS offering branching and rebasing, but in order to make the examples concrete and - easy-to-follow, we'll be demonstrating how EdgeDB branches interact with + easy-to-follow, we'll be demonstrating how Gel branches interact with Git branches. You may adapt these examples to your VCS of choice. @@ -23,7 +23,7 @@ it leaving your original schema intact. ------------------------------ Create a feature branch in your VCS and switch to it. Then, create and switch -to a corresponding branch in EdgeDB using the CLI. +to a corresponding branch in Gel using the CLI. .. code-block:: bash diff --git a/docs/intro/cli.rst b/docs/intro/cli.rst index 437fdb425f2..5f5383e75ab 100644 --- a/docs/intro/cli.rst +++ b/docs/intro/cli.rst @@ -7,12 +7,12 @@ The CLI ======= The ``edgedb`` command line tool is an integral part of the developer workflow -of building with EdgeDB. Below are instructions for installing it. +of building with Gel. Below are instructions for installing it. Installation ------------ -To get started with EdgeDB, the first step is install the ``edgedb`` CLI. +To get started with Gel, the first step is install the ``edgedb`` CLI. **Linux or macOS** @@ -24,7 +24,7 @@ To get started with EdgeDB, the first step is install the ``edgedb`` CLI. .. note:: - EdgeDB on Windows requires WSL 2 because the EdgeDB server runs on Linux. + Gel on Windows requires WSL 2 because the Gel server runs on Linux. .. code-block:: powershell @@ -37,7 +37,7 @@ shell environment. Then test the installation: .. code-block:: bash $ edgedb --version - EdgeDB CLI 4.x+abcdefg + Gel CLI x.x+abcdefg .. note:: @@ -74,7 +74,7 @@ to see a breakdown of all the commands and options. Cloud Connection Options: -The majority of CLI commands perform some action against a *particular* EdgeDB +The majority of CLI commands perform some action against a *particular* Gel instance. As such, there are a standard set of flags that are used to specify *which instance* should be the target of the command, plus additional information like TLS certificates. The following command documents these flags. @@ -89,20 +89,20 @@ information like TLS certificates. The following command documents these flags. Cloud instances available to you) --dsn - DSN for EdgeDB to connect to (overrides all other options except + DSN for Gel to connect to (overrides all other options except password) --credentials-file Path to JSON file to read credentials from -H, --host - EdgeDB instance host + Gel instance host -P, --port - Port to connect to EdgeDB + Port to connect to Gel --unix-path - A path to a Unix socket for EdgeDB connection + A path to a Unix socket for Gel connection When the supplied path is a directory, the actual path will be computed using the `--port` and `--admin` parameters. @@ -115,12 +115,12 @@ the ``--help`` flag. .. code-block:: bash $ edgedb instance --help - Manage local EdgeDB instances + Manage local Gel instances Usage: edgedb instance Commands: - create Initialize a new EdgeDB instance + create Initialize a new Gel instance list Show all instances status Show status of an instance start Start an instance diff --git a/docs/intro/clients.rst b/docs/intro/clients.rst index 95f6f19f8af..035cefa5870 100644 --- a/docs/intro/clients.rst +++ b/docs/intro/clients.rst @@ -5,24 +5,24 @@ Client Libraries ================ EdgeDB implements libraries for popular languages that make it easier to work -with EdgeDB. These libraries provide a common set of functionality. +with Gel. These libraries provide a common set of functionality. - *Instantiating clients.* Most libraries implement a ``Client`` class that - internally manages a pool of physical connections to your EdgeDB instance. + internally manages a pool of physical connections to your Gel instance. - *Resolving connections.* All client libraries implement a standard protocol for determining how to connect to your database. In most cases, this will involve checking for special environment variables like ``EDGEDB_DSN`` or, in - the case of EdgeDB Cloud instances, ``EDGEDB_INSTANCE`` and + the case of Gel Cloud instances, ``EDGEDB_INSTANCE`` and ``EDGEDB_SECRET_KEY``. (More on this in :ref:`the Connection section below `.) - *Executing queries.* A ``Client`` will provide some methods for executing queries against your database. Under the hood, this query is executed using - EdgeDB's efficient binary protocol. + Gel's efficient binary protocol. .. note:: - For some use cases, you may not need a client library. EdgeDB allows you to + For some use cases, you may not need a client library. Gel allows you to execute :ref:`queries over HTTP `. This is slower than the binary protocol and lacks support for transactions and rich data types, but may be suitable if a client library isn't available for your language of @@ -31,7 +31,7 @@ with EdgeDB. These libraries provide a common set of functionality. Available libraries =================== -To execute queries from your application code, use one of EdgeDB's *client +To execute queries from your application code, use one of Gel's *client libraries* for the following languages. - :ref:`JavaScript/TypeScript ` @@ -111,7 +111,7 @@ Configure the environment as needed for your preferred language. $ mix new edgedb_quickstart -Install the EdgeDB client library. +Install the Gel client library. .. tabs:: @@ -149,7 +149,7 @@ Install the EdgeDB client library. .. code-tab:: bash :caption: .NET - $ dotnet add package EdgeDB.Net.Driver + $ dotnet add package Gel.Net.Driver .. code-tab:: xml :caption: Maven (Java) @@ -268,21 +268,21 @@ database and provide a set of methods for executing queries. .. code-tab:: csharp :caption: .NET - using EdgeDB; + using Gel; - var client = new EdgeDBClient(); + var client = new GelClient(); var result = await client.QuerySingleAsync("select random();"); Console.WriteLine(result); .. code-tab:: java :caption: Futures (Java) - import com.edgedb.driver.EdgeDBClient; + import com.edgedb.driver.GelClient; import java.util.concurrent.CompletableFuture; public class Main { public static void main(String[] args) { - var client = new EdgeDBClient(); + var client = new GelClient(); client.querySingle(String.class, "select random();") .thenAccept(System.out::println) @@ -293,12 +293,12 @@ database and provide a set of methods for executing queries. .. code-tab:: java :caption: Reactor (Java) - import com.edgedb.driver.EdgeDBClient; + import com.edgedb.driver.GelClient; import reactor.core.publisher.Mono; public class Main { public static void main(String[] args) { - var client = new EdgeDBClient(); + var client = new GelClient(); Mono.fromFuture(client.querySingle(String.class, "select random();")) .doOnNext(System.out::println) @@ -310,10 +310,10 @@ database and provide a set of methods for executing queries. :caption: Elixir # lib/edgedb_quickstart.ex - defmodule EdgeDBQuickstart do + defmodule GelQuickstart do def run do - {:ok, client} = EdgeDB.start_link() - result = EdgeDB.query_single!(client, "select random()") + {:ok, client} = Gel.start_link() + result = Gel.query_single!(client, "select random()") IO.inspect(result) end end @@ -364,10 +364,10 @@ Finally, execute the file. .. code-tab:: bash :caption: Elixir - $ mix run -e EdgeDBQuickstart.run + $ mix run -e GelQuickstart.run You should see a random number get printed to the console. This number was -generated inside your EdgeDB instance using EdgeQL's built-in +generated inside your Gel instance using EdgeQL's built-in :eql:func:`random` function. .. _ref_intro_clients_connection: @@ -398,18 +398,18 @@ Using environment variables .. _ref_intro_clients_connection_cloud: -For EdgeDB Cloud -^^^^^^^^^^^^^^^^ +For Gel Cloud +^^^^^^^^^^^^^ In production, connection information can be securely passed to the client -library via environment variables. For EdgeDB Cloud instances, the recommended +library via environment variables. For Gel Cloud instances, the recommended variables to set are ``EDGEDB_INSTANCE`` and ``EDGEDB_SECRET_KEY``. Set ``EDGEDB_INSTANCE`` to ``/`` where -```` is the name you set when you created the EdgeDB Cloud +```` is the name you set when you created the Gel Cloud instance. -If you have not yet created a secret key, you can do so in the EdgeDB Cloud UI +If you have not yet created a secret key, you can do so in the Gel Cloud UI or by running :ref:`ref_cli_edgedb_cloud_secretkey_create` via the CLI. For self-hosted instances @@ -517,18 +517,18 @@ Other mechanisms "tls_cert_data": "-----BEGIN CERTIFICATE-----\nabcdef..." } -``EDGEDB_INSTANCE`` (local/EdgeDB Cloud only) - The name of an instance. Useful only for local or EdgeDB Cloud instances. +``EDGEDB_INSTANCE`` (local/Gel Cloud only) + The name of an instance. Useful only for local or Gel Cloud instances. .. note:: - For more on EdgeDB Cloud instances, see the :ref:`EdgeDB Cloud instance + For more on Gel Cloud instances, see the :ref:`Gel Cloud instance connection section ` above. Reference --------- -These are the most common ways to connect to an instance, however EdgeDB +These are the most common ways to connect to an instance, however Gel supports several other options for advanced use cases. For a complete reference on connection configuration, see :ref:`Reference > Connection Parameters `. diff --git a/docs/intro/edgeql.rst b/docs/intro/edgeql.rst index 93384b5daa6..9b3dc55b50e 100644 --- a/docs/intro/edgeql.rst +++ b/docs/intro/edgeql.rst @@ -3,7 +3,7 @@ EdgeQL ====== -EdgeQL is the query language of EdgeDB. It's intended as a spiritual successor +EdgeQL is the query language of Gel. It's intended as a spiritual successor to SQL that solves some of its biggest design limitations. This page is intended as a rapid-fire overview so you can hit the ground running with EdgeDB. Refer to the linked pages for more in-depth documentation. @@ -31,7 +31,7 @@ EdgeDB has a rich primitive type system consisting of the following data types. * - Booleans - ``bool`` * - Numbers - - ``int16`` ``int32`` ``int64`` ``float32`` ``float64`` + - ``int16`` ``int32`` ``int64`` ``float32`` ``float64`` ``bigint`` ``decimal`` * - UUID - ``uuid`` diff --git a/docs/intro/index.rst b/docs/intro/index.rst index 24ab8234c8b..4da51a5b09d 100644 --- a/docs/intro/index.rst +++ b/docs/intro/index.rst @@ -19,7 +19,7 @@ Get Started edgeql clients Live tutorial - Easy EdgeDB book + Easy Gel book EdgeDB is a next-generation `graph-relational database `_ designed @@ -27,7 +27,7 @@ as a spiritual successor to the relational database. It inherits the strengths of SQL databases: type safety, performance, reliability, and transactionality. But instead of modeling data in a -relational (tabular) way, EdgeDB represents data with *object types* +relational (tabular) way, Gel represents data with *object types* containing *properties* and *links* to other objects. It leverages this object-oriented model to provide a superpowered query language that solves some of SQL's biggest usability problems. @@ -41,34 +41,34 @@ building your application. - **Get Started** — Start with the :ref:`quickstart `. It walks - through EdgeDB's core workflows: how to install EdgeDB, create an instance, + through Gel's core workflows: how to install Gel, create an instance, write a simple schema, execute a migration, write some simple queries, and use the client libraries. The rest of the section goes deeper on each of these subjects. - **Schema** — - A set of pages that break down the concepts of syntax of EdgeDB's schema - definition language (SDL). This starts with a rundown of EdgeDB's primitive + A set of pages that break down the concepts of syntax of Gel's schema + definition language (SDL). This starts with a rundown of Gel's primitive type system (:ref:`Primitives `), followed by a description of (:ref:`Object Types `) and the things they can contain: links, properties, indexes, access policies, and more. - **EdgeQL** — - A set of pages that break down EdgeDB's query language, EdgeQL. It starts + A set of pages that break down Gel's query language, EdgeQL. It starts with a rundown of how to declare :ref:`literal values `, then introduces some key EdgeQL concepts like sets, paths, and type casts. With the basics established, it proceeds to break down all of EdgeQL's top-level statements: ``select``, ``insert``, and so on. - **Guides** — - Contains collections of guides on topics that are peripheral to EdgeDB + Contains collections of guides on topics that are peripheral to Gel itself: how to deploy to various cloud providers, how to integrate with various frameworks, and how to introspect the schema to build - code-generation tools on top of EdgeDB. + code-generation tools on top of Gel. - **Standard Library** — - This section contains an encyclopedic breakdown of EdgeDB's built-in types + This section contains an encyclopedic breakdown of Gel's built-in types and the functions/operators that can be used with them. We didn't want to \ clutter the **EdgeQL** section with all the nitty-gritty on each of these. If you're looking for a particular function (say, a ``replace``), go to the @@ -77,12 +77,12 @@ building your application. (:eql:func:`str_replace`). - **Client Libraries** - The documentation for EdgeDB's set of official client libraries for + The documentation for Gel's set of official client libraries for JavaScript/TypeScript, Python, Go, and Rust. All client libraries implement - EdgeDB's binary protocol and provide a standard interface for executing + Gel's binary protocol and provide a standard interface for executing queries. If you're using another language, you can execute queries :ref:`over HTTP `. This section also includes - documentation for EdgeDB's :ref:`GraphQL ` endpoint. + documentation for Gel's :ref:`GraphQL ` endpoint. - **CLI** Complete reference for the ``edgedb`` command-line tool. The CLI is @@ -90,20 +90,20 @@ building your application. relevant documentation—so you shouldn't need to reference this section often. - **Reference** - The *Reference* section contains a complete breakdown of EdgeDB's *syntax* + The *Reference* section contains a complete breakdown of Gel's *syntax* (for both EdgeQL and SDL), *internals* (like the binary protocol and dump file format), and *configuration settings*. Usually you'll only need to reference these once you're an advanced user. - **Changelog** - Detailed changelogs for each successive version of EdgeDB, including any + Detailed changelogs for each successive version of Gel, including any breaking changes, new features, bigfixes, and links to Tooling ^^^^^^^ -To actually build apps with EdgeDB, you'll need to know more than SDL and +To actually build apps with Gel, you'll need to know more than SDL and EdgeQL. - **CLI** — @@ -117,9 +117,9 @@ EdgeQL. To actually execute queries, you'll use one of our client libraries for JavaScript, Go, or Python; find your preferred library under :ref:`Client Libraries `. If you're using another language, you can - still use EdgeDB! You can execute :ref:`queries via HTTP `. + still use Gel! You can execute :ref:`queries via HTTP `. - **Deployment** — - To publish an EdgeDB-backed application, you'll need to deploy EdgeDB. Refer + To publish an Gel-backed application, you'll need to deploy Gel. Refer to :ref:`Guides > Deployment ` for step-by-step deployment guides for all major cloud hosting platforms, as well as instructions for self-hosting with Docker. @@ -136,7 +136,7 @@ EdgeDB features: - built-in support for schema migrations. EdgeDB is not a graph database: the data is stored and queried using -relational database techniques. Unlike most graph databases, EdgeDB +relational database techniques. Unlike most graph databases, Gel maintains a strict schema. EdgeDB is not a document database, but inserting and querying hierarchical diff --git a/docs/intro/instances.rst b/docs/intro/instances.rst index 47c475e2982..e68fb612e0e 100644 --- a/docs/intro/instances.rst +++ b/docs/intro/instances.rst @@ -4,7 +4,7 @@ Instances ========= -Let's get to the good stuff. You can spin up an EdgeDB instance with a single +Let's get to the good stuff. You can spin up an Gel instance with a single command. .. code-block:: bash @@ -12,13 +12,13 @@ command. $ edgedb instance create my_instance This creates a new instance named ``my_instance`` that runs the latest stable -version of EdgeDB. (EdgeDB itself will be automatically installed if it isn't +version of Gel. (Gel itself will be automatically installed if it isn't already.) Alternatively you can specify a specific version with ``--version``. .. code-block:: bash - $ edgedb instance create my_instance --version 2.1 + $ edgedb instance create my_instance --version 6.1 $ edgedb instance create my_instance --version nightly We can execute a query against our new instance with ``edgedb query``. Specify @@ -52,9 +52,9 @@ To list all instances on your machine: ┌────────┬──────────────────┬──────────┬────────────────┬──────────┐ │ Kind │ Name │ Port │ Version │ Status │ ├────────┼──────────────────┼──────────┼────────────────┼──────────┤ - │ local │ my_instance │ 10700 │ 4.x+cc4f3b5 │ active │ - │ local │ my_instance_2 │ 10701 │ 4.x+cc4f3b5 │ active │ - │ local │ my_instance_3 │ 10702 │ 4.x+cc4f3b5 │ active │ + │ local │ my_instance │ 10700 │ x.x+cc4f3b5 │ active │ + │ local │ my_instance_2 │ 10701 │ x.x+cc4f3b5 │ active │ + │ local │ my_instance_3 │ 10702 │ x.x+cc4f3b5 │ active │ └────────┴──────────────────┴──────────┴────────────────┴──────────┘ Further reference diff --git a/docs/intro/migrations.rst b/docs/intro/migrations.rst index 46c2c34f5f6..7cd443214a3 100644 --- a/docs/intro/migrations.rst +++ b/docs/intro/migrations.rst @@ -32,7 +32,7 @@ If you get output similar to the output above, you're ready to get started! 2. Write an initial schema -------------------------- -By convention, your EdgeDB schema is defined inside one or more ``.esdl`` +By convention, your Gel schema is defined inside one or more ``.esdl`` files that live in a directory called ``dbschema`` in the root directory of your codebase. @@ -43,7 +43,7 @@ your codebase. │ └── default.esdl # schema file (written by you) └── edgedb.toml -The schema itself is written using EdgeDB's schema definition language. Edit +The schema itself is written using Gel's schema definition language. Edit your ``dbschema/default.esdl`` and add the following schema inside your ``module default`` block: @@ -117,7 +117,7 @@ create``. $ edgedb migration create -The CLI reads your schema file and sends it to the active EdgeDB instance. The +The CLI reads your schema file and sends it to the active Gel instance. The instance compares the file's contents to its current schema state and determines a migration plan. **The migration plan is generated by the database itself.** @@ -263,7 +263,7 @@ following EdgeQL features are often useful: .. list-table:: * - ``assert_exists`` - - This is an "escape hatch" function that tells EdgeDB to assume the input + - This is an "escape hatch" function that tells Gel to assume the input has *at least* one element. .. code-block:: @@ -275,7 +275,7 @@ following EdgeQL features are often useful: otherwise it will fail. * - ``assert_single`` - - This tells EdgeDB to assume the input has *at most* one element. This + - This tells Gel to assume the input has *at most* one element. This will throw an error if the argument is a set containing more than one element. This is useful is you are changing a property from ``multi`` to ``single``. diff --git a/docs/intro/projects.rst b/docs/intro/projects.rst index 0885abb3012..1be0ffe1fa7 100644 --- a/docs/intro/projects.rst +++ b/docs/intro/projects.rst @@ -11,14 +11,14 @@ CLI command. $ edgedb migration create -I my_instance -That's one of the reasons we introduced the concept of an *EdgeDB +That's one of the reasons we introduced the concept of an *Gel project*. A project is a directory on your file system that is associated -("linked") with an EdgeDB instance. +("linked") with an Gel instance. .. note:: Projects are intended to make *local development* easier! They only exist on - your local machine and are managed with the CLI. When deploying EdgeDB for + your local machine and are managed with the CLI. When deploying Gel for production, you will typically pass connection information to the client library using environment variables. @@ -29,9 +29,9 @@ When you're inside a project, all CLI commands will be applied against the $ edgedb migration create -The same is true for all EdgeDB client libraries (discussed in more depth in +The same is true for all Gel client libraries (discussed in more depth in the :ref:`Clients ` section). If the following file lives -inside an EdgeDB project directory, ``createClient`` will discover the project +inside an Gel project directory, ``createClient`` will discover the project and connect to its linked instance with no additional configuration. .. code-block:: typescript @@ -54,15 +54,15 @@ project init`` inside it. You'll see something like this: No `edgedb.toml` found in this repo or above. Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: my_instance]: > my_instance - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: 4.x]: + Checking Gel versions... + Specify the version of Gel to use with this project [default: x.x]: > # (left blank for default) ... - Successfully installed 4.x+cc4f3b5 - Initializing EdgeDB instance... + Successfully installed x.x+cc4f3b5 + Initializing Gel instance... Applying migrations... Everything is up to date. Revision initial Project initialized. @@ -70,9 +70,9 @@ project init`` inside it. You'll see something like this: This command does a couple important things. -1. It spins up a new EdgeDB instance called ``my_instance``. +1. It spins up a new Gel instance called ``my_instance``. 2. If no ``edgedb.toml`` file exists, it will create one. This is a - configuration file that marks a given directory as an EdgeDB project. Learn + configuration file that marks a given directory as an Gel project. Learn more about it in :ref:`our edgedb.toml reference `. @@ -86,7 +86,7 @@ This command does a couple important things. ``dbschema`` directory exists and contains a subdirectory called ``migrations``, those migrations will be applied against the new instance. -Every project maps one-to-one to a particular EdgeDB instance. From +Every project maps one-to-one to a particular Gel instance. From inside a project directory, you can run ``edgedb project info`` to see information about the current project. @@ -109,7 +109,7 @@ executed against the project-linked instance. For instance, you can simply run .. code-block:: bash $ edgedb - EdgeDB 4.x+cc4f3b5 (repl 4.x+da2788e) + Gel x.x+cc4f3b5 (repl x.x+da2788e) Type \help for help, \quit to quit. my_instance:edgedb> select "Hello world!"; @@ -125,7 +125,7 @@ with the ``-I`` flag. Hint: Run `edgedb project init` or use any of `-H`, `-P`, `-I` arguments to specify connection parameters. See `--help` for details $ edgedb -I my_instance - EdgeDB 4.x+cc4f3b5 (repl 4.x+da2788e) + Gel x.x+cc4f3b5 (repl x.x+da2788e) Type \help for help, \quit to quit. my_instance:edgedb> @@ -135,8 +135,8 @@ linked instance without additional configuration. Using remote instances ^^^^^^^^^^^^^^^^^^^^^^ -You may want to initialize a project that points to a remote EdgeDB instance. -This is totally a valid case and EdgeDB fully supports it! Before running +You may want to initialize a project that points to a remote Gel instance. +This is totally a valid case and Gel fully supports it! Before running ``edgedb project init``, you just need to create an alias for the remote instance using ``edgedb instance link``, like so: @@ -204,7 +204,7 @@ A standalone instance (not linked to a project) can be upgraded with the $ edgedb project upgrade --to-latest $ edgedb project upgrade --to-nightly - $ edgedb project upgrade --to-version 4.x + $ edgedb project upgrade --to-version x.x See info diff --git a/docs/intro/quickstart.rst b/docs/intro/quickstart.rst index b2c9a7e1789..3fcd6c6f46c 100644 --- a/docs/intro/quickstart.rst +++ b/docs/intro/quickstart.rst @@ -4,7 +4,7 @@ Quickstart ========== -Welcome to EdgeDB! +Welcome to Gel! This quickstart will walk you through the entire process of creating a simple EdgeDB-powered application: installation, defining your schema, adding some @@ -15,12 +15,12 @@ data, and writing your first query. Let's jump in! 1. Installation =============== -First let's install the EdgeDB CLI. Open a terminal and run the appropriate +First let's install the Gel CLI. Open a terminal and run the appropriate command below. .. note:: Great news for Node users! - Skip installing and start using the EdgeDB CLI right away! Just prepend + Skip installing and start using the Gel CLI right away! Just prepend :ref:`any CLI command ` with ``npx`` or your package manager's equivalent. For example, to create a new project, you can use ``npx edgedb project init``. @@ -38,27 +38,27 @@ Linux .. code-tab:: bash :caption: APT - $ # Import the EdgeDB packaging key + $ # Import the Gel packaging key $ sudo mkdir -p /usr/local/share/keyrings && \ sudo curl --proto '=https' --tlsv1.2 -sSf \ -o /usr/local/share/keyrings/edgedb-keyring.gpg \ https://packages.edgedb.com/keys/edgedb-keyring.gpg && \ - $ # Add the EdgeDB package repository + $ # Add the Gel package repository $ echo deb [signed-by=/usr/local/share/keyrings/edgedb-keyring.gpg]\ https://packages.edgedb.com/apt \ $(grep "VERSION_CODENAME=" /etc/os-release | cut -d= -f2) main \ | sudo tee /etc/apt/sources.list.d/edgedb.list - $ # Install the EdgeDB package + $ # Install the Gel package $ sudo apt-get update && sudo apt-get install edgedb-5 .. code-tab:: bash :caption: YUM - $ # Add the EdgeDB package repository + $ # Add the Gel package repository $ sudo curl --proto '=https' --tlsv1.2 -sSfL \ https://packages.edgedb.com/rpm/edgedb-rhel.repo \ > /etc/yum.repos.d/edgedb.repo - $ # Install the EdgeDB package + $ # Install the Gel package $ sudo yum install edgedb-5 macOS @@ -74,9 +74,9 @@ macOS .. code-tab:: bash :caption: Homebrew - $ # Add the EdgeDB tap to your Homebrew + $ # Add the Gel tap to your Homebrew $ brew tap edgedb/tap - $ # Install EdgeDB CLI + $ # Install Gel CLI $ brew install edgedb-cli Windows (Powershell) @@ -84,9 +84,9 @@ Windows (Powershell) .. note:: - EdgeDB on Windows requires WSL 2 to create local instances because the - EdgeDB server runs on Linux. It is *not* required if you will use the CLI - only to manage EdgeDB Cloud and/or other remote instances. This quickstart + Gel on Windows requires WSL 2 to create local instances because the + Gel server runs on Linux. It is *not* required if you will use the CLI + only to manage Gel Cloud and/or other remote instances. This quickstart *does* create local instances, so WSL 2 is required to complete the quickstart. @@ -96,7 +96,7 @@ Windows (Powershell) .. note:: Command prompt installation - To install EdgeDB in the Windows Command prompt, follow these steps: + To install Gel in the Windows Command prompt, follow these steps: 1. `Download the CLI `__ @@ -113,7 +113,7 @@ installs the ``edgedb`` CLI on your machine. You may be asked for your password. Once the installation completes, you may need to **restart your terminal** before you can use the ``edgedb`` command. -Now let's set up your EdgeDB project. +Now let's set up your Gel project. .. _ref_quickstart_createdb: @@ -127,14 +127,14 @@ In a terminal, create a new directory and ``cd`` into it. $ mkdir quickstart $ cd quickstart -Then initialize your EdgeDB project: +Then initialize your Gel project: .. code-block:: bash $ edgedb project init This starts an interactive tool that walks you through the process of setting -up your first EdgeDB instance. You should see something like this: +up your first Gel instance. You should see something like this: .. code-block:: bash @@ -142,12 +142,12 @@ up your first EdgeDB instance. You should see something like this: No `edgedb.toml` found in `/path/to/quickstart` or above Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: quickstart]: > quickstart - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: 5.x]: - > 5.x + Checking Gel versions... + Specify the version of Gel to use with this project [default: x.x]: + > x.x Specify branch name: [default: main]: > main ┌─────────────────────┬───────────────────────────────────────────────┐ @@ -155,13 +155,13 @@ up your first EdgeDB instance. You should see something like this: │ Project config │ ~/path/to/quickstart/edgedb.toml │ │ Schema dir (empty) │ ~/path/to/quickstart/dbschema │ │ Installation method │ portable package │ - │ Version │ 5.x+cc4f3b5 │ + │ Version │ x.x+cc4f3b5 │ │ Instance name │ quickstart │ └─────────────────────┴───────────────────────────────────────────────┘ Downloading package... 00:00:01 [====================] 41.40 MiB/41.40 MiB 32.89MiB/s | ETA: 0s - Successfully installed 5.x+cc4f3b5 - Initializing EdgeDB instance... + Successfully installed x.x+cc4f3b5 + Initializing Gel instance... Applying migrations... Everything is up to date. Revision initial Project initialized. @@ -172,25 +172,25 @@ This did a couple things. 1. First, it scaffolded your project by creating an :ref:`ref_reference_edgedb_toml` config file and a schema file - ``dbschema/default.esdl``. In the next section, you'll define a schema in - ``default.esdl``. + ``dbschema/default.gel``. In the next section, you'll define a schema in + ``default.gel``. -2. Second, it spun up an EdgeDB instance called ``quickstart`` and "linked" it +2. Second, it spun up an Gel instance called ``quickstart`` and "linked" it to the current directory. As long as you're inside the project directory, all CLI commands will be executed against this - instance. For more details on how EdgeDB projects work, check out the + instance. For more details on how Gel projects work, check out the :ref:`Managing instances ` guide. .. note:: - Quick note! You can have several **instances** of EdgeDB running on your + Quick note! You can have several **instances** of Gel running on your computer simultaneously. Each instance may be **branched** many times. Each branch may have an independent schema consisting of a number of **modules** (though commonly your schema will be entirely defined inside the ``default`` module). Let's connect to our new instance! Run ``edgedb`` in your terminal to open an -interactive REPL to your instance. You're now connected to a live EdgeDB +interactive REPL to your instance. You're now connected to a live Gel instance running on your computer! Try executing a simple query (``select 1 + 1;``) after the REPL prompt (``quickstart:main>``): @@ -219,22 +219,22 @@ see the following file structure. │ ├── migrations EdgeDB schemas are defined with a dedicated schema definition language called -(predictably) EdgeDB SDL (or just **SDL** for short). It's an elegant, +(predictably) Gel SDL (or just **SDL** for short). It's an elegant, declarative way to define your data model. -SDL lives inside ``.esdl`` files. Commonly, your entire schema will be -declared in a file called ``default.esdl`` but you can split your schema -across several ``.esdl`` files if you prefer. +SDL lives inside |.gel| files. Commonly, your entire schema will be +declared in a file called ``default.gel`` but you can split your schema +across several |.gel| files if you prefer. .. note:: - Syntax-highlighter packages/extensions for ``.esdl`` files are available + Syntax-highlighter packages/extensions for |.gel| files are available for `Visual Studio Code `_, `Sublime Text `_, `Atom `_, - and `Vim `_. + and `Vim `_. Let's build a simple movie database. We'll need to define two **object types** (equivalent to a *table* in SQL): Movie and Person. Open @@ -270,10 +270,10 @@ Let's build a simple movie database. We'll need to define two **object types** A few things to note here. -- Our types don't contain an ``id`` property; EdgeDB automatically +- Our types don't contain an ``id`` property; Gel automatically creates this property and assigns a unique UUID to every object inserted into the database. -- The ``Movie`` type includes a **link** named ``actors``. In EdgeDB, links are +- The ``Movie`` type includes a **link** named ``actors``. In Gel, links are used to represent relationships between object types. They eliminate the need for foreign keys; later, you'll see just how easy it is to write "deep" queries without JOINs. @@ -360,7 +360,7 @@ see something a little different than before. required? [y,n,l,c,b,s,q,?] > -As before, EdgeDB parses the schema files and compared them against its +As before, Gel parses the schema files and compared them against its current internal schema. It correctly detects the change we made, and prompts us to confirm it. This interactive process lets you sanity check every change and provide guidance when a migration is ambiguous (e.g. when a property is @@ -379,7 +379,7 @@ Enter ``y`` to confirm the change. fill_expr> {} Hm, now we're seeing another prompt. Because ``title`` is changing from -*optional* to *required*, EdgeDB is asking us what to do for all the ``Movie`` +*optional* to *required*, Gel is asking us what to do for all the ``Movie`` objects that don't currently have a value for ``title`` defined. We'll just specify a placeholder value of "Untitled". Replace the ``{}`` value with ``"Untitled"`` and press Enter. @@ -417,8 +417,8 @@ Let's wrap up by applying the new migration. 5. Write some queries ===================== -Let's write some simple queries via *EdgeDB UI*, the admin dashboard baked -into every EdgeDB instance (v2.0+ only). To open the dashboard: +Let's write some simple queries via *Gel UI*, the admin dashboard baked +into every Gel instance. To open the dashboard: .. code-block:: bash @@ -505,7 +505,7 @@ will look something like this: ] EdgeDB UI is a useful development tool, but in practice your application will -likely be using one of EdgeDB's *client libraries* to execute queries. EdgeDB +likely be using one of Gel's *client libraries* to execute queries. Gel provides official libraries for many langauges: - :ref:`JavaScript/TypeScript ` @@ -529,7 +529,7 @@ Check out the :ref:`Clients Onwards and upwards =================== -You now know the basics of EdgeDB! You've installed the CLI and database, set +You now know the basics of Gel! You've installed the CLI and database, set up a local project, run a couple migrations, inserted and queried some data, and used a client library. @@ -545,9 +545,9 @@ and used a client library. - For a deep dive into the EdgeQL query language, check out the `Interactive Tutorial `_. -- For an immersive, comprehensive walkthrough of EdgeDB concepts, check out - our illustrated e-book `Easy EdgeDB `_; it's designed to walk a - total beginner through EdgeDB, from the basics all the way through advanced +- For an immersive, comprehensive walkthrough of Gel concepts, check out + our illustrated e-book `Easy Gel `_; it's designed to walk a + total beginner through Gel, from the basics all the way through advanced concepts. - To start building an application using the language of your choice, check out diff --git a/docs/intro/schema.rst b/docs/intro/schema.rst index ee7e4f9c5bc..4f223d5ac22 100644 --- a/docs/intro/schema.rst +++ b/docs/intro/schema.rst @@ -5,8 +5,8 @@ Schema ====== -This page is intended as a rapid-fire overview of EdgeDB's schema definition -language (SDL) so you can hit the ground running with EdgeDB. Refer to the +This page is intended as a rapid-fire overview of Gel's schema definition +language (SDL) so you can hit the ground running with Gel. Refer to the linked pages for more in-depth documentation! Scalar types @@ -22,7 +22,7 @@ types. * - Booleans - ``bool`` * - Numbers - - ``int16`` ``int32`` ``int64`` ``float32`` ``float64`` + - ``int16`` ``int32`` ``int64`` ``float32`` ``float64`` ``bigint`` ``decimal`` * - UUID - ``uuid`` @@ -53,7 +53,7 @@ These primitives can be combined into arrays, tuples, and ranges. * - Ranges - ``range`` -Collectively, *primitive* and *collection* types comprise EdgeDB's *scalar +Collectively, *primitive* and *collection* types comprise Gel's *scalar type system*. Object types @@ -199,7 +199,7 @@ Object types can have links to other object types. required name: str; } -The ``link`` keyword can be omitted for non-computed links since EdgeDB v3. +The ``link`` keyword can be omitted for non-computed links since Gel v3. Use the ``required`` and ``multi`` keywords to specify the cardinality of the relation. @@ -371,7 +371,7 @@ understand backlink syntax is to split it into two parts: ``[is Movie]`` This is a *type filter* that filters out all objects that aren't ``Movie`` - objects. A backlink still works without this filter, but could contain any + objects. A backlink still works without this filter, but could contain any other number of objects besides ``Movie`` objects. See :ref:`Schema > Computeds > Backlinks `. diff --git a/docs/reference/admin/branches.rst b/docs/reference/admin/branches.rst index 95b3b4eb6fb..0f3d85cd8bb 100644 --- a/docs/reference/admin/branches.rst +++ b/docs/reference/admin/branches.rst @@ -27,7 +27,7 @@ Create a new branch without schema or data. Description ----------- -The command ``create empty branch`` creates a new EdgeDB branch without schema +The command ``create empty branch`` creates a new Gel branch without schema or data, aside from standard schemas. Examples @@ -54,7 +54,7 @@ Create a new branch copying the schema of an existing branch. Description ----------- -The command ``create schema branch`` creates a new EdgeDB branch with schema +The command ``create schema branch`` creates a new Gel branch with schema copied from an already existing branch. Examples @@ -81,7 +81,7 @@ Create a new branch copying the schema and data of an existing branch. Description ----------- -The command ``create data branch`` creates a new EdgeDB branch with schema and +The command ``create data branch`` creates a new Gel branch with schema and data copied from an already existing branch. Examples diff --git a/docs/reference/admin/configure.rst b/docs/reference/admin/configure.rst index 32b71ff1ec4..1a00e68d79c 100644 --- a/docs/reference/admin/configure.rst +++ b/docs/reference/admin/configure.rst @@ -44,16 +44,16 @@ current session. Some configuration parameters cannot be modified by .. versionchanged:: _default :eql:synopsis:`configure current database` is used to configure an - individual EdgeDB database within a server instance with the + individual Gel database within a server instance with the changes persisted across server restarts. .. versionchanged:: 5.0 :eql:synopsis:`configure current branch` is used to configure an - individual EdgeDB branch within a server instance with the + individual Gel branch within a server instance with the changes persisted across server restarts. -:eql:synopsis:`configure instance` is used to configure the entire EdgeDB +:eql:synopsis:`configure instance` is used to configure the entire Gel instance with the changes persisted across server restarts. This variant acts directly on the file system and cannot be rolled back, so it cannot be used in a transaction block. diff --git a/docs/reference/admin/databases.rst b/docs/reference/admin/databases.rst index 55336b6c6eb..849c4f23301 100644 --- a/docs/reference/admin/databases.rst +++ b/docs/reference/admin/databases.rst @@ -8,9 +8,9 @@ Database .. versionadded:: 5.0 - In EdgeDB 5.0, databases were replaced by :ref:`branches - `. If you're running EdgeDB 5.0 or later, try the - :ref:`branch administrative commands ` instead. + In |EdgeDB| 5, databases were replaced by :ref:`branches + `, use the :ref:`branch administrative commands + ` instead. This section describes the administrative commands pertaining to :ref:`databases `. @@ -30,7 +30,7 @@ Create a new database. Description ----------- -The command ``create database`` creates a new EdgeDB database. +The command ``create database`` creates a new Gel database. The new database will be created with all standard schemas prepopulated. diff --git a/docs/reference/admin/index.rst b/docs/reference/admin/index.rst index 57a096cf69f..4eb523f216e 100644 --- a/docs/reference/admin/index.rst +++ b/docs/reference/admin/index.rst @@ -3,7 +3,7 @@ Administration ============== -Administrative commands for managing EdgeDB: +Administrative commands for managing Gel: * :ref:`configure ` @@ -20,7 +20,7 @@ Administrative commands for managing EdgeDB: .. versionadded:: 5.0 - New administrative commands were added in our EdgeDB 5.0 release: + New administrative commands were added in |EdgeDB| 5 release: * :ref:`branch ` diff --git a/docs/reference/backend_ha.rst b/docs/reference/backend_ha.rst index 29deeb39760..8af526d2f3a 100644 --- a/docs/reference/backend_ha.rst +++ b/docs/reference/backend_ha.rst @@ -4,13 +4,13 @@ Backend high-availability ========================= High availability is a sophisticated and systematic challenge, especially for -databases. To address the problem, EdgeDB server now supports selected +databases. To address the problem, Gel server now supports selected highly-available backend Postgres clusters, namely in 2 categories: * API-based HA * Adaptive HA without API -When the backend HA feature is enabled in EdgeDB, EdgeDB server will try its +When the backend HA feature is enabled in Gel, Gel server will try its best to detect and react to backend failovers, whether a proper API is available or not. @@ -24,23 +24,23 @@ API-based HA ------------ EdgeDB server accepts different types of backends by looking into the protocol -of the ``--backend-dsn`` command-line parameter. EdgeDB supports the following +of the ``--backend-dsn`` command-line parameter. Gel supports the following DSN protocols currently: * ``stolon+consul+http://`` * ``stolon+consul+https://`` -When using these protocols, EdgeDB builds the actual DSN of the cluster's +When using these protocols, Gel builds the actual DSN of the cluster's leader node by calling the corresponding API using credentials in the ``--backend-dsn`` and subscribes to that API for failover events. Once failover -is detected, EdgeDB drops all backend connections and routes all new backend +is detected, Gel drops all backend connections and routes all new backend connections to the new leader node. `Stolon `_ is an open-source cloud native -PostgreSQL manager for PostgreSQL high availability. Currently, EdgeDB supports -using a Stolon cluster as the backend in a Consul-based setup, where EdgeDB +PostgreSQL manager for PostgreSQL high availability. Currently, Gel supports +using a Stolon cluster as the backend in a Consul-based setup, where Gel acts as a Stolon proxy. This way, you only need to manage Stolon sentinels and -keepers, plus a Consul deployment. To use a Stolon cluster, run EdgeDB server +keepers, plus a Consul deployment. To use a Stolon cluster, run Gel server with a DSN, like so: .. code-block:: bash @@ -68,14 +68,14 @@ a switch in addition to a regular backend DSN: --backend-dsn postgres://xxx.rds.amazonaws.com \ --enable-backend-adaptive-ha -Once enabled, EdgeDB server will keep track of unusual backend events like +Once enabled, Gel server will keep track of unusual backend events like unexpected disconnects or Postgres shutdown notifications. When a threshold is -reached, EdgeDB considers the backend to be in the "failover" state. It then +reached, Gel considers the backend to be in the "failover" state. It then drops all current backend connections and try to re-establish new connections -with the same backend DSN. Because EdgeDB doesn't cache resolved DNS values, +with the same backend DSN. Because Gel doesn't cache resolved DNS values, the new connections will be established with the new leader node. -Under the hood of adaptive HA, EdgeDB maintains a state machine to avoid +Under the hood of adaptive HA, Gel maintains a state machine to avoid endless switch-overs in an unstable network. State changes only happen when certain conditions are met. @@ -117,4 +117,4 @@ certain conditions are met. * (and) sys_pgcon is healthy. ("pgcon" is a code name for backend connections, and "sys_pgcon" is a special -backend connection which EdgeDB uses to talk to the "EdgeDB system database".) +backend connection which Gel uses to talk to the "Gel system database".) diff --git a/docs/reference/bindings/datetime.rst b/docs/reference/bindings/datetime.rst index 521ff07d5bc..018c245c9f1 100644 --- a/docs/reference/bindings/datetime.rst +++ b/docs/reference/bindings/datetime.rst @@ -21,7 +21,7 @@ Usually we try to map those types to the respective language-native types, with the following caveats: * The type in standard library -* It has enough range (EdgeDB has timestamps from year 1 to 9999) +* It has enough range (Gel has timestamps from year 1 to 9999) * And it has good enough precision (at least microseconds) If any of the above criteria is not met, we usually provide a custom type in @@ -80,9 +80,9 @@ perform, in particular: 2. Decoding timestamps *and* time deltas from the binary format is precision of native type is lower than microseconds (applies for JavaScript for example) -3. Converting from EdgeDB specific type (if there is one) to native type and +3. Converting from Gel specific type (if there is one) to native type and back (depending on the difference in precision) -4. Parsing a string to an EdgeDB specific type (this operation is optional to +4. Parsing a string to an Gel specific type (this operation is optional to implement, but if it is implemented, it must obey the rules) .. lint-off diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst index 2179cd93054..b4b3364a7bf 100644 --- a/docs/reference/configuration.rst +++ b/docs/reference/configuration.rst @@ -108,7 +108,7 @@ Query behavior .. note:: This setting can also be conveniently accessed via the "Config" dropdown - menu at the top of the EdgeDB UI (accessible by running the CLI command + menu at the top of the Gel UI (accessible by running the CLI command ``edgedb ui`` from within a project). The setting will apply only to your UI session, so you won't have to remember to re-enable it when you're done. diff --git a/docs/reference/connection.rst b/docs/reference/connection.rst index 65383c0cdbc..ba28b19e9bf 100644 --- a/docs/reference/connection.rst +++ b/docs/reference/connection.rst @@ -10,7 +10,7 @@ Connection parameters The CLI and client libraries (collectively referred to as "clients" below) must -connect to an EdgeDB instance to run queries or commands. There are several +connect to an Gel instance to run queries or commands. There are several connection parameters, each of which can be specified in several ways. .. _ref_reference_connection_instance: @@ -18,7 +18,7 @@ connection parameters, each of which can be specified in several ways. Specifying an instance ---------------------- -There are several ways to uniquely identify an EdgeDB instance. +There are several ways to uniquely identify an Gel instance. .. list-table:: @@ -28,7 +28,7 @@ There are several ways to uniquely identify an EdgeDB instance. * - Instance name - ``--instance/-I `` - ``EDGEDB_INSTANCE`` - * - Secret key (required in some EdgeDB Cloud scenarios; see description) + * - Secret key (required in some Gel Cloud scenarios; see description) - ``--secret-key`` - ``EDGEDB_SECRET_KEY`` * - DSN @@ -56,7 +56,7 @@ Let's dig into each of these a bit more. All local instances (instances created on your local machine using the CLI) are associated with a name. This name is what's needed to connect; under the hood, the CLI stores the instance credentials (username, password, etc) on - your file system in the EdgeDB :ref:`config directory + your file system in the Gel :ref:`config directory `. The CLI and client libraries look up these credentials to connect. @@ -65,20 +65,20 @@ Let's dig into each of these a bit more. locally, so you can connect to a remote instance using just its name, just like a local instance. - If you have authenticated with EdgeDB Cloud in the CLI using the - :ref:`ref_cli_edgedb_cloud_login` command, you can address your own EdgeDB + If you have authenticated with Gel Cloud in the CLI using the + :ref:`ref_cli_edgedb_cloud_login` command, you can address your own Gel Cloud instances using the instance name format ``/``. If you are not authenticated, .. _ref_reference_connection_secret_key: **Secret key** - If you want to connect to an EdgeDB Cloud instance in either of these + If you want to connect to an Gel Cloud instance in either of these scenarios: - from a client binding - from the CLI to an instance not belonging to the currently authenticated - EdgeDB Cloud user + Gel Cloud user you will need to provide a secret key in addition to the instance name. Generate a dedicated secret key for the instance via the CLI with @@ -178,9 +178,9 @@ Let's dig into each of these a bit more. Relative paths are resolved relative to the current working directory. **Project-linked instances** - When you run ``edgedb project init`` in a given directory, EdgeDB creates an + When you run ``edgedb project init`` in a given directory, Gel creates an instance and "links" it to that directory. There's nothing magical about this - link; it's just a bit of metadata that gets stored in the EdgeDB config + link; it's just a bit of metadata that gets stored in the Gel config directory. When you use the client libraries or run a CLI command inside a project-linked directory, the library/CLI can detect this, look up the linked instance's credentials, and connect automatically. @@ -193,7 +193,7 @@ Let's dig into each of these a bit more. Priority levels --------------- -The section above describes the various ways of specifying an EdgeDB instance. +The section above describes the various ways of specifying an Gel instance. There are also several ways to provide this configuration information to the client. From highest to lowest priority, you can pass them explicitly as parameters/flags (useful for debugging), use environment variables (recommended @@ -222,7 +222,7 @@ for production), or rely on ``edgedb project`` (recommended for development). .. code-block:: bash $ edgedb --instance my_instance - EdgeDB 2.x + Gel x.x Type \help for help, \quit to quit. edgedb> @@ -230,7 +230,7 @@ for production), or rely on ``edgedb project`` (recommended for development). 2. **Environment variables**. This is the recommended mechanism for providing connection information to - your EdgeDB client, especially in production or when running EdgeDB inside a + your Gel client, especially in production or when running Gel inside a container. All clients read the following variables from the environment: - ``EDGEDB_DSN`` @@ -246,7 +246,7 @@ for production), or rely on ``edgedb project`` (recommended for development). .. code-block:: bash $ edgedb # no flags needed - EdgeDB 2.x + Gel x.x Type \help for help, \quit to quit. edgedb> @@ -273,7 +273,7 @@ for production), or rely on ``edgedb project`` (recommended for development). otherwise specified any connection parameters, the CLI and client libraries will connect to the instance that's been linked to your project. - This makes it easy to get up and running with EdgeDB. Once you've run + This makes it easy to get up and running with Gel. Once you've run ``edgedb project init``, the CLI and client libraries will be able to connect to your database without any explicit flags or parameters, as long as you're inside the project directory. @@ -324,7 +324,7 @@ instance-level configuration object. - N/A **EDGEDB_DATABASE** - Each EdgeDB *instance* can contain multiple *databases*. When an instance is + Each Gel *instance* can contain multiple *databases*. When an instance is created, a default database named ``edgedb`` is created. Unless otherwise specified, all incoming connections connect to the ``edgedb`` database. @@ -351,17 +351,17 @@ instance-level configuration object. - N/A **EDGEDB_BRANCH** - Each EdgeDB *instance* can be branched multiple times. When an instance is + Each Gel *instance* can be branched multiple times. When an instance is created, a default branch named ``main`` is created. For CLI-managed instances, connections are made to the currently active branch. In other cases, incoming connections connect to the ``main`` branch by default. **EDGEDB_USER/EDGEDB_PASSWORD** These are the credentials of the database user account to connect to the - EdgeDB instance. + Gel instance. **EDGEDB_TLS_CA_FILE** - TLS is required to connect to any EdgeDB instance. To do so, the client needs + TLS is required to connect to any Gel instance. To do so, the client needs a reference to the root certificate of your instance's certificate chain. Typically this will be handled for you when you create a local instance or ``link`` a remote one. diff --git a/docs/reference/ddl/future.rst b/docs/reference/ddl/future.rst index 301f5b1396a..010dd057572 100644 --- a/docs/reference/ddl/future.rst +++ b/docs/reference/ddl/future.rst @@ -45,7 +45,7 @@ drop future :eql-statement: -Stop importing future behavior prior to the EdgeDB version in which it appears. +Stop importing future behavior prior to the Gel version in which it appears. .. eql:synopsis:: @@ -57,9 +57,9 @@ Description The command ``drop future`` disables a currently active future behavior for the current :versionreplace:`database;5.0:branch`. However, this is only possible -for versions of EdgeDB when the behavior in question is not officially +for versions of Gel when the behavior in question is not officially introduced. Once a particular behavior is introduced as the standard behavior -in an EdgeDB release, it cannot be disabled. Running this command will simply +in an Gel release, it cannot be disabled. Running this command will simply denote that no special action is needed to enable it in this case. @@ -76,6 +76,6 @@ defining other access policies: drop future nonrecursive_access_policies; -Once EdgeDB 3.0 is released there is no more need for enabling non-recursive +Since |EdgeDB| 3.0 was released there is no more need for enabling non-recursive access policy behavior anymore. So the above command will simply indicate that the database no longer does anything non-standard. diff --git a/docs/reference/ddl/index.rst b/docs/reference/ddl/index.rst index 36207ac8b06..b2a12f11e74 100644 --- a/docs/reference/ddl/index.rst +++ b/docs/reference/ddl/index.rst @@ -29,7 +29,7 @@ DDL EdgeQL includes a set of *data definition language* (DDL) commands that manipulate the database's schema. DDL is the low-level equivalent to -:ref:`EdgeDB schema definition language `. You can execute DDL +:ref:`Gel schema definition language `. You can execute DDL commands against your database, just like any other EdgeQL query. .. code-block:: edgeql-repl @@ -50,7 +50,7 @@ to ``Person``. Under the hood, all migrations are represented as DDL scripts: a sequence of imperative commands representing the migration. When you :ref:`create a -migration ` with the CLI, EdgeDB produces a DDL script. +migration ` with the CLI, Gel produces a DDL script. Comparison to SDL diff --git a/docs/reference/ddl/migrations.rst b/docs/reference/ddl/migrations.rst index 25a778bba31..b6b2cc23c33 100644 --- a/docs/reference/ddl/migrations.rst +++ b/docs/reference/ddl/migrations.rst @@ -31,7 +31,7 @@ Parameters ---------- :eql:synopsis:`` - Complete schema defined with the declarative :ref:`EdgeDB schema + Complete schema defined with the declarative :ref:`Gel schema definition language`. Description @@ -70,7 +70,7 @@ available: Examples -------- -Create a new migration to a target schema specified by the EdgeDB Schema +Create a new migration to a target schema specified by the Gel Schema syntax: .. code-block:: edgeql @@ -125,7 +125,7 @@ and records the migration into the system migration log. Examples -------- -Create a new migration to a target schema specified by the EdgeDB Schema +Create a new migration to a target schema specified by the Gel Schema syntax: .. code-block:: edgeql diff --git a/docs/reference/edgedb_toml.rst b/docs/reference/edgedb_toml.rst index 36fa602f91e..5c14806e09b 100644 --- a/docs/reference/edgedb_toml.rst +++ b/docs/reference/edgedb_toml.rst @@ -1,10 +1,10 @@ .. _ref_reference_edgedb_toml: -=========== -edgedb.toml -=========== +======== +gel.toml +======== -The ``edgedb.toml`` file is created in the project root after running +The |gel.toml| file is created in the project root after running :ref:`ref_cli_edgedb_project_init`. If this file is present in a directory, it signals to the CLI and client bindings that the directory is an instance-linked EdgeDB project. It supports two configuration settings across two tables: @@ -19,16 +19,16 @@ EdgeDB project. It supports two configuration settings across two tables: ``[edgedb]`` table ================== -- ``server-version``- The server version of the EdgeDB project. +- ``server-version``- The server version of the Gel project. .. note:: The version specification is assumed to be **a minimum version**, but the CLI will *not* upgrade to subsequent major versions. This means if the - version specified is ``3.1`` and versions 3.2 and 3.3 are available, 3.3 - will be installed, even if version 4.0 is also available. + version specified is ``6.1`` and versions 6.2 and 6.3 are available, 6.3 + will be installed, even if version 7.0 is also available. - To specify an exact version, prepend with ``=`` like this: ``=3.1``. We + To specify an exact version, prepend with ``=`` like this: ``=6.1``. We support `all of the same version specifications as Cargo`_, Rust's package manager. @@ -45,8 +45,8 @@ Example .. code-block:: toml - [edgedb] - server-version = "3.1" + [gel] + server-version = "6.0" [project] schema-dir = "db/schema" diff --git a/docs/reference/edgeql/cardinality.rst b/docs/reference/edgeql/cardinality.rst index 052ebfd121a..88ea69d6c40 100644 --- a/docs/reference/edgeql/cardinality.rst +++ b/docs/reference/edgeql/cardinality.rst @@ -12,7 +12,7 @@ Terminology ----------- The term **cardinality** is used to refer to both the *exact* number of -elements in a given set or a *range* of possible values. Internally, EdgeDB +elements in a given set or a *range* of possible values. Internally, Gel tracks 5 different cardinality ranges: ``Empty`` (zero elements), ``One`` (a singleton set), ``AtMostOne`` (zero or one elements), ``AtLeastOne`` (one or more elements), and ``Many`` (any number of elements). @@ -27,14 +27,14 @@ assigned in question *must* have a cardinality of ``One`` or ``AtLeastOne`` Functions and operators ----------------------- -It's often useful to think of EdgeDB functions/operators as either +It's often useful to think of Gel functions/operators as either *element-wise* or *aggregate*. Element-wise operations are applied to *each item* in a set. Aggregate operations operate on sets *as a whole*. .. note:: This is a simplification, but it's a useful mental model when getting - started with EdgeDB. + started with Gel. .. _ref_reference_cardinality_aggregate: @@ -95,7 +95,7 @@ operation is applied to a cartesian product of all the input sets. {true, true, true, false} By extension, if any of the input sets are empty, the result of applying an -element-wise function is also empty. In effect, when EdgeDB detects an empty +element-wise function is also empty. In effect, when Gel detects an empty set, it "short-circuits" and returns an empty set without applying the operation. diff --git a/docs/reference/edgeql/casts.rst b/docs/reference/edgeql/casts.rst index c51f32e0af9..01bd31ab5bc 100644 --- a/docs/reference/edgeql/casts.rst +++ b/docs/reference/edgeql/casts.rst @@ -142,7 +142,7 @@ Casting Table .. note:: - The UUID-to-object cast is only available in EdgeDB 3.0+. + The UUID-to-object cast is only available since |EdgeDB| 3.0+. .. This file is automatically generated by `make casts`: .. csv-table:: diff --git a/docs/reference/edgeql/group.rst b/docs/reference/edgeql/group.rst index f2d28531948..c404663fa30 100644 --- a/docs/reference/edgeql/group.rst +++ b/docs/reference/edgeql/group.rst @@ -10,10 +10,6 @@ Group :index: group using by -.. note:: - - The ``group`` statement is only available in EdgeDB 2.0 or later. - ``group``--partition a set into subsets based on one or more keys .. eql:synopsis:: diff --git a/docs/reference/edgeql/insert.rst b/docs/reference/edgeql/insert.rst index 81c16b20eec..bc56006012a 100644 --- a/docs/reference/edgeql/insert.rst +++ b/docs/reference/edgeql/insert.rst @@ -67,9 +67,7 @@ See :ref:`ref_eql_forstatement` for more details. parent type. The specified *property-expr* may be either a reference to a property (or - link) or a tuple of references to properties (or links). Although versions - prior to 2.10 do *not* support ``unless conflict`` on :ref:`multi - properties `, 2.10 adds support for these. + link) or a tuple of references to properties (or links). A caveat, however, is that ``unless conflict`` will not prevent conflicts caused between multiple DML operations in the same diff --git a/docs/reference/edgeql/shapes.rst b/docs/reference/edgeql/shapes.rst index 61596448a7b..5ea8c9b0073 100644 --- a/docs/reference/edgeql/shapes.rst +++ b/docs/reference/edgeql/shapes.rst @@ -118,7 +118,7 @@ This achieves a couple of things: it's easier to see which friends belong to which user and we no longer need the placeholder ``''`` for those users who don't have friends. -The recommended way to get this information in EdgeDB, however, is to +The recommended way to get this information in Gel, however, is to use *shapes*, because they mimic the structure of the data and the output: .. code-block:: edgeql-repl @@ -260,7 +260,7 @@ leaking them into the output. General Shaping Rules ===================== -In EdgeDB typically all shapes appearing in the top-level +In Gel typically all shapes appearing in the top-level :eql:stmt:`select` should be reflected in the output. This also applies to shapes no matter where and how they are nested. Aside from other shapes, this includes nesting in arrays: diff --git a/docs/reference/edgeql/tx_commit.rst b/docs/reference/edgeql/tx_commit.rst index 67cd94654a1..c4dc157eca9 100644 --- a/docs/reference/edgeql/tx_commit.rst +++ b/docs/reference/edgeql/tx_commit.rst @@ -1,5 +1,5 @@ .. - Portions Copyright (c) 2019 MagicStack Inc. and the EdgeDB authors. + Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors. Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/docs/reference/edgeql/tx_rollback.rst b/docs/reference/edgeql/tx_rollback.rst index d5fa1e9cc8e..b66c0851e39 100644 --- a/docs/reference/edgeql/tx_rollback.rst +++ b/docs/reference/edgeql/tx_rollback.rst @@ -1,5 +1,5 @@ .. - Portions Copyright (c) 2019 MagicStack Inc. and the EdgeDB authors. + Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors. Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/docs/reference/edgeql/tx_sp_declare.rst b/docs/reference/edgeql/tx_sp_declare.rst index a19e1c5432e..fe499192b83 100644 --- a/docs/reference/edgeql/tx_sp_declare.rst +++ b/docs/reference/edgeql/tx_sp_declare.rst @@ -1,5 +1,5 @@ .. - Portions Copyright (c) 2019 MagicStack Inc. and the EdgeDB authors. + Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors. Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/docs/reference/edgeql/tx_sp_release.rst b/docs/reference/edgeql/tx_sp_release.rst index 440d8e0ba96..0a56db65008 100644 --- a/docs/reference/edgeql/tx_sp_release.rst +++ b/docs/reference/edgeql/tx_sp_release.rst @@ -1,5 +1,5 @@ .. - Portions Copyright (c) 2019 MagicStack Inc. and the EdgeDB authors. + Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors. Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/docs/reference/edgeql/tx_sp_rollback.rst b/docs/reference/edgeql/tx_sp_rollback.rst index 850fc86720f..04f39a6b939 100644 --- a/docs/reference/edgeql/tx_sp_rollback.rst +++ b/docs/reference/edgeql/tx_sp_rollback.rst @@ -1,5 +1,5 @@ .. - Portions Copyright (c) 2019 MagicStack Inc. and the EdgeDB authors. + Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors. Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California diff --git a/docs/reference/edgeql/tx_start.rst b/docs/reference/edgeql/tx_start.rst index 9562ee3cc42..1e4dd551c6b 100644 --- a/docs/reference/edgeql/tx_start.rst +++ b/docs/reference/edgeql/tx_start.rst @@ -1,5 +1,5 @@ .. - Portions Copyright (c) 2019 MagicStack Inc. and the EdgeDB authors. + Portions Copyright (c) 2019 MagicStack Inc. and the Gel authors. Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group Portions Copyright (c) 1994, The Regents of the University of California @@ -48,7 +48,7 @@ Description This command starts a new transaction block. -Any EdgeDB command outside of an explicit transaction block starts +Any Gel command outside of an explicit transaction block starts an implicit transaction block; the transaction is then automatically committed if the command was executed successfully, or automatically rollbacked if there was an error. This behavior is often called @@ -61,12 +61,12 @@ Parameters The :eql:synopsis:`` can be one of the following: :eql:synopsis:`isolation serializable` - All statements in the current transaction can only see data - changes that were committed before the first query or data - modification statement was executed within this transaction. + All statements in the current transaction can only see data + changes that were committed before the first query or data + modification statement was executed within this transaction. If a pattern of reads and writes among concurrent serializable - transactions creates a situation that could not have occurred - in any serial (one-at-a-time) execution of those transactions, + transactions creates a situation that could not have occurred + in any serial (one-at-a-time) execution of those transactions, one of them will be rolled back with a serialization_failure error. :eql:synopsis:`read write` diff --git a/docs/reference/environment.rst b/docs/reference/environment.rst index 7e052ae75e2..a4977de1400 100644 --- a/docs/reference/environment.rst +++ b/docs/reference/environment.rst @@ -3,7 +3,7 @@ Environment Variables ===================== -The behavior of EdgeDB can be configured with environment variables. The +The behavior of Gel can be configured with environment variables. The variables documented on this page are supported when using the ``edgedb-server`` tool and the official :ref:`Docker image `. @@ -79,7 +79,7 @@ if this is set. EDGEDB_SERVER_BINARY .................... -Sets the EdgeDB server binary to run. Default is ``edgedb-server``. +Sets the Gel server binary to run. Default is ``edgedb-server``. EDGEDB_SERVER_BOOTSTRAP_COMMAND_FILE @@ -132,7 +132,7 @@ assumed. EDGEDB_SERVER_EXTRA_ARGS ........................ -Additional arguments to pass when starting the EdgeDB server. +Additional arguments to pass when starting the Gel server. EDGEDB_SERVER_GENERATE_SELF_SIGNED_CERT @@ -182,7 +182,7 @@ default. EDGEDB_SERVER_TENANT_ID ....................... -Specifies the tenant ID of this server when hosting multiple EdgeDB instances +Specifies the tenant ID of this server when hosting multiple Gel instances on one Postgres cluster. Must be an alphanumeric ASCII string, maximum 10 characters long. @@ -206,17 +206,17 @@ password set in ``EDGEDB_SERVER_PASSWORD`` or the hash set in Server variables ---------------- -These variables will work whether you are running EdgeDB inside Docker or not. +These variables will work whether you are running Gel inside Docker or not. EDGEDB_DEBUG_HTTP_INJECT_CORS ............................. -Set to ``1`` to have EdgeDB send appropriate CORS headers with HTTP responses. +Set to ``1`` to have Gel send appropriate CORS headers with HTTP responses. .. note:: - This is set to ``1`` by default for EdgeDB Cloud instances. + This is set to ``1`` by default for Gel Cloud instances. .. _ref_reference_envvar_admin_ui: @@ -277,8 +277,8 @@ and ``*_ENV`` variants are also supported. EDGEDB_SERVER_MAX_BACKEND_CONNECTIONS ..................................... -The maximum NUM of connections this EdgeDB instance could make to the backend -PostgreSQL cluster. If not set, EdgeDB will detect and calculate the NUM: +The maximum NUM of connections this Gel instance could make to the backend +PostgreSQL cluster. If not set, Gel will detect and calculate the NUM: RAM/100MiB for local Postgres, or pg_settings.max_connections for remote Postgres minus the NUM of ``--reserved-pg-connections``. @@ -296,7 +296,7 @@ Specifies the security mode of the server's binary endpoint. When set to EDGEDB_SERVER_BIND_ADDRESS .......................... -Specifies the network interface on which EdgeDB will listen. +Specifies the network interface on which Gel will listen. Maps directly to the ``edgedb-server`` flag ``--bind-address``. The ``*_FILE`` and ``*_ENV`` variants are also supported. @@ -417,7 +417,7 @@ Set the logging level. Default is ``info``. Other possible values are EDGEDB_SERVER_PORT .................. -Specifies the network port on which EdgeDB will listen. Default is ``5656``. +Specifies the network port on which Gel will listen. Default is ``5656``. Maps directly to the ``edgedb-server`` flag ``--port``. The ``*_FILE`` and ``*_ENV`` variants are also supported. @@ -444,7 +444,7 @@ and ``*_ENV`` variants are also supported. EDGEDB_SERVER_RUNSTATE_DIR .......................... -Specifies a path where EdgeDB will place its Unix socket and other transient +Specifies a path where Gel will place its Unix socket and other transient files. Maps directly to the ``edgedb-server`` flag ``--runstate-dir``. diff --git a/docs/reference/http.rst b/docs/reference/http.rst index 35062efb156..b5d318c52d9 100644 --- a/docs/reference/http.rst +++ b/docs/reference/http.rst @@ -3,7 +3,7 @@ HTTP API ======== -Using HTTP, you may check the health of your EdgeDB instance, check metrics on +Using HTTP, you may check the health of your Gel instance, check metrics on your instance, and make queries. @@ -19,15 +19,15 @@ your instance, and make queries. .. note:: - Here's how to determine your local EdgeDB instance's HTTP server URL: + Here's how to determine your local Gel instance's HTTP server URL: .. versionchanged:: _default - The ``hostname`` will be ``localhost`` - Find the ``port`` by running ``edgedb instance list``. This will print a - table of all EdgeDB instances on your machine, including their associated + table of all Gel instances on your machine, including their associated port number. - - In most cases, ``database-name`` will be ``edgedb``. An EdgeDB *instance* + - In most cases, ``database-name`` will be ``edgedb``. An Gel *instance* can contain multiple databases. On initialization, a default database called ``edgedb`` is created; all queries are executed against this database unless otherwise specified. @@ -36,9 +36,9 @@ your instance, and make queries. - The ``hostname`` will be ``localhost`` - Find the ``port`` by running ``edgedb instance list``. This will print a - table of all EdgeDB instances on your machine, including their associated + table of all Gel instances on your machine, including their associated port number. - - The default branch in your EdgeDB database is ``main``. Use this for + - The default branch in your Gel database is ``main``. Use this for ```` unless you want to query a different branch. To determine the URL of a remote instance you have linked with the CLI, you @@ -93,7 +93,7 @@ Retrieve instance metrics. http://:/metrics -All EdgeDB instances expose a Prometheus-compatible endpoint available via GET +All Gel instances expose a Prometheus-compatible endpoint available via GET request. The following metrics are made available. System @@ -235,7 +235,7 @@ Your instance is now able to receive EdgeQL queries over HTTP. Making a query request ^^^^^^^^^^^^^^^^^^^^^^ -Make a query to your EdgeDB database using this URL: +Make a query to your Gel database using this URL: .. versionchanged:: _default @@ -263,7 +263,7 @@ submit a JSON payload with ``query`` and ``variables`` as top-level keys in that payload as in this example: Here's an example query you might want to run to insert a new person in your -database, as executed from the EdgeDB REPL: +database, as executed from the Gel REPL: .. code-block:: edgeql-repl diff --git a/docs/reference/projects.rst b/docs/reference/projects.rst index f1e79ceee7c..29236716abe 100644 --- a/docs/reference/projects.rst +++ b/docs/reference/projects.rst @@ -4,7 +4,7 @@ Create a project ================ -Projects are the most convenient way to develop applications with EdgeDB. This +Projects are the most convenient way to develop applications with Gel. This is the recommended approach. To get started, navigate to the root directory of your codebase in a shell and @@ -16,29 +16,29 @@ run ``edgedb project init``. You'll see something like this: No `edgedb.toml` found in this repo or above. Do you want to initialize a new project? [Y/n] > Y - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [1-rc3]: + Checking Gel versions... + Specify the version of Gel to use with this project [1-rc3]: > # left blank for default - Specify the name of EdgeDB instance to use with this project: + Specify the name of Gel instance to use with this project: > my_instance - Initializing EdgeDB instance... + Initializing Gel instance... Bootstrap complete. Server is up and running now. Project initialialized. Let's unpack that. -1. First, it asks you to specify an EdgeDB version, defaulting to the most +1. First, it asks you to specify an Gel version, defaulting to the most recent version you have installed. You can also specify a version you *don't* have installed, in which case it will be installed. -2. Then it asks you how you'd like to run EdgeDB: locally, in a Docker image, +2. Then it asks you how you'd like to run Gel: locally, in a Docker image, or in the cloud (coming soon!). 3. Then it asks for an instance name. If no instance currently exists with this name, it will be created (using the method you specified in #2). 4. Then it **links** the current directory to that instance. A "link" is - represented as some metadata stored in EdgeDB's :ref:`config directory + represented as some metadata stored in Gel's :ref:`config directory `—feel free to peek inside to see how it's stored. 5. Then it creates an :ref:`ref_reference_edgedb_toml` file, which marks this - directory as an EdgeDB project. + directory as an Gel project. 6. Finally, it creates a ``dbschema`` directory and a ``dbschema/default.esdl`` schema file (if they don't already exist). @@ -73,7 +73,7 @@ What do you mean *link*? ^^^^^^^^^^^^^^^^^^^^^^^^ The "link" is just metaphor that makes projects easier to think about; in -practice, it's just a bit of metadata we store in the EdgeDB :ref:`config +practice, it's just a bit of metadata we store in the Gel :ref:`config directory `. When the CLI or client libraries try to connect to an instance, they read the currect directory and cross-reference it against the list of initialized projects. If there's a match, it reads the @@ -83,7 +83,7 @@ How does this work in production? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It doesn't. Projects are intended as a convenient development tool that make it -easier to develop EdgeDB-backed applications locally. In production, you should +easier to develop Gel-backed applications locally. In production, you should provide instance credentials to your client library of choice using environment variables. See :ref:`Connection parameters ` page for more information. @@ -95,7 +95,7 @@ What's the ``edgedb.toml`` file? The most important role of ``edgedb.toml`` is to mark a directory as an instance-linked project, but it can also specify the server version and the schema directory for a project. The server version value in the generated -``edgedb.toml`` is determined by the EdgeDB version you selected when you ran +``edgedb.toml`` is determined by the Gel version you selected when you ran :ref:`ref_cli_edgedb_project_init`. Read :ref:`our reference documentation on edgedb.toml @@ -111,8 +111,8 @@ Read :ref:`our reference documentation on edgedb.toml How do I use ``edgedb project`` for existing codebases? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you already have an project on your computer that uses EdgeDB, follow these -steps to convert it into an EdgeDB project: +If you already have an project on your computer that uses Gel, follow these +steps to convert it into an Gel project: 1. Navigate into the project directory (the one containing you ``dbschema`` directory). @@ -127,7 +127,7 @@ Feels good, right? How does this make projects more portable? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Let's say you just cloned a full-stack application that uses EdgeDB. The +Let's say you just cloned a full-stack application that uses Gel. The project directory already contains an ``edgedb.toml`` file. What do you do? Just run ``edgedb project init`` inside the directory! This is the beauty of @@ -135,7 +135,7 @@ Just run ``edgedb project init`` inside the directory! This is the beauty of particular name, running on a particular port, creating users and passwords, specifying environment variables, or any of the other things that make setting up local databases hard. Running ``edgedb project init`` will install the -necessary version of EdgeDB (if you don't already have it installed), create an +necessary version of Gel (if you don't already have it installed), create an instance, apply all unapplied migrations. Then you can start up the application and it should work out of the box. @@ -155,25 +155,25 @@ project init`` inside project again to create or select a new instance. No `edgedb.toml` found in `~/path/to/my_project` or above. Do you want to initialize a new project? [Y/n] > Y - Specify the name of EdgeDB instance to use with this project + Specify the name of Gel instance to use with this project [default: my_project]: > my_project - Checking EdgeDB versions... - Specify the version of EdgeDB to use with this project [default: 2.x]: - > 2.x + Checking Gel versions... + Specify the version of Gel to use with this project [default: x.x]: + > x.x How do I use ``edgedb project`` with a non-local instance? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Sometimes you may want to work on an EdgeDB instance that is just not in your +Sometimes you may want to work on an Gel instance that is just not in your local development environment, like you may have a second workstation, or you want to test against a staging database shared by the team. -This is totally a valid case and EdgeDB fully supports it! +This is totally a valid case and Gel fully supports it! Before running ``edgedb project init``, you just need to create a local link to -the remote EdgeDB instance first: +the remote Gel instance first: .. TODO: Will need to change this once https://github.com/edgedb/edgedb-cli/issues/1269 is resolved diff --git a/docs/reference/protocol/dataformats.rst b/docs/reference/protocol/dataformats.rst index 84669be6089..1f8763f9b15 100644 --- a/docs/reference/protocol/dataformats.rst +++ b/docs/reference/protocol/dataformats.rst @@ -4,7 +4,7 @@ Data wire formats ================= -This section describes the data wire format of standard EdgeDB types. +This section describes the data wire format of standard Gel types. .. _ref_protocol_fmt_array: @@ -406,7 +406,7 @@ std::datetime :eql:type:`std::datetime` values are represented as a 64-bit integer, most sigificant byte first. The value is the number of *microseconds* between the encoded datetime and January 1st 2000, 00:00 UTC. A Unix -timestamp can be converted into an EdgeDB ``datetime`` value using this +timestamp can be converted into an Gel ``datetime`` value using this formula: .. code-block:: c diff --git a/docs/reference/protocol/errors.rst b/docs/reference/protocol/errors.rst index bbff21a29d5..612c11f9af6 100644 --- a/docs/reference/protocol/errors.rst +++ b/docs/reference/protocol/errors.rst @@ -7,14 +7,14 @@ Errors Errors inheritance ================== -Each error in EdgeDB consists of a code, a name, and optionally tags. Errors -in EdgeDB can inherit from other errors. This is denoted by matching code +Each error in Gel consists of a code, a name, and optionally tags. Errors +in Gel can inherit from other errors. This is denoted by matching code prefixes. For example, ``TransactionConflictError`` (``0x_05_03_01_00``) is the parent error for ``TransactionSerializationError`` (``0x_05_03_01_01``) and ``TransactionDeadlockError`` (``0x_05_03_01_02``). The matching prefix here is ``0x_05_03_01``. -When the EdgeDB client expects a more general error and EdgeDB returns a more +When the Gel client expects a more general error and Gel returns a more specific error that inherits from the general error, the check in the client must take this into account. This can be expressed by the ``binary and`` operation or ``&`` opeator in most programming languages: diff --git a/docs/reference/protocol/index.rst b/docs/reference/protocol/index.rst index d1975ca8ae7..63945110415 100644 --- a/docs/reference/protocol/index.rst +++ b/docs/reference/protocol/index.rst @@ -20,11 +20,11 @@ clients and servers. The protocol is supported over TCP/IP. .. _ref_protocol_connecting: -Connecting to EdgeDB -==================== +Connecting to Gel +================= -The EdgeDB binary protocol has two modes of operation: sockets and HTTP -tunnelling. When connecting to EdgeDB, the client can specify an accepted +The Gel binary protocol has two modes of operation: sockets and HTTP +tunnelling. When connecting to Gel, the client can specify an accepted `ALPN Protocol`_ to use. If the client does not specify an ALPN protocol, HTTP tunnelling is assumed. @@ -88,7 +88,7 @@ the client should abort the authentication attempt. the HTTP connection. The client then sends any following message to ``/branch/{BRANCH}`` with the following headers: -* ``X-EdgeDB-User``: The username specified in the +* ``X-Gel-User``: The username specified in the :ref:`connection parameters `. * ``Authorization``: The authorization token received from the @@ -169,7 +169,7 @@ The following data types are used in the descriptions: Message Format ============== -All messages in the EdgeDB wire protocol have the following format: +All messages in the Gel wire protocol have the following format: .. code-block:: c @@ -211,7 +211,7 @@ message and continue as before. Message Flow ============ -There are two main phases in the lifetime of an EdgeDB connection: the +There are two main phases in the lifetime of an Gel connection: the connection phase, and the command phase. The connection phase is responsible for negotiating the protocol and connection parameters, including authentication. The command phase is the regular operation phase where the @@ -239,7 +239,7 @@ the connection if protocol version is unsupported. Server *MUST* send subset of the extensions received in :ref:`ref_protocol_msg_client_handshake` (i.e. it never adds extra ones). -While it's not required by the protocol specification itself, EdgeDB server +While it's not required by the protocol specification itself, Gel server currently requires setting the following params in :ref:`ref_protocol_msg_client_handshake`: diff --git a/docs/reference/protocol/messages.rst b/docs/reference/protocol/messages.rst index 5a94a776b89..d7c3e38021f 100644 --- a/docs/reference/protocol/messages.rst +++ b/docs/reference/protocol/messages.rst @@ -609,7 +609,7 @@ by the server. .. note:: At the moment, the only SASL authentication method supported - by EdgeDB is ``SCRAM-SHA-256`` + by Gel is ``SCRAM-SHA-256`` (`RFC 7677 `_). The client must select an appropriate authentication method from the list diff --git a/docs/reference/protocol/typedesc.rst b/docs/reference/protocol/typedesc.rst index 5e85962b11a..e8cc9cfc925 100644 --- a/docs/reference/protocol/typedesc.rst +++ b/docs/reference/protocol/typedesc.rst @@ -15,12 +15,12 @@ The type descriptor is essentially a list of type information *blocks*: * *blocks* can reference other *blocks*. While parsing the *blocks*, a database driver can assemble an -*encoder* or a *decoder* of the EdgeDB binary data. +*encoder* or a *decoder* of the Gel binary data. An *encoder* is used to encode objects, native to the driver's runtime, -to binary data that EdgeDB can decode and work with. +to binary data that Gel can decode and work with. -A *decoder* is used to decode data from EdgeDB native format to +A *decoder* is used to decode data from Gel native format to data types native to the driver. .. versionchanged:: _default @@ -106,7 +106,7 @@ Scalar Type Descriptor }; The descriptor IDs for fundamental scalar types are constant. -The following table lists all EdgeDB fundamental type descriptor IDs: +The following table lists all Gel fundamental type descriptor IDs: .. list-table:: :header-rows: 1 diff --git a/docs/reference/sdl/index.rst b/docs/reference/sdl/index.rst index cca47ba4927..53a7cc6a294 100644 --- a/docs/reference/sdl/index.rst +++ b/docs/reference/sdl/index.rst @@ -6,22 +6,22 @@ SDL :edb-alt-title: Schema Definition Language -This section describes the high-level language used to define EdgeDB -schema. It is called the EdgeDB *schema definition language* or +This section describes the high-level language used to define Gel +schema. It is called the Gel *schema definition language* or *SDL*. There's a correspondence between this declarative high-level language and the imperative low-level :ref:`DDL `. .. versionchanged:: _default SDL is a declarative language optimized for human readability and - expressing the state of the EdgeDB schema without getting into the details + expressing the state of the Gel schema without getting into the details of how to arrive at that state. Each *SDL* block represents the complete schema state for a given :ref:`database `. .. versionchanged:: 5.0 SDL is a declarative language optimized for human readability and - expressing the state of the EdgeDB schema without getting into the details + expressing the state of the Gel schema without getting into the details of how to arrive at that state. Each *SDL* block represents the complete schema state for a given :ref:`branch `. @@ -33,7 +33,7 @@ nested in their respective modules. .. versionadded:: 3.0 - EdgeDB 3.0 introduces a new SDL syntax which diverges slightly from DDL. + |EdgeDB| 3.0 introduces a new SDL syntax which diverges slightly from DDL. The old SDL syntax is still fully supported, but the new syntax allows for cleaner and less verbose expression of your schemas. @@ -50,7 +50,7 @@ nested in their respective modules. required property email -> str; } - could be replaced with this equivalent one in EdgeDB 3+: + could be replaced with this equivalent one in |EdgeDB| 3+ / Gel: .. code-block:: sdl diff --git a/docs/reference/sdl/links.rst b/docs/reference/sdl/links.rst index f90a05ff702..24f17a356ad 100644 --- a/docs/reference/sdl/links.rst +++ b/docs/reference/sdl/links.rst @@ -285,7 +285,7 @@ The following options are available: .. versionadded:: 3.0 - As of EdgeDB 3.0, the ``extending`` clause is now a sub-declaration of + As of |EdgeDB| 3.0, the ``extending`` clause is now a sub-declaration of the link and included inside the curly braces rather than an option as in earlier versions. diff --git a/docs/reference/sdl/properties.rst b/docs/reference/sdl/properties.rst index 42817e68da4..ce304438b19 100644 --- a/docs/reference/sdl/properties.rst +++ b/docs/reference/sdl/properties.rst @@ -255,7 +255,7 @@ The following options are available: .. versionadded:: 3.0 - As of EdgeDB 3.0, the ``extended`` clause is now a sub-declaration of + As of |EdgeDB| 3.0, the ``extended`` clause is now a sub-declaration of the property and included inside the curly braces rather than an option as in earlier versions. diff --git a/docs/reference/sql_adapter.rst b/docs/reference/sql_adapter.rst index 2cecdf8fa02..06548dbd987 100644 --- a/docs/reference/sql_adapter.rst +++ b/docs/reference/sql_adapter.rst @@ -14,16 +14,16 @@ Connecting EdgeDB server supports PostgreSQL connection interface. It implements PostgreSQL wire protocol as well as SQL query language. -As of EdgeDB 6.0, it also supports a subset of Data Modification Language, +As of |Gel| 6.0, it also supports a subset of Data Modification Language, namely INSERT, DELETE and UPDATE statements. It does not, however, support PostgreSQL Data Definition Language (e.g. ``CREATE TABLE``). This means that it is not possible to use SQL -connections to EdgeDB to modify its schema. Instead, the schema should be -managed using ESDL (EdgeDB Schema Definition Language) and migration commands. +connections to Gel to modify its schema. Instead, the schema should be +managed using ESDL (Gel Schema Definition Language) and migration commands. -Any Postgres-compatible client can connect to an EdgeDB database by using the -same port that is used for the EdgeDB protocol and the +Any Postgres-compatible client can connect to an Gel database by using the +same port that is used for the Gel protocol and the :versionreplace:`database;5.0:branch` name, username, and password already used for the database. @@ -59,7 +59,7 @@ for the database. .. note:: - The insecure DSN returned by the CLI for EdgeDB Cloud instances will not + The insecure DSN returned by the CLI for Gel Cloud instances will not contain the password. You will need to either :ref:`create a new role and set the password `, using those values to connect to your SQL client, or change the password of the existing role, using that @@ -74,7 +74,7 @@ for the database. .. warning:: - Connecting to an EdgeDB Cloud instance via a Postgres client requires SNI + Connecting to an Gel Cloud instance via a Postgres client requires SNI support which was introduced in libpq v14. If a Postgres client uses your system's libpq (``psql`` does), you can connect as long as your libpq version is 14+. To check your version, run ``psql --version`` or @@ -118,9 +118,9 @@ In this example, when prompted for the password, you would enter .. warning:: - EdgeDB server requires TLS by default, and this is also true for our SQL + Gel server requires TLS by default, and this is also true for our SQL support. Make sure to require SSL encryption in your SQL tool or client - when using EdgeDB's SQL support. Alternatively, you can disable the TLS + when using Gel's SQL support. Alternatively, you can disable the TLS requirement by setting the ``EDGEDB_SERVER_BINARY_ENDPOINT_SECURITY`` environment variable to ``optional``. @@ -128,8 +128,8 @@ In this example, when prompted for the password, you would enter Querying ======== -Object types in your EdgeDB schema are exposed as regular SQL tables containing -all the data you store in your EdgeDB database. +Object types in your Gel schema are exposed as regular SQL tables containing +all the data you store in your Gel database. If you have a database with the following schema: @@ -253,16 +253,16 @@ Tested SQL tools - `dbt `_ [2]_ -.. [1] At the moment, EdgeDB does not support "Log replication" (i.e., using +.. [1] At the moment, Gel does not support "Log replication" (i.e., using the `Postgres replication mechanism`_). Supported replication methods include `XMIN Replication`_, incremental updates using "a user-defined monotonically increasing id," and full table updates. .. [2] dbt models are built and stored in the database as either tables or - views. Because the EdgeDB SQL adapter does not allow writing or even + views. Because the Gel SQL adapter does not allow writing or even creating schemas, view, or tables, any attempt to materialize dbt models will result in errors. If you want to build the models, we suggest first transferring your data to a true Postgres instance via pg_dump or Airbyte. - Tests and previews can still be run directy against the EdgeDB instance. + Tests and previews can still be run directy against the Gel instance. .. _Postgres replication mechanism: https://www.postgresql.org/docs/current/runtime-config-replication.html @@ -273,7 +273,7 @@ Tested SQL tools ESDL to PostgreSQL ================== -As mentioned, the SQL schema of the database is managed trough EdgeDB Schema +As mentioned, the SQL schema of the database is managed trough Gel Schema Definition Language (ESDL). Here is a breakdown of how each of the ESDL construct is mapped to PostgreSQL schema: @@ -318,33 +318,26 @@ construct is mapped to PostgreSQL schema: DML commands ============ -.. versionchanged:: _default - - Data Modification Language commands (``INSERT``, ``UPDATE``, ``DELETE``, ..) - are not supported in EdgeDB <6.0. - -.. versionchanged:: 6.0 - .. versionadded:: 6.0 - When using ``INSERT``, ``DELETE`` or ``UPDATE`` on any table, mutation - rewrites and triggers are applied. These commands do not have a - straight-forward translation to EdgeQL DML commands, but instead use the - following mapping: +When using ``INSERT``, ``DELETE`` or ``UPDATE`` on any table, mutation +rewrites and triggers are applied. These commands do not have a +straight-forward translation to EdgeQL DML commands, but instead use the +following mapping: - - ``INSERT INTO "Foo"`` object table maps to ``insert Foo``, +- ``INSERT INTO "Foo"`` object table maps to ``insert Foo``, - - ``INSERT INTO "Foo.keywords"`` link/property table maps to an - ``update Foo { keywords += ... }``, +- ``INSERT INTO "Foo.keywords"`` link/property table maps to an + ``update Foo { keywords += ... }``, - - ``DELETE FROM "Foo"`` object table maps to ``delete Foo``, +- ``DELETE FROM "Foo"`` object table maps to ``delete Foo``, - - ``DELETE FROM "Foo.keywords"`` link property/table maps to - ``update Foo { keywords -= ... }``, +- ``DELETE FROM "Foo.keywords"`` link property/table maps to + ``update Foo { keywords -= ... }``, - - ``UPDATE "Foo"`` object table maps to ``update Foo set { ... }``, +- ``UPDATE "Foo"`` object table maps to ``update Foo set { ... }``, - - ``UPDATE "Foo.keywords"`` is not supported. +- ``UPDATE "Foo.keywords"`` is not supported. Connection settings @@ -363,7 +356,7 @@ SQL adapter supports most of PostgreSQL connection settings .. versionadded:: 6.0 - In addition, there are the following EdgeDB-specific settings: + In addition, there are the following Gel-specific settings: - settings prefixed with ``"global "`` set the values of globals. @@ -421,25 +414,19 @@ perform worse compared to other tables in the database. As a result, tools like Locking ======= -.. versionchanged:: _default - - SQL adapter does not support ``LOCK`` in EdgeDB <6.0. - -.. versionchanged:: 6.0 - .. versionadded:: 6.0 - SQL adapter supports LOCK command with the following limitations: +SQL adapter supports LOCK command with the following limitations: - - it cannot be used on tables that represent object types with access - properties or links of such objects, - - it cannot be used on tables that represent object types that have child - types extending them. +- it cannot be used on tables that represent object types with access + properties or links of such objects, +- it cannot be used on tables that represent object types that have child + types extending them. Query cache =========== -An SQL query is issued to EdgeDB, it is compiled to an internal SQL query, which +An SQL query is issued to Gel, it is compiled to an internal SQL query, which is then issued to the backing PostgreSQL instance. The compiled query is then cached, so each following issue of the same query will not perform any compilation, but just pass through the cached query. @@ -450,7 +437,7 @@ compilation, but just pass through the cached query. extracts constant values and replaces them by internal query parameters. This allows sharing of compilation cache between queries that differ in only constant values. This process is totally opaque and is fully handled by - EdgeDB. For example: + Gel. For example: .. code-block:: sql @@ -462,7 +449,7 @@ compilation, but just pass through the cached query. SELECT $1, $2; - This way, when a similar query is issued to EdgeDB: + This way, when a similar query is issued to Gel: .. code-block:: sql @@ -503,16 +490,16 @@ Following functions are not supported: - most of system administration functions. -Example: gradual transition from ORMs to EdgeDB -=============================================== +Example: gradual transition from ORMs to Gel +============================================ When a project is using Object-Relational Mappings (e.g. SQLAlchemy, Django, -Hibernate ORM, TypeORM) and is considering the migration to EdgeDB, it might +Hibernate ORM, TypeORM) and is considering the migration to Gel, it might want to execute the transition gradually, as opposed to a total rewrite of the project. In this case, the project can start the transition by migrating the ORM models -to EdgeDB Schema Definition Language. +to Gel Schema Definition Language. For example, such Hibernate ORM model in Java: @@ -532,7 +519,7 @@ For example, such Hibernate ORM model in Java: // ... getters and setters ... } -... would be translated to the following EdgeDB SDL: +... would be translated to the following Gel SDL: .. code-block:: sdl @@ -542,8 +529,8 @@ For example, such Hibernate ORM model in Java: required releaseYear: int32; } -A new EdgeDB instance can now be created and migrated to the translated schema. -At this stage, EdgeDB will allow SQL connections to write into the ``"Movie"`` +A new Gel instance can now be created and migrated to the translated schema. +At this stage, Gel will allow SQL connections to write into the ``"Movie"`` table, just as it would have been created with the following DDL command: .. code-block:: sql @@ -555,11 +542,11 @@ table, just as it would have been created with the following DDL command: releaseYear INTEGER NOT NULL ); -When translating the old ORM model to EdgeDB SDL, one should aim to make the -SQL schema of EdgeDB match the SQL schema that the ORM expects. +When translating the old ORM model to Gel SDL, one should aim to make the +SQL schema of Gel match the SQL schema that the ORM expects. When this match is accomplished, any query that used to work with the old, plain -PostgreSQL, should now also work with the EdgeDB. For example, we can execute +PostgreSQL, should now also work with the Gel. For example, we can execute the following query: .. code-block:: sql @@ -569,7 +556,7 @@ the following query: RETURNING id, title, releaseYear; To complete the migration, the data can be exported from our old database into -an ``.sql`` file, which can be import it into EdgeDB: +an ``.sql`` file, which can be import it into Gel: .. code-block:: bash @@ -577,16 +564,16 @@ an ``.sql`` file, which can be import it into EdgeDB: --data-only --inserts --no-owner --no-privileges \ > dump.sql - $ psql {your EdgeDB connection params} --file dump.sql + $ psql {your Gel connection params} --file dump.sql -Now, the ORM can be pointed to EdgeDB instead of the old PostgreSQL database, +Now, the ORM can be pointed to Gel instead of the old PostgreSQL database, which has been fully replaced. Arguably, the development of new features with the ORM is now more complex for the duration of the transition, since the developer has to modify two model -definitions: the ORM and the EdgeDB schema. +definitions: the ORM and the Gel schema. -But it allows any new models to use EdgeDB schema, EdgeQL and code generators +But it allows any new models to use Gel schema, EdgeQL and code generators for the client language of choice. The ORM-based code can now also be gradually rewritten to use EdgeQL, one model at the time. diff --git a/docs/stdlib/array.rst b/docs/stdlib/array.rst index 3b868303901..a0d9a585894 100644 --- a/docs/stdlib/array.rst +++ b/docs/stdlib/array.rst @@ -78,7 +78,7 @@ Empty arrays ^^^^^^^^^^^^ You can also create an empty array, but it must be done by providing the type -information using type casting. EdgeDB cannot infer the type of an empty array +information using type casting. Gel cannot infer the type of an empty array created otherwise. For example: .. code-block:: edgeql-repl @@ -105,7 +105,7 @@ Reference Array indexing starts at zero. - An array can contain any type except another array. In EdgeDB, arrays are + An array can contain any type except another array. In Gel, arrays are always one-dimensional. An array type is created implicitly when an :ref:`array diff --git a/docs/stdlib/bool.rst b/docs/stdlib/bool.rst index 113efa3abe2..9e0b5f892b9 100644 --- a/docs/stdlib/bool.rst +++ b/docs/stdlib/bool.rst @@ -390,7 +390,7 @@ operator. ... select detached Person filter .name = 'Dracula' ... ) ... }; - edgedb error: EdgeDBError: Invalid frenemies + edgedb error: GelError: Invalid frenemies In the following examples, the ``size`` properties of the ``File`` objects are ``1024``, ``1024``, and ``131,072``. diff --git a/docs/stdlib/bytes.rst b/docs/stdlib/bytes.rst index d358f033243..c6e946b61f3 100644 --- a/docs/stdlib/bytes.rst +++ b/docs/stdlib/bytes.rst @@ -99,10 +99,10 @@ Bytes .. code-block:: edgeql-repl - db> select b'Hello EdgeDB!'; + db> select b'Hello Gel!'; {"\"SGVsbG8gRWRnZURCIQ==\""} db> select to_json("\"SGVsbG8gRWRnZURCIQ==\""); - {b'Hello EdgeDB!'} + {b'Hello Gel!'} ---------- diff --git a/docs/stdlib/cfg.rst b/docs/stdlib/cfg.rst index 0468fce80e8..a4e9dc88885 100644 --- a/docs/stdlib/cfg.rst +++ b/docs/stdlib/cfg.rst @@ -16,17 +16,17 @@ EdgeDB. * - :eql:type:`cfg::AbstractConfig` - The abstract base type for all configuration objects. The properties of this type define the set of configuruation settings supported by - EdgeDB. + Gel. * - :eql:type:`cfg::Config` - The main configuration object. The properties of this object reflect the overall configuration setting from instance level all the way to session level. * - :eql:type:`cfg::DatabaseConfig` - The database configuration object. It reflects all the applicable - configuration at the EdgeDB database level. + configuration at the Gel database level. * - :eql:type:`cfg::BranchConfig` - The database branch configuration object. It reflects all the applicable - configuration at the EdgeDB branch level. + configuration at the Gel branch level. * - :eql:type:`cfg::InstanceConfig` - The instance configuration object. * - :eql:type:`cfg::ExtensionConfig` @@ -36,7 +36,7 @@ EdgeDB. * - :eql:type:`cfg::Auth` - An object type representing an authentication profile. * - :eql:type:`cfg::ConnectionTransport` - - An enum type representing the different protocols that EdgeDB speaks. + - An enum type representing the different protocols that Gel speaks. * - :eql:type:`cfg::AuthMethod` - An abstract object type representing a method of authentication * - :eql:type:`cfg::Trust` @@ -128,7 +128,7 @@ Query cache Allows the developer to set where the query cache is stored. Possible values: * ``cfg::QueryCacheMode.InMemory``- All query cache is lost on server restart. - This mirrors pre-5.0 EdgeDB's behavior. + This mirrors pre-5.0 |EdgeDB| behavior. * ``cfg::QueryCacheMode.RegInline``- The in-memory query cache is also stored in the database as-is so it can be restored on restart. * ``cfg::QueryCacheMode.Default``- Allow the server to select the best caching @@ -165,7 +165,7 @@ Query behavior .. note:: This setting can also be conveniently accessed via the "Config" dropdown - menu at the top of the EdgeDB UI (accessible by running the CLI command + menu at the top of the Gel UI (accessible by running the CLI command ``edgedb ui`` from within a project). The setting will apply only to your UI session, so you won't have to remember to re-enable it when you're done. @@ -182,7 +182,7 @@ Query behavior This parameter takes a ``str`` instead of a ``bool`` to allow more verbose messages when all queries are forced to fail. The database will attempt to deserialize this ``str`` into a JSON object that must include - a ``type`` (which must be an EdgeDB + a ``type`` (which must be an Gel :ref:`error type ` name), and may also include ``message``, ``hint``, and ``details`` which can be set ad-hoc by the user. @@ -285,7 +285,7 @@ Client connections An abstract type representing the configuration of an instance or database. The properties of this object type represent the set of configuration - options supported by EdgeDB (listed above). + options supported by Gel (listed above). ---------- @@ -296,7 +296,7 @@ Client connections The main configuration object type. This type will have only one object instance. The ``cfg::Config`` object - represents the sum total of the current EdgeDB configuration. It reflects + represents the sum total of the current Gel configuration. It reflects the result of applying instance, branch, and session level configuration. Examining this object is the recommended way of determining the current configuration. @@ -323,14 +323,14 @@ Client connections This type will have only one object instance. The ``cfg::DatabaseConfig`` object represents the state of :versionreplace:`database;5.0:branch` and - instance-level EdgeDB configuration. + instance-level Gel configuration. For overall configuration state please refer to the :eql:type:`cfg::Config` instead. .. versionadded:: 5.0 - As of EdgeDB 5.0, this config object represents database *branch* + As of |EdgeDB| 5.0, this config object represents database *branch* and instance-level configuration. @@ -345,7 +345,7 @@ Client connections This type will have only one object instance. The ``cfg::BranchConfig`` object represents the state of :versionreplace:`database;5.0:branch` and - instance-level EdgeDB configuration. + instance-level Gel configuration. For overall configuration state please refer to the :eql:type:`cfg::Config` instead. @@ -359,7 +359,7 @@ Client connections The instance-level configuration object type. This type will have only one object instance. The ``cfg::InstanceConfig`` - object represents the state of only instance-level EdgeDB configuration. + object represents the state of only instance-level Gel configuration. For overall configuraiton state please refer to the :eql:type:`cfg::Config` instead. @@ -440,7 +440,7 @@ Client connections .. eql:type:: cfg::ConnectionTransport - An enum listing the various protocols that EdgeDB can speak. + An enum listing the various protocols that Gel can speak. Possible values are: @@ -450,12 +450,12 @@ Client connections * - **Value** - **Description** * - ``cfg::ConnectionTransport.TCP`` - - EdgeDB binary protocol + - Gel binary protocol * - ``cfg::ConnectionTransport.TCP_PG`` - Postgres protocol for the :ref:`SQL query mode ` * - ``cfg::ConnectionTransport.HTTP`` - - EdgeDB binary protocol + - Gel binary protocol :ref:`tunneled over HTTP ` * - ``cfg::ConnectionTransport.SIMPLE_HTTP`` - :ref:`EdgeQL over HTTP ` diff --git a/docs/stdlib/datetime.rst b/docs/stdlib/datetime.rst index ccfbd1b97e5..fccdf0e5213 100644 --- a/docs/stdlib/datetime.rst +++ b/docs/stdlib/datetime.rst @@ -538,7 +538,7 @@ functionality. In most cases, ``date_duration`` is fully compatible with :eql:type:`cal::relative_duration` and shares the same general behavior - and caveats. EdgeDB will apply type coercion in the event it expects a + and caveats. Gel will apply type coercion in the event it expects a :eql:type:`cal::relative_duration` and finds a ``cal::date_duration`` instead. @@ -653,10 +653,10 @@ functionality. Subtraction doesn't make sense for some type combinations. You couldn't subtract a point in time from a duration, so neither can - EdgeDB (although the inverse — subtracting a duration from a point in + Gel (although the inverse — subtracting a duration from a point in time — is perfectly fine). You also couldn't subtract a timezone-aware datetime from a local one or vice versa. If you attempt any of these, - EdgeDB will raise an exception as shown in these examples. + Gel will raise an exception as shown in these examples. When subtracting a date/time object from a time interval, an exception will be raised: diff --git a/docs/stdlib/fts.rst b/docs/stdlib/fts.rst index 2412370494d..029005b1a8b 100644 --- a/docs/stdlib/fts.rst +++ b/docs/stdlib/fts.rst @@ -7,7 +7,7 @@ Full-text Search ================ The ``fts`` built-in module contains various tools that enable full-text -search functionality in EdgeDB. +search functionality in Gel. .. note:: @@ -291,11 +291,6 @@ the matching object *must not* contain: }, } -.. note:: - - EdgeDB 4.0 only supports Postgres full-text search backend. Support for - other backends is still in development. - ---------- @@ -429,4 +424,4 @@ the matching object *must not* contain: overridden when making a call to :eql:func:`fts::search`. -.. _iso639: https://iso639-3.sil.org/code_tables/639/data \ No newline at end of file +.. _iso639: https://iso639-3.sil.org/code_tables/639/data diff --git a/docs/stdlib/json.rst b/docs/stdlib/json.rst index a051d199968..94606aae211 100644 --- a/docs/stdlib/json.rst +++ b/docs/stdlib/json.rst @@ -58,7 +58,7 @@ JSON Constructing JSON Values ------------------------ -JSON in EdgeDB is a :ref:`scalar type `. This type +JSON in Gel is a :ref:`scalar type `. This type doesn't have its own literal, and instead can be obtained by either casting a value to the :eql:type:`json` type, or by using the :eql:func:`to_json` function: @@ -70,7 +70,7 @@ function: db> select 'hello world'; {Json("\"hello world\"")} -Any value in EdgeDB can be cast to a :eql:type:`json` type as well: +Any value in Gel can be cast to a :eql:type:`json` type as well: .. code-block:: edgeql-repl @@ -90,7 +90,7 @@ Any value in EdgeDB can be cast to a :eql:type:`json` type as well: db> select json_object_pack({("hello", "world")}); {Json("{\"hello\": \"world\"}")} -Additionally, any :eql:type:`Object` in EdgeDB can be cast as a +Additionally, any :eql:type:`Object` in Gel can be cast as a :eql:type:`json` type. This produces the same JSON value as the JSON-serialized result of that said object. Furthermore, this result will be the same as the output of a :eql:stmt:`select expression