diff --git a/Cargo.lock b/Cargo.lock index 5f657c3d5ee..65c9740f0b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,6 +690,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "auto_impl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "auto_ops" version = "0.3.0" @@ -891,6 +903,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "1.3.3" @@ -1142,6 +1160,26 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c2rust-bitfields" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb34f0c0ace43530b2df7f18bc69ee0c4082158aa451ece29602f8c841e73764" +dependencies = [ + "c2rust-bitfields-derive", +] + +[[package]] +name = "c2rust-bitfields-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd1601a7b828ab874d890e5a895563ca8ad485bdd3d2a359f148c8b72537241" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cactus" version = "1.0.6" @@ -1254,6 +1292,18 @@ dependencies = [ "libc", ] +[[package]] +name = "cc-traits" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" + +[[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" @@ -1354,6 +1404,12 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + [[package]] name = "ciborium" version = "0.2.1" @@ -1601,6 +1657,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "comfy-table" version = "7.1.0" @@ -2462,6 +2528,12 @@ dependencies = [ "parking_lot_core 0.9.9", ] +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "datafusion" version = "32.0.0" @@ -2898,6 +2970,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "difflib" version = "0.4.0" @@ -2916,6 +2994,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs" version = "4.0.0" @@ -3304,6 +3391,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "flow" +version = "0.6.0" +dependencies = [ + "api", + "bimap", + "common-error", + "common-macro", + "common-meta", + "common-query", + "common-telemetry", + "common-time", + "datatypes", + "hydroflow", + "itertools 0.10.5", + "serde", + "servers", + "session", + "snafu", + "tokio", + "tonic 0.10.2", +] + [[package]] name = "fnv" version = "1.0.7" @@ -3866,6 +3976,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "0.2.11" @@ -3938,6 +4057,103 @@ dependencies = [ "serde", ] +[[package]] +name = "hydroflow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5129724896b4c3cf12f8e5f5af2f1d94b4c5933ae911189747025c6a5ff1346" +dependencies = [ + "bincode", + "byteorder", + "bytes", + "futures", + "getrandom", + "hydroflow_datalog", + "hydroflow_lang", + "hydroflow_macro", + "instant", + "itertools 0.10.5", + "lattices", + "pusherator", + "ref-cast", + "regex", + "rustc-hash", + "sealed", + "serde", + "serde_json", + "slotmap", + "smallvec", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "variadics", +] + +[[package]] +name = "hydroflow_datalog" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41813c88b02f3bfa8f5962e125495aa47c8d382cf5d135b02da40af4342bc6fb" +dependencies = [ + "hydroflow_datalog_core", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "hydroflow_datalog_core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea77a3b2f09bba3d461f9ce0dee28798d3b07dafe77fc46de4675155f5925e53" +dependencies = [ + "hydroflow_lang", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "rust-sitter", + "rust-sitter-tool", + "slotmap", + "syn 2.0.43", +] + +[[package]] +name = "hydroflow_lang" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3191eee8ef49b4a814e4c33a0ce0d7470b733dc6118ea744f7f15168c38803f" +dependencies = [ + "auto_impl", + "clap 4.4.11", + "data-encoding", + "itertools 0.10.5", + "prettyplease 0.2.15", + "proc-macro2", + "quote", + "regex", + "serde", + "serde_json", + "slotmap", + "syn 2.0.43", + "webbrowser", +] + +[[package]] +name = "hydroflow_macro" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be25d2a927fe4e6afe3e204786e968e983f53f313cc561950ff1cd09ecd92fc" +dependencies = [ + "hydroflow_lang", + "itertools 0.10.5", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "hyper" version = "0.14.28" @@ -4156,6 +4372,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -4259,6 +4478,28 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if 1.0.0", + "combine", + "jni-sys", + "log", + "thiserror", + "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.27" @@ -4352,6 +4593,17 @@ dependencies = [ "regex", ] +[[package]] +name = "lattices" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3bff82353a971b61106a49369cfc1bd8398661107eadcb5387fcd21c43cac9" +dependencies = [ + "cc-traits", + "sealed", + "serde", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -4692,6 +4944,15 @@ dependencies = [ "libc", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -5253,6 +5514,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -5495,6 +5762,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "object" version = "0.32.2" @@ -6844,6 +7120,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b7e158a385023d209d6d5f2585c4b468f6dcb3dd5aca9b75c4f1678c05bb375" +[[package]] +name = "pusherator" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd486cb5153e0d8fa91d3daebae48917ae299b2569cc79901922f3923dc312ef" +dependencies = [ + "either", + "variadics", +] + [[package]] name = "pyo3" version = "0.19.2" @@ -7126,6 +7412,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + [[package]] name = "rawpointer" version = "0.2.1" @@ -7181,6 +7473,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ref-cast" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53313ec9f12686aeeffb43462c3ac77aa25f590a5f630eb2cde0de59417b29c7" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "regex" version = "1.10.2" @@ -7632,6 +7944,55 @@ dependencies = [ "ordered-multimap 0.7.1", ] +[[package]] +name = "rust-sitter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f365b4eb9591dd3e685791389a932041b0dc6ccf5db1ec3d8913f67279365" +dependencies = [ + "rust-sitter-macro", + "tree-sitter-c2rust", +] + +[[package]] +name = "rust-sitter-common" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c0a0b1da7317031274502b7c52cbb7cf529e7d1e1f3e23876519372b173a94" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust-sitter-macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25e213e40efa00713547cc0f3529694aca547cfceb0839bbc9406632e14d410" +dependencies = [ + "proc-macro2", + "quote", + "rust-sitter-common", + "syn 1.0.109", +] + +[[package]] +name = "rust-sitter-tool" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803c6596476a188a4dd18106eb927a926a202e00077cdaa5648dd620262af158" +dependencies = [ + "cc", + "rust-sitter-common", + "serde", + "serde_json", + "syn 1.0.109", + "syn-inline-mod", + "tempfile", + "tree-sitter", + "tree-sitter-cli", +] + [[package]] name = "rust_decimal" version = "1.33.1" @@ -8324,6 +8685,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sealed" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -8409,6 +8782,7 @@ version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -8761,6 +9135,22 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "smallbitvec" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ce4f9dc4a41b4c3476cc925f1efb11b66df373a8fde5d4b8915fa91b5d995e" + [[package]] name = "smallvec" version = "1.11.2" @@ -9014,7 +9404,7 @@ dependencies = [ "crc", "crossbeam-queue", "digest", - "dirs", + "dirs 4.0.0", "dotenvy", "either", "event-listener", @@ -9381,6 +9771,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "syn-inline-mod" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b670f535364c67358ecffb60b9f2579f9b45d3c71e8cca6d45d22ee0fadaa7eb" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "syn_derive" version = "0.1.8" @@ -9789,6 +10189,18 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -9962,6 +10374,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -10332,6 +10745,117 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "tree-sitter" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-c2rust" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee40a4d9cf5a30c199935f346887588239daceae4d1418d81b789276fffb8d91" +dependencies = [ + "c2rust-bitfields", + "once_cell", + "regex", +] + +[[package]] +name = "tree-sitter-cli" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae7e9d844d4d38e511a7b93fe8ced79f2a364c32fdea10d04546f1c8317d5a0c" +dependencies = [ + "ansi_term", + "anyhow", + "atty", + "clap 2.34.0", + "difference", + "dirs 3.0.2", + "glob", + "html-escape", + "indexmap 1.9.3", + "lazy_static", + "log", + "regex", + "regex-syntax 0.6.29", + "rustc-hash", + "semver", + "serde", + "serde_json", + "smallbitvec", + "tiny_http", + "toml 0.5.11", + "tree-sitter", + "tree-sitter-config", + "tree-sitter-highlight", + "tree-sitter-loader", + "tree-sitter-tags", + "walkdir", + "webbrowser", + "which", +] + +[[package]] +name = "tree-sitter-config" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fec4cb27f052ead2246631b332dba0cb6af9a54ce012badee59c4b0ded5e03" +dependencies = [ + "anyhow", + "dirs 3.0.2", + "serde", + "serde_json", +] + +[[package]] +name = "tree-sitter-highlight" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc" +dependencies = [ + "regex", + "thiserror", + "tree-sitter", +] + +[[package]] +name = "tree-sitter-loader" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b17eef4833c7c139abed66d562dfa23228e97e647597baf246fd56c21bbfaf" +dependencies = [ + "anyhow", + "cc", + "dirs 3.0.2", + "libloading", + "once_cell", + "regex", + "serde", + "serde_json", + "tree-sitter", + "tree-sitter-highlight", + "tree-sitter-tags", +] + +[[package]] +name = "tree-sitter-tags" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb3f1376219530a37a809751ecf65aa35fd8b9c1c4ab6d4faf5f6a9eeda2c05" +dependencies = [ + "memchr", + "regex", + "thiserror", + "tree-sitter", +] + [[package]] name = "triomphe" version = "0.1.11" @@ -10693,6 +11217,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8parse" version = "0.2.1" @@ -10729,6 +11259,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "variadics" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4500f518837578bf2d62d9c12f47ecb5b5279da689574793b7bace8138b4784" + [[package]] name = "vcpkg" version = "0.2.15" @@ -10903,6 +11439,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webbrowser" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71" +dependencies = [ + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc", + "raw-window-handle", + "url", + "web-sys", +] + [[package]] name = "webpki" version = "0.22.4" diff --git a/Cargo.toml b/Cargo.toml index 384c72e05a1..82c8bcb4f67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ members = [ "src/datanode", "src/datatypes", "src/file-engine", + "src/flow", "src/frontend", "src/log-store", "src/meta-client", diff --git a/src/flow/Cargo.toml b/src/flow/Cargo.toml new file mode 100644 index 00000000000..d500b2351a8 --- /dev/null +++ b/src/flow/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "flow" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +api.workspace = true +bimap = "0.6.3" +common-error.workspace = true +common-macro.workspace = true +common-meta.workspace = true +common-query.workspace = true +common-telemetry.workspace = true +common-time.workspace = true +datatypes.workspace = true +hydroflow = "0.5.0" +itertools.workspace = true +serde.workspace = true +servers.workspace = true +session.workspace = true +snafu.workspace = true +tokio.workspace = true +tonic.workspace = true diff --git a/src/flow/src/adapter.rs b/src/flow/src/adapter.rs new file mode 100644 index 00000000000..f53fae56745 --- /dev/null +++ b/src/flow/src/adapter.rs @@ -0,0 +1,18 @@ +// Copyright 2023 Greptime Team +// +// 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. + +//! for getting data from source and sending results to sink +//! and communicating with other parts of the database + +pub(crate) mod error; diff --git a/src/flow/src/adapter/error.rs b/src/flow/src/adapter/error.rs new file mode 100644 index 00000000000..b99c8a7007d --- /dev/null +++ b/src/flow/src/adapter/error.rs @@ -0,0 +1,77 @@ +// Copyright 2023 Greptime Team +// +// 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. + +use std::any::Any; + +use common_macro::stack_trace_debug; +use common_telemetry::common_error::ext::ErrorExt; +use common_telemetry::common_error::status_code::StatusCode; +use datatypes::data_type::ConcreteDataType; +use datatypes::value::Value; +use serde::{Deserialize, Serialize}; +use servers::define_into_tonic_status; +use snafu::{Location, Snafu}; + +use crate::expr::EvalError; + +#[derive(Snafu)] +#[snafu(visibility(pub))] +#[stack_trace_debug] +pub enum Error { + /// TODO(discord9): add detailed location of column + #[snafu(display("Failed to eval stream"))] + Eval { + source: EvalError, + location: Location, + }, + + #[snafu(display("Table not found: {name}"))] + TableNotFound { name: String, location: Location }, + + #[snafu(display("Table already exist: {name}"))] + TableAlreadyExist { name: String, location: Location }, + + #[snafu(display("Failed to join task"))] + JoinTask { + #[snafu(source)] + error: tokio::task::JoinError, + location: Location, + }, + + #[snafu(display("Invalid query: {reason}"))] + InvalidQuery { reason: String, location: Location }, + + #[snafu(display("No protobuf type for value: {value}"))] + NoProtoType { value: Value, location: Location }, +} + +pub type Result = std::result::Result; + +impl ErrorExt for Error { + fn status_code(&self) -> StatusCode { + match self { + Self::Eval { .. } | &Self::JoinTask { .. } => StatusCode::Internal, + &Self::TableAlreadyExist { .. } => StatusCode::TableAlreadyExists, + Self::TableNotFound { .. } => StatusCode::TableNotFound, + &Self::InvalidQuery { .. } => StatusCode::PlanQuery, + Self::NoProtoType { .. } => StatusCode::Unexpected, + } + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +define_into_tonic_status!(Error); diff --git a/src/flow/src/expr.rs b/src/flow/src/expr.rs new file mode 100644 index 00000000000..cc83ffc7132 --- /dev/null +++ b/src/flow/src/expr.rs @@ -0,0 +1,22 @@ +// Copyright 2023 Greptime Team +// +// 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. + +//! for declare Expression in dataflow, including map, reduce, id and join(TODO!) etc. + +pub(crate) mod error; +mod id; + +pub(crate) use id::{GlobalId, Id, LocalId}; + +pub(crate) use crate::expr::error::{EvalError, InvalidArgumentSnafu, OptimizeSnafu}; diff --git a/src/flow/src/expr/error.rs b/src/flow/src/expr/error.rs new file mode 100644 index 00000000000..8c459200c87 --- /dev/null +++ b/src/flow/src/expr/error.rs @@ -0,0 +1,61 @@ +// Copyright 2023 Greptime Team +// +// 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. + +use std::any::Any; + +use common_macro::stack_trace_debug; +use common_telemetry::common_error::ext::ErrorExt; +use common_telemetry::common_error::status_code::StatusCode; +use datatypes::data_type::ConcreteDataType; +use serde::{Deserialize, Serialize}; +use snafu::{Location, Snafu}; + +/// EvalError is about errors happen on columnar evaluation +/// +/// TODO(discord9): add detailed location of column/operator(instead of code) to errors tp help identify related column +#[derive(Snafu)] +#[snafu(visibility(pub))] +#[stack_trace_debug] +pub enum EvalError { + #[snafu(display("Division by zero"))] + DivisionByZero, + + #[snafu(display("Type mismatch: expected {expected}, actual {actual}"))] + TypeMismatch { + expected: ConcreteDataType, + actual: ConcreteDataType, + location: Location, + }, + + /// can't nest datatypes error because EvalError need to be store in map and serialization + #[snafu(display("Fail to unpack from value to given type: {msg}"))] + TryFromValue { msg: String, location: Location }, + + #[snafu(display("Fail to cast value of type {from} to given type {to}"))] + CastValue { + from: ConcreteDataType, + to: ConcreteDataType, + source: datatypes::Error, + location: Location, + }, + + #[snafu(display("Invalid argument: {reason}"))] + InvalidArgument { reason: String, location: Location }, + + #[snafu(display("Internal error: {reason}"))] + Internal { reason: String, location: Location }, + + #[snafu(display("Optimize error: {reason}"))] + Optimize { reason: String, location: Location }, +} diff --git a/src/flow/src/expr/id.rs b/src/flow/src/expr/id.rs new file mode 100644 index 00000000000..6a3c50e9b7c --- /dev/null +++ b/src/flow/src/expr/id.rs @@ -0,0 +1,41 @@ +// Copyright 2023 Greptime Team +// +// 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. + +use serde::{Deserialize, Serialize}; + +/// Global id's scope is in Current Worker, and is cross-dataflow +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum GlobalId { + /// System namespace. + System(u64), + /// User namespace. + User(u64), + /// Transient namespace. + Transient(u64), + /// Dummy id for query being explained + Explain, +} + +/// Local id is used in local scope created by `Plan::Let{id: LocalId, value, body}` +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub struct LocalId(pub(crate) u64); + +/// Id is used to identify a dataflow component in plan like `Plan::Get{id: Id}` +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum Id { + /// An identifier that refers to a local component of a dataflow. + Local(LocalId), + /// An identifier that refers to a global dataflow. + Global(GlobalId), +} diff --git a/src/flow/src/expr/relation.rs b/src/flow/src/expr/relation.rs new file mode 100644 index 00000000000..9e6072e7896 --- /dev/null +++ b/src/flow/src/expr/relation.rs @@ -0,0 +1,33 @@ +// Copyright 2023 Greptime Team +// +// 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. + +pub(crate) use func::AggregateFunc; +use serde::{Deserialize, Serialize}; + +use crate::expr::ScalarExpr; + +mod func; + +/// Describes an aggregation expression. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct AggregateExpr { + /// Names the aggregation function. + pub func: AggregateFunc, + /// An expression which extracts from each row the input to `func`. + /// TODO(discord9): currently unused, it only used in generate KeyValPlan from AggregateExpr + pub expr: ScalarExpr, + /// Should the aggregation be applied only to distinct results in each group. + #[serde(default)] + pub distinct: bool, +} diff --git a/src/flow/src/lib.rs b/src/flow/src/lib.rs new file mode 100644 index 00000000000..a6031050476 --- /dev/null +++ b/src/flow/src/lib.rs @@ -0,0 +1,19 @@ +// Copyright 2023 Greptime Team +// +// 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. + +#![allow(unused)] +// allow unused for now because it should be use later +mod adapter; +mod expr; +mod repr; diff --git a/src/flow/src/repr.rs b/src/flow/src/repr.rs new file mode 100644 index 00000000000..8c15483666e --- /dev/null +++ b/src/flow/src/repr.rs @@ -0,0 +1,165 @@ +// Copyright 2023 Greptime Team +// +// 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. + +//! basically a wrapper around the `datatype` crate +//! for basic Data Representation + +mod relation; + +use std::borrow::Borrow; +use std::slice::SliceIndex; + +use api::helper::{pb_value_to_value_ref, value_to_grpc_value}; +use api::v1::Row as ProtoRow; +use datatypes::data_type::ConcreteDataType; +use datatypes::types::cast; +use datatypes::types::cast::CastOption; +use datatypes::value::Value; +use itertools::Itertools; +pub(crate) use relation::{RelationDesc, RelationType}; +use serde::{Deserialize, Serialize}; +use snafu::ResultExt; + +use crate::expr::error::{CastValueSnafu, EvalError}; + +/// System-wide Record count difference type. +pub type Diff = i64; + +/// System-wide default timestamp type +pub type Timestamp = i64; + +/// Default type for a repr of changes to a collection. +pub type DiffRow = (Row, Timestamp, Diff); + +/// Convert a value that is or can be converted to Datetime to internal timestamp +pub fn value_to_internal_ts(value: Value) -> Result { + match value { + Value::DateTime(ts) => Ok(ts.val()), + arg => { + let arg_ty = arg.data_type(); + let res = cast(arg, &ConcreteDataType::datetime_datatype()).context({ + CastValueSnafu { + from: arg_ty, + to: ConcreteDataType::datetime_datatype(), + } + })?; + if let Value::DateTime(ts) = res { + Ok(ts.val()) + } else { + unreachable!() + } + } + } +} + +/// A row is a vector of values. +/// +/// TODO(discord9): use a more efficient representation +/// i.e. more compact like raw u8 of \[tag0, value0, tag1, value1, ...\] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)] +pub struct Row { + pub inner: Vec, +} + +impl Row { + pub fn empty() -> Self { + Self { inner: vec![] } + } + pub fn new(row: Vec) -> Self { + Self { inner: row } + } + pub fn get(&self, idx: usize) -> Option<&Value> { + self.inner.get(idx) + } + pub fn clear(&mut self) { + self.inner.clear(); + } + /// clear and return the inner vector + /// + /// useful if you want to reuse the vector as a buffer + pub fn packer(&mut self) -> &mut Vec { + self.inner.clear(); + &mut self.inner + } + /// pack a iterator of values into a row + pub fn pack(iter: I) -> Row + where + I: IntoIterator, + { + Self { + inner: iter.into_iter().collect(), + } + } + /// unpack a row into a vector of values + pub fn unpack(self) -> Vec { + self.inner + } + pub fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.inner.extend(iter); + } + pub fn into_iter(self) -> impl Iterator { + self.inner.into_iter() + } + pub fn iter(&self) -> impl Iterator { + self.inner.iter() + } + pub fn len(&self) -> usize { + self.inner.len() + } +} + +impl From for Row { + fn from(row: ProtoRow) -> Self { + Row::pack( + row.values + .iter() + .map(|pb_val| -> Value { pb_value_to_value_ref(pb_val, &None).into() }), + ) + } +} + +impl From for ProtoRow { + fn from(row: Row) -> Self { + let values = row + .unpack() + .into_iter() + .map(value_to_grpc_value) + .collect_vec(); + ProtoRow { values } + } +} + +#[test] +fn test_row() { + let row = Row::empty(); + let row_1 = Row::new(vec![]); + assert_eq!(row, row_1); + let mut row_2 = Row::new(vec![Value::Int32(1), Value::Int32(2)]); + assert_eq!(row_2.get(0), Some(&Value::Int32(1))); + row_2.clear(); + assert_eq!(row_2.get(0), None); + row_2 + .packer() + .extend(vec![Value::Int32(1), Value::Int32(2)]); + assert_eq!(row_2.get(0), Some(&Value::Int32(1))); + row_2.extend(vec![Value::Int32(1), Value::Int32(2)]); + assert_eq!(row_2.len(), 4); + let row_3 = Row::pack(row_2.into_iter()); + assert_eq!(row_3.len(), 4); + let row_4 = Row::pack(row_3.iter().cloned()); + assert_eq!(row_3, row_4); +} diff --git a/src/flow/src/repr/relation.rs b/src/flow/src/repr/relation.rs new file mode 100644 index 00000000000..2f98d8f1db2 --- /dev/null +++ b/src/flow/src/repr/relation.rs @@ -0,0 +1,398 @@ +// Copyright 2023 Greptime Team +// +// 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. + +use datatypes::prelude::ConcreteDataType; +use serde::{Deserialize, Serialize}; +use snafu::ensure; + +use crate::adapter::error::{InvalidQuerySnafu, Result}; + +/// a set of column indices that are "keys" for the collection. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct Key { + pub column_indices: Vec, +} + +impl Key { + /// create a new Key + pub fn new() -> Self { + Self { + column_indices: Vec::new(), + } + } + + /// create a new Key from a vector of column indices + pub fn from(mut column_indices: Vec) -> Self { + column_indices.sort_unstable(); + Self { column_indices } + } + + /// Add a column to Key + pub fn add_col(&mut self, col: usize) { + self.column_indices.push(col); + } + + /// Add columns to Key + pub fn add_cols(&mut self, cols: I) + where + I: IntoIterator, + { + self.column_indices.extend(cols); + } + + /// Remove a column from Key + pub fn remove_col(&mut self, col: usize) { + self.column_indices.retain(|&r| r != col); + } + + /// get all columns in Key + pub fn get(&self) -> &Vec { + &self.column_indices + } + + /// True if Key is empty + pub fn is_empty(&self) -> bool { + self.column_indices.is_empty() + } + + /// True if all columns in self are also in other + pub fn subset_of(&self, other: &Key) -> bool { + self.column_indices + .iter() + .all(|c| other.column_indices.contains(c)) + } +} + +/// The type of a relation. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct RelationType { + /// The type for each column, in order. + pub column_types: Vec, + /// Sets of indices that are "keys" for the collection. + /// + /// Each element in this list is a set of column indices, each with the + /// property that the collection contains at most one record with each + /// distinct set of values for each column. Alternately, for a specific set + /// of values assigned to the these columns there is at most one record. + /// + /// A collection can contain multiple sets of keys, although it is common to + /// have either zero or one sets of key indices. + #[serde(default)] + pub keys: Vec, + /// optionally indicate the column that is TIME INDEX + pub time_index: Option, +} + +impl RelationType { + /// Constructs a `RelationType` representing the relation with no columns and + /// no keys. + pub fn empty() -> Self { + RelationType::new(vec![]) + } + + /// Constructs a new `RelationType` from specified column types. + /// + /// The `RelationType` will have no keys. + pub fn new(column_types: Vec) -> Self { + RelationType { + column_types, + keys: Vec::new(), + time_index: None, + } + } + + /// Adds a new key for the relation. Also sorts the key indices. + pub fn with_key(mut self, mut indices: Vec) -> Self { + indices.sort_unstable(); + let key = Key::from(indices); + if !self.keys.contains(&key) { + self.keys.push(key); + } + self + } + + pub fn with_keys(mut self, keys: Vec>) -> Self { + for key in keys { + self = self.with_key(key) + } + self + } + + /// Computes the number of columns in the relation. + pub fn arity(&self) -> usize { + self.column_types.len() + } + + /// Gets the index of the columns used when creating a default index. + pub fn default_key(&self) -> Vec { + if let Some(key) = self.keys.first() { + if key.is_empty() { + (0..self.column_types.len()).collect() + } else { + key.get().clone() + } + } else { + (0..self.column_types.len()).collect() + } + } + + /// True if any collection described by `self` could safely be described by `other`. + /// + /// In practice this means checking that the scalar types match exactly, and that the + /// nullability of `self` is at least as strict as `other`, and that all keys of `other` + /// contain some key of `self` (as a set of key columns is less strict than any subset). + pub fn subtypes(&self, other: &RelationType) -> bool { + if self.column_types.len() != other.column_types.len() { + return false; + } + + for (col1, col2) in self.column_types.iter().zip(other.column_types.iter()) { + if col1.nullable && !col2.nullable { + return false; + } + if col1.scalar_type != col2.scalar_type { + return false; + } + } + + let all_keys = other + .keys + .iter() + .all(|key1| self.keys.iter().any(|key2| key1.subset_of(key2))); + if !all_keys { + return false; + } + + true + } +} + +/// The type of a `Value` +/// +/// [`ColumnType`] bundles information about the scalar type of a datum (e.g., +/// Int32 or String) with its nullability. +/// +/// To construct a column type, either initialize the struct directly, or +/// use the [`ScalarType::nullable`] method. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)] +pub struct ColumnType { + /// The underlying scalar type (e.g., Int32 or String) of this column. + pub scalar_type: ConcreteDataType, + /// Whether this datum can be null. + #[serde(default = "return_true")] + pub nullable: bool, +} + +/// This method exists solely for the purpose of making ColumnType nullable by +/// default in unit tests. The default value of a bool is false, and the only +/// way to make an object take on any other value by default is to pass it a +/// function that returns the desired default value. See +/// +#[inline(always)] +fn return_true() -> bool { + true +} + +/// A description of the shape of a relation. +/// +/// It bundles a [`RelationType`] with the name of each column in the relation. +/// Individual column names are optional. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] +pub struct RelationDesc { + typ: RelationType, + names: Vec, +} + +impl RelationDesc { + /// Constructs a new `RelationDesc` that represents the empty relation + /// with no columns and no keys. + pub fn empty() -> Self { + RelationDesc { + typ: RelationType::empty(), + names: vec![], + } + } + + /// Constructs a new `RelationDesc` from a `RelationType` and an iterator + /// over column names. + /// + pub fn try_new(typ: RelationType, names: I) -> Result + where + I: IntoIterator, + N: Into, + { + let names: Vec<_> = names.into_iter().map(|name| name.into()).collect(); + ensure!( + typ.arity() == names.len(), + InvalidQuerySnafu { + reason: format!( + "Length mismatch between RelationType {:?} and column names {:?}", + typ.column_types, names + ) + } + ); + Ok(RelationDesc { typ, names }) + } + + /// Constructs a new `RelationDesc` from a `RelationType` and an iterator + /// over column names. + /// + /// # Panics + /// + /// Panics if the arity of the `RelationType` is not equal to the number of + /// items in `names`. + pub fn new_unchecked(typ: RelationType, names: I) -> Self + where + I: IntoIterator, + N: Into, + { + let names: Vec<_> = names.into_iter().map(|name| name.into()).collect(); + assert_eq!(typ.arity(), names.len()); + RelationDesc { typ, names } + } + + pub fn from_names_and_types(iter: I) -> Self + where + I: IntoIterator, + T: Into, + N: Into, + { + let (names, types): (Vec<_>, Vec<_>) = iter.into_iter().unzip(); + let types = types.into_iter().map(Into::into).collect(); + let typ = RelationType::new(types); + Self::new_unchecked(typ, names) + } + /// Concatenates a `RelationDesc` onto the end of this `RelationDesc`. + pub fn concat(mut self, other: Self) -> Self { + let self_len = self.typ.column_types.len(); + self.names.extend(other.names); + self.typ.column_types.extend(other.typ.column_types); + for k in other.typ.keys { + let k = k + .column_indices + .into_iter() + .map(|idx| idx + self_len) + .collect(); + self = self.with_key(k); + } + self + } + + /// Appends a column with the specified name and type. + pub fn with_column(mut self, name: N, column_type: ColumnType) -> Self + where + N: Into, + { + self.typ.column_types.push(column_type); + self.names.push(name.into()); + self + } + + /// Adds a new key for the relation. + pub fn with_key(mut self, indices: Vec) -> Self { + self.typ = self.typ.with_key(indices); + self + } + + /// Drops all existing keys. + pub fn without_keys(mut self) -> Self { + self.typ.keys.clear(); + self + } + + /// Builds a new relation description with the column names replaced with + /// new names. + /// + pub fn try_with_names(self, names: I) -> Result + where + I: IntoIterator, + N: Into, + { + Self::try_new(self.typ, names) + } + + /// Computes the number of columns in the relation. + pub fn arity(&self) -> usize { + self.typ.arity() + } + + /// Returns the relation type underlying this relation description. + pub fn typ(&self) -> &RelationType { + &self.typ + } + + /// Returns an iterator over the columns in this relation. + pub fn iter(&self) -> impl Iterator { + self.iter_names().zip(self.iter_types()) + } + + /// Returns an iterator over the types of the columns in this relation. + pub fn iter_types(&self) -> impl Iterator { + self.typ.column_types.iter() + } + + /// Returns an iterator over the names of the columns in this relation. + pub fn iter_names(&self) -> impl Iterator { + self.names.iter() + } + + /// Finds a column by name. + /// + /// Returns the index and type of the column named `name`. If no column with + /// the specified name exists, returns `None`. If multiple columns have the + /// specified name, the leftmost column is returned. + pub fn get_by_name(&self, name: &ColumnName) -> Option<(usize, &ColumnType)> { + self.iter_names() + .position(|n| n == name) + .map(|i| (i, &self.typ.column_types[i])) + } + + /// Gets the name of the `i`th column. + /// + /// # Panics + /// + /// Panics if `i` is not a valid column index. + pub fn get_name(&self, i: usize) -> &ColumnName { + &self.names[i] + } + + /// Mutably gets the name of the `i`th column. + /// + /// # Panics + /// + /// Panics if `i` is not a valid column index. + pub fn get_name_mut(&mut self, i: usize) -> &mut ColumnName { + &mut self.names[i] + } + + /// Gets the name of the `i`th column if that column name is unambiguous. + /// + /// If at least one other column has the same name as the `i`th column, + /// returns `None`. If the `i`th column has no name, returns `None`. + /// + /// # Panics + /// + /// Panics if `i` is not a valid column index. + pub fn get_unambiguous_name(&self, i: usize) -> Option<&ColumnName> { + let name = &self.names[i]; + if self.iter_names().filter(|n| *n == name).count() == 1 { + Some(name) + } else { + None + } + } +} + +/// The name of a column in a [`RelationDesc`]. +pub type ColumnName = String;