From 0cd3d23556e27e11db007485dbf36239986cad8c Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Sat, 28 Mar 2026 01:48:20 +0000 Subject: [PATCH 1/9] Elixir bindings initial PR --- Cargo.toml | 2 +- bindings/elixir/.formatter.exs | 20 + bindings/elixir/.gitignore | 9 + bindings/elixir/README.md | 60 +++ bindings/elixir/lib/fluss.ex | 53 +++ bindings/elixir/lib/fluss/admin.ex | 98 +++++ bindings/elixir/lib/fluss/append_writer.ex | 71 +++ bindings/elixir/lib/fluss/config.ex | 58 +++ bindings/elixir/lib/fluss/connection.ex | 48 +++ bindings/elixir/lib/fluss/log_scanner.ex | 87 ++++ bindings/elixir/lib/fluss/native.ex | 88 ++++ bindings/elixir/lib/fluss/schema.ex | 87 ++++ bindings/elixir/lib/fluss/table.ex | 48 +++ bindings/elixir/lib/fluss/table_descriptor.ex | 54 +++ bindings/elixir/lib/fluss/write_handle.ex | 40 ++ bindings/elixir/mix.exs | 61 +++ bindings/elixir/mix.lock | 13 + bindings/elixir/native/fluss_nif/Cargo.toml | 32 ++ bindings/elixir/native/fluss_nif/src/admin.rs | 108 +++++ .../native/fluss_nif/src/append_writer.rs | 71 +++ bindings/elixir/native/fluss_nif/src/atoms.rs | 44 ++ .../elixir/native/fluss_nif/src/config.rs | 73 ++++ .../elixir/native/fluss_nif/src/connection.rs | 40 ++ bindings/elixir/native/fluss_nif/src/lib.rs | 46 ++ .../native/fluss_nif/src/log_scanner.rs | 170 ++++++++ .../native/fluss_nif/src/row_convert.rs | 256 +++++++++++ .../elixir/native/fluss_nif/src/schema.rs | 160 +++++++ bindings/elixir/native/fluss_nif/src/table.rs | 85 ++++ .../native/fluss_nif/src/write_handle.rs | 48 +++ bindings/elixir/test/fluss_test.exs | 112 +++++ .../test/integration/log_table_test.exs | 407 ++++++++++++++++++ bindings/elixir/test/support/cluster.ex | 211 +++++++++ bindings/elixir/test/test_helper.exs | 27 ++ 33 files changed, 2786 insertions(+), 1 deletion(-) create mode 100644 bindings/elixir/.formatter.exs create mode 100644 bindings/elixir/.gitignore create mode 100644 bindings/elixir/README.md create mode 100644 bindings/elixir/lib/fluss.ex create mode 100644 bindings/elixir/lib/fluss/admin.ex create mode 100644 bindings/elixir/lib/fluss/append_writer.ex create mode 100644 bindings/elixir/lib/fluss/config.ex create mode 100644 bindings/elixir/lib/fluss/connection.ex create mode 100644 bindings/elixir/lib/fluss/log_scanner.ex create mode 100644 bindings/elixir/lib/fluss/native.ex create mode 100644 bindings/elixir/lib/fluss/schema.ex create mode 100644 bindings/elixir/lib/fluss/table.ex create mode 100644 bindings/elixir/lib/fluss/table_descriptor.ex create mode 100644 bindings/elixir/lib/fluss/write_handle.ex create mode 100644 bindings/elixir/mix.exs create mode 100644 bindings/elixir/mix.lock create mode 100644 bindings/elixir/native/fluss_nif/Cargo.toml create mode 100644 bindings/elixir/native/fluss_nif/src/admin.rs create mode 100644 bindings/elixir/native/fluss_nif/src/append_writer.rs create mode 100644 bindings/elixir/native/fluss_nif/src/atoms.rs create mode 100644 bindings/elixir/native/fluss_nif/src/config.rs create mode 100644 bindings/elixir/native/fluss_nif/src/connection.rs create mode 100644 bindings/elixir/native/fluss_nif/src/lib.rs create mode 100644 bindings/elixir/native/fluss_nif/src/log_scanner.rs create mode 100644 bindings/elixir/native/fluss_nif/src/row_convert.rs create mode 100644 bindings/elixir/native/fluss_nif/src/schema.rs create mode 100644 bindings/elixir/native/fluss_nif/src/table.rs create mode 100644 bindings/elixir/native/fluss_nif/src/write_handle.rs create mode 100644 bindings/elixir/test/fluss_test.exs create mode 100644 bindings/elixir/test/integration/log_table_test.exs create mode 100644 bindings/elixir/test/support/cluster.ex create mode 100644 bindings/elixir/test/test_helper.exs diff --git a/Cargo.toml b/Cargo.toml index 2abdbf06..b625ab34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ keywords = ["fluss", "streaming-storage", "datalake"] [workspace] resolver = "2" -members = ["crates/fluss", "crates/fluss-test-cluster", "crates/examples", "bindings/python", "bindings/cpp"] +members = ["crates/fluss", "crates/fluss-test-cluster", "crates/examples", "bindings/python", "bindings/cpp", "bindings/elixir/native/fluss_nif"] [workspace.dependencies] fluss = { package = "fluss-rs", version = "0.2.0", path = "crates/fluss", features = ["storage-all"] } diff --git a/bindings/elixir/.formatter.exs b/bindings/elixir/.formatter.exs new file mode 100644 index 00000000..dd63ff52 --- /dev/null +++ b/bindings/elixir/.formatter.exs @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/bindings/elixir/.gitignore b/bindings/elixir/.gitignore new file mode 100644 index 00000000..90277ffb --- /dev/null +++ b/bindings/elixir/.gitignore @@ -0,0 +1,9 @@ +# Elixir build artifacts +_build/ +deps/ + +# Generated NIF shared library +priv/native/ + +# Crash dumps +erl_crash.dump diff --git a/bindings/elixir/README.md b/bindings/elixir/README.md new file mode 100644 index 00000000..656b03c5 --- /dev/null +++ b/bindings/elixir/README.md @@ -0,0 +1,60 @@ +# Fluss Elixir Client + +Elixir client for [Apache Fluss (Incubating)](https://fluss.apache.org/), built on the official Rust client via [Rustler](https://github.com/rusterlium/rustler) NIFs. + +Currently supports **log tables** (append + scan). Primary key (KV) table support is planned. + +## Requirements + +- Elixir >= 1.15 +- Rust stable toolchain (for compiling the NIF) + +## Quick Start + +```elixir +config = Fluss.Config.new("localhost:9123") +conn = Fluss.Connection.new!(config) +admin = Fluss.Admin.new!(conn) + +schema = + Fluss.Schema.build() + |> Fluss.Schema.column("ts", :bigint) + |> Fluss.Schema.column("message", :string) + |> Fluss.Schema.build!() + +:ok = Fluss.Admin.create_table(admin, "my_db", "events", Fluss.TableDescriptor.new!(schema)) + +table = Fluss.Table.get!(conn, "my_db", "events") +writer = Fluss.AppendWriter.new!(table) +Fluss.AppendWriter.append(writer, [1_700_000_000, "hello"]) +:ok = Fluss.AppendWriter.flush(writer) + +scanner = Fluss.LogScanner.new!(table) +:ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) +:ok = Fluss.LogScanner.poll(scanner, 5_000) + +receive do + {:fluss_records, records} -> + for record <- records, do: IO.inspect(record[:row]) +end +``` + +## Data Types + +Simple: `:boolean`, `:tinyint`, `:smallint`, `:int`, `:bigint`, `:float`, `:double`, `:string`, `:bytes`, `:date`, `:time`, `:timestamp`, `:timestamp_ltz` + +Parameterized: `{:decimal, precision, scale}`, `{:char, length}`, `{:binary, length}` + +## Development + +```bash +cd bindings/elixir +mix test # unit tests +mix test --include integration # starts Docker cluster +``` + +Set `FLUSS_BOOTSTRAP_SERVERS` to use an existing cluster. + +## License + +Apache License 2.0 diff --git a/bindings/elixir/lib/fluss.ex b/bindings/elixir/lib/fluss.ex new file mode 100644 index 00000000..c6ceea36 --- /dev/null +++ b/bindings/elixir/lib/fluss.ex @@ -0,0 +1,53 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss do + @moduledoc """ + Elixir client for Apache Fluss (Incubating). + + ## Examples + + config = Fluss.Config.new("localhost:9123") + conn = Fluss.Connection.new!(config) + admin = Fluss.Admin.new!(conn) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("ts", :bigint) + |> Fluss.Schema.column("message", :string) + |> Fluss.Schema.build!() + + :ok = Fluss.Admin.create_table(admin, "my_db", "events", Fluss.TableDescriptor.new!(schema)) + + table = Fluss.Table.get!(conn, "my_db", "events") + writer = Fluss.AppendWriter.new!(table) + Fluss.AppendWriter.append(writer, [1_700_000_000, "hello"]) + :ok = Fluss.AppendWriter.flush(writer) + + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) + :ok = Fluss.LogScanner.poll(scanner, 5_000) + receive do + {:fluss_records, records} -> records + end + + """ + + alias Fluss.Native + + def earliest_offset, do: Native.earliest_offset() +end diff --git a/bindings/elixir/lib/fluss/admin.ex b/bindings/elixir/lib/fluss/admin.ex new file mode 100644 index 00000000..dfc1720b --- /dev/null +++ b/bindings/elixir/lib/fluss/admin.ex @@ -0,0 +1,98 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Admin do + @moduledoc """ + Admin client for DDL operations (create/drop databases and tables). + + ## Examples + + admin = Fluss.Admin.new!(conn) + :ok = Fluss.Admin.create_database(admin, "my_db") + :ok = Fluss.Admin.create_table(admin, "my_db", "events", descriptor) + + """ + + alias Fluss.Native + + @type t :: reference() + + @spec new(Fluss.Connection.t()) :: {:ok, t()} | {:error, String.t()} + def new(conn) do + case Native.admin_new(conn) do + {:error, _} = err -> err + admin -> {:ok, admin} + end + end + + @spec new!(Fluss.Connection.t()) :: t() + def new!(conn) do + case Native.admin_new(conn) do + {:error, reason} -> raise "failed to create admin: #{reason}" + admin -> admin + end + end + + @spec create_database(t(), String.t(), boolean()) :: :ok | {:error, String.t()} + def create_database(admin, name, ignore_if_exists \\ true), + do: Native.admin_create_database(admin, name, ignore_if_exists) + + @spec drop_database(t(), String.t(), boolean()) :: :ok | {:error, String.t()} + def drop_database(admin, name, ignore_if_not_exists \\ true), + do: Native.admin_drop_database(admin, name, ignore_if_not_exists) + + @spec list_databases(t()) :: {:ok, [String.t()]} | {:error, String.t()} + def list_databases(admin) do + case Native.admin_list_databases(admin) do + {:error, _} = err -> err + dbs -> {:ok, dbs} + end + end + + @spec list_databases!(t()) :: [String.t()] + def list_databases!(admin) do + case Native.admin_list_databases(admin) do + {:error, reason} -> raise "failed to list databases: #{reason}" + dbs -> dbs + end + end + + @spec create_table(t(), String.t(), String.t(), Fluss.TableDescriptor.t(), boolean()) :: + :ok | {:error, String.t()} + def create_table(admin, database, table, descriptor, ignore_if_exists \\ true), + do: Native.admin_create_table(admin, database, table, descriptor, ignore_if_exists) + + @spec drop_table(t(), String.t(), String.t(), boolean()) :: :ok | {:error, String.t()} + def drop_table(admin, database, table, ignore_if_not_exists \\ true), + do: Native.admin_drop_table(admin, database, table, ignore_if_not_exists) + + @spec list_tables(t(), String.t()) :: {:ok, [String.t()]} | {:error, String.t()} + def list_tables(admin, database) do + case Native.admin_list_tables(admin, database) do + {:error, _} = err -> err + tables -> {:ok, tables} + end + end + + @spec list_tables!(t(), String.t()) :: [String.t()] + def list_tables!(admin, database) do + case Native.admin_list_tables(admin, database) do + {:error, reason} -> raise "failed to list tables: #{reason}" + tables -> tables + end + end +end diff --git a/bindings/elixir/lib/fluss/append_writer.ex b/bindings/elixir/lib/fluss/append_writer.ex new file mode 100644 index 00000000..5e62b3f3 --- /dev/null +++ b/bindings/elixir/lib/fluss/append_writer.ex @@ -0,0 +1,71 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.AppendWriter do + @moduledoc """ + Writer for appending records to a log table. + + Values are passed as a list in column order. Use `nil` for null values. + `append/2` returns a `Fluss.WriteHandle` — drop it for fire-and-forget, + or call `Fluss.WriteHandle.wait/1` for per-record acknowledgment. + + ## Examples + + writer = Fluss.AppendWriter.new!(table) + + # Fire-and-forget + Fluss.AppendWriter.append(writer, [1_700_000_000, "hello"]) + Fluss.AppendWriter.append(writer, [1_700_000_001, "world"]) + :ok = Fluss.AppendWriter.flush(writer) + + # Per-record ack + {:ok, handle} = Fluss.AppendWriter.append(writer, [1_700_000_002, "critical"]) + :ok = Fluss.WriteHandle.wait(handle) + + """ + + alias Fluss.Native + + @type t :: reference() + + @spec new(Fluss.Table.t()) :: {:ok, t()} | {:error, String.t()} + def new(table) do + case Native.append_writer_new(table) do + {:error, _} = err -> err + w -> {:ok, w} + end + end + + @spec new!(Fluss.Table.t()) :: t() + def new!(table) do + case Native.append_writer_new(table) do + {:error, reason} -> raise "failed to create append writer: #{reason}" + w -> w + end + end + + @spec append(t(), list()) :: {:ok, Fluss.WriteHandle.t()} | {:error, String.t()} + def append(writer, values) when is_list(values) do + case Native.append_writer_append(writer, values) do + {:error, _} = err -> err + handle -> {:ok, handle} + end + end + + @spec flush(t()) :: :ok | {:error, String.t()} + def flush(writer), do: Native.append_writer_flush(writer) +end diff --git a/bindings/elixir/lib/fluss/config.ex b/bindings/elixir/lib/fluss/config.ex new file mode 100644 index 00000000..daeec1f6 --- /dev/null +++ b/bindings/elixir/lib/fluss/config.ex @@ -0,0 +1,58 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Config do + @moduledoc """ + Client configuration for connecting to a Fluss cluster. + + ## Examples + + config = Fluss.Config.new("localhost:9123") + + config = + Fluss.Config.default() + |> Fluss.Config.set_bootstrap_servers("host1:9123,host2:9123") + |> Fluss.Config.set_writer_batch_size(1_048_576) + + """ + + alias Fluss.Native + + @type t :: reference() + + @spec new(String.t()) :: t() + def new(bootstrap_servers) when is_binary(bootstrap_servers) do + Native.config_new(bootstrap_servers) + end + + @spec default() :: t() + def default, do: Native.config_default() + + @spec set_bootstrap_servers(t(), String.t()) :: t() + def set_bootstrap_servers(config, servers), + do: Native.config_set_bootstrap_servers(config, servers) + + @spec set_writer_batch_size(t(), integer()) :: t() + def set_writer_batch_size(config, size), do: Native.config_set_writer_batch_size(config, size) + + @spec set_writer_batch_timeout_ms(t(), integer()) :: t() + def set_writer_batch_timeout_ms(config, ms), + do: Native.config_set_writer_batch_timeout_ms(config, ms) + + @spec get_bootstrap_servers(t()) :: String.t() + def get_bootstrap_servers(config), do: Native.config_get_bootstrap_servers(config) +end diff --git a/bindings/elixir/lib/fluss/connection.ex b/bindings/elixir/lib/fluss/connection.ex new file mode 100644 index 00000000..20fb6b59 --- /dev/null +++ b/bindings/elixir/lib/fluss/connection.ex @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Connection do + @moduledoc """ + A connection to a Fluss cluster. + + ## Examples + + config = Fluss.Config.new("localhost:9123") + {:ok, conn} = Fluss.Connection.new(config) + + """ + + alias Fluss.Native + + @type t :: reference() + + @spec new(Fluss.Config.t()) :: {:ok, t()} | {:error, String.t()} + def new(config) do + case Native.connection_new(config) do + {:error, _} = err -> err + conn -> {:ok, conn} + end + end + + @spec new!(Fluss.Config.t()) :: t() + def new!(config) do + case Native.connection_new(config) do + {:error, reason} -> raise "failed to connect to Fluss: #{reason}" + conn -> conn + end + end +end diff --git a/bindings/elixir/lib/fluss/log_scanner.ex b/bindings/elixir/lib/fluss/log_scanner.ex new file mode 100644 index 00000000..f6c2d1be --- /dev/null +++ b/bindings/elixir/lib/fluss/log_scanner.ex @@ -0,0 +1,87 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.LogScanner do + @moduledoc """ + Scanner for reading records from a log table. + + `poll/2` is non-blocking — it returns `:ok` immediately and sends results + as `{:fluss_records, records}` or `{:fluss_poll_error, reason}` to the + calling process. No dirty scheduler threads are held during the wait. + + Each record is an atom-keyed map: `:offset`, `:timestamp`, `:change_type`, `:row`. + Row values are also atom-keyed (column names interned as atoms). + + ## Examples + + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) + :ok = Fluss.LogScanner.poll(scanner, 5_000) + + receive do + {:fluss_records, records} -> + for record <- records, do: IO.inspect(record[:row]) + {:fluss_poll_error, reason} -> + IO.puts("poll error: \#{reason}") + end + + """ + + alias Fluss.Native + + @type t :: reference() + @type record :: %{atom() => term()} + + @spec new(Fluss.Table.t()) :: {:ok, t()} | {:error, String.t()} + def new(table) do + case Native.log_scanner_new(table) do + {:error, _} = err -> err + s -> {:ok, s} + end + end + + @spec new!(Fluss.Table.t()) :: t() + def new!(table) do + case Native.log_scanner_new(table) do + {:error, reason} -> raise "failed to create log scanner: #{reason}" + s -> s + end + end + + @spec subscribe(t(), integer(), integer()) :: :ok | {:error, String.t()} + def subscribe(scanner, bucket, offset), + do: Native.log_scanner_subscribe(scanner, bucket, offset) + + @doc """ + Subscribes to multiple buckets. Takes a list of `{bucket_id, offset}` tuples. + """ + @spec subscribe_buckets(t(), [{integer(), integer()}]) :: :ok | {:error, String.t()} + def subscribe_buckets(scanner, bucket_offsets) when is_list(bucket_offsets), + do: Native.log_scanner_subscribe_buckets(scanner, bucket_offsets) + + @spec unsubscribe(t(), integer()) :: :ok | {:error, String.t()} + def unsubscribe(scanner, bucket), + do: Native.log_scanner_unsubscribe(scanner, bucket) + + @doc """ + Starts a non-blocking poll. Returns `:ok` immediately. + Results arrive as `{:fluss_records, [record]}` or `{:fluss_poll_error, reason}`. + """ + @spec poll(t(), non_neg_integer()) :: :ok + def poll(scanner, timeout_ms), + do: Native.log_scanner_poll(scanner, timeout_ms) +end diff --git a/bindings/elixir/lib/fluss/native.ex b/bindings/elixir/lib/fluss/native.ex new file mode 100644 index 00000000..a69636ed --- /dev/null +++ b/bindings/elixir/lib/fluss/native.ex @@ -0,0 +1,88 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Native do + @moduledoc false + use Rustler, otp_app: :fluss, crate: "fluss_nif" + + # Config + def config_new(_bootstrap_servers), do: :erlang.nif_error(:nif_not_loaded) + def config_default, do: :erlang.nif_error(:nif_not_loaded) + def config_set_bootstrap_servers(_config, _servers), do: :erlang.nif_error(:nif_not_loaded) + def config_set_writer_batch_size(_config, _size), do: :erlang.nif_error(:nif_not_loaded) + def config_set_writer_batch_timeout_ms(_config, _ms), do: :erlang.nif_error(:nif_not_loaded) + def config_get_bootstrap_servers(_config), do: :erlang.nif_error(:nif_not_loaded) + + # Connection + def connection_new(_config), do: :erlang.nif_error(:nif_not_loaded) + + # Admin + def admin_new(_conn), do: :erlang.nif_error(:nif_not_loaded) + + def admin_create_database(_admin, _name, _ignore_if_exists), + do: :erlang.nif_error(:nif_not_loaded) + + def admin_drop_database(_admin, _name, _ignore_if_not_exists), + do: :erlang.nif_error(:nif_not_loaded) + + def admin_list_databases(_admin), do: :erlang.nif_error(:nif_not_loaded) + + def admin_create_table(_admin, _db, _table, _descriptor, _ignore_if_exists), + do: :erlang.nif_error(:nif_not_loaded) + + def admin_drop_table(_admin, _db, _table, _ignore_if_not_exists), + do: :erlang.nif_error(:nif_not_loaded) + + def admin_list_tables(_admin, _database), do: :erlang.nif_error(:nif_not_loaded) + + # Schema + def schema_builder_new, do: :erlang.nif_error(:nif_not_loaded) + def schema_builder_column(_builder, _name, _data_type), do: :erlang.nif_error(:nif_not_loaded) + def schema_builder_primary_key(_builder, _keys), do: :erlang.nif_error(:nif_not_loaded) + def schema_builder_build(_builder), do: :erlang.nif_error(:nif_not_loaded) + def table_descriptor_new(_schema), do: :erlang.nif_error(:nif_not_loaded) + def table_descriptor_with_bucket_count(_schema, _count), do: :erlang.nif_error(:nif_not_loaded) + + def table_descriptor_with_properties(_schema, _properties), + do: :erlang.nif_error(:nif_not_loaded) + + # Table + def table_get(_conn, _database, _table), do: :erlang.nif_error(:nif_not_loaded) + def table_has_primary_key(_table), do: :erlang.nif_error(:nif_not_loaded) + def table_column_names(_table), do: :erlang.nif_error(:nif_not_loaded) + + # AppendWriter + def append_writer_new(_table), do: :erlang.nif_error(:nif_not_loaded) + def append_writer_append(_writer, _values), do: :erlang.nif_error(:nif_not_loaded) + def append_writer_flush(_writer), do: :erlang.nif_error(:nif_not_loaded) + + # LogScanner + def log_scanner_new(_table), do: :erlang.nif_error(:nif_not_loaded) + def log_scanner_subscribe(_scanner, _bucket, _offset), do: :erlang.nif_error(:nif_not_loaded) + + def log_scanner_subscribe_buckets(_scanner, _bucket_offsets), + do: :erlang.nif_error(:nif_not_loaded) + + def log_scanner_unsubscribe(_scanner, _bucket), do: :erlang.nif_error(:nif_not_loaded) + def log_scanner_poll(_scanner, _timeout_ms), do: :erlang.nif_error(:nif_not_loaded) + + # WriteHandle + def write_handle_wait(_handle), do: :erlang.nif_error(:nif_not_loaded) + + # Constants + def earliest_offset, do: :erlang.nif_error(:nif_not_loaded) +end diff --git a/bindings/elixir/lib/fluss/schema.ex b/bindings/elixir/lib/fluss/schema.ex new file mode 100644 index 00000000..566a9b0c --- /dev/null +++ b/bindings/elixir/lib/fluss/schema.ex @@ -0,0 +1,87 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Schema do + @moduledoc """ + Schema builder for defining table columns and primary keys. + + Simple types: `:boolean`, `:tinyint`, `:smallint`, `:int`, `:bigint`, + `:float`, `:double`, `:string`, `:bytes`, `:date`, `:time`, `:timestamp`, `:timestamp_ltz` + + Parameterized types: `{:decimal, precision, scale}`, `{:char, length}`, `{:binary, length}` + + ## Examples + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.column("name", :string) + |> Fluss.Schema.column("amount", {:decimal, 10, 2}) + |> Fluss.Schema.build!() + + """ + + alias Fluss.Native + + @type t :: reference() + @type builder :: reference() + + @type data_type :: + :boolean + | :tinyint + | :smallint + | :int + | :bigint + | :float + | :double + | :string + | :bytes + | :date + | :time + | :timestamp + | :timestamp_ltz + | {:decimal, non_neg_integer(), non_neg_integer()} + | {:char, non_neg_integer()} + | {:binary, non_neg_integer()} + + @spec build() :: builder() + def build, do: Native.schema_builder_new() + + @spec column(builder(), String.t(), data_type()) :: builder() + def column(builder, name, data_type) do + case Native.schema_builder_column(builder, name, data_type) do + {:error, reason} -> raise "failed to add column: #{reason}" + ref -> ref + end + end + + @spec primary_key(builder(), [String.t()]) :: builder() + def primary_key(builder, keys) do + case Native.schema_builder_primary_key(builder, keys) do + {:error, reason} -> raise "failed to set primary key: #{reason}" + ref -> ref + end + end + + @spec build!(builder()) :: t() + def build!(builder) do + case Native.schema_builder_build(builder) do + {:error, reason} -> raise "failed to build schema: #{reason}" + ref -> ref + end + end +end diff --git a/bindings/elixir/lib/fluss/table.ex b/bindings/elixir/lib/fluss/table.ex new file mode 100644 index 00000000..7d469244 --- /dev/null +++ b/bindings/elixir/lib/fluss/table.ex @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Table do + @moduledoc """ + A handle to a Fluss table, used to create writers and scanners. + """ + + alias Fluss.Native + + @type t :: reference() + + @spec get(Fluss.Connection.t(), String.t(), String.t()) :: {:ok, t()} | {:error, String.t()} + def get(conn, database, table) do + case Native.table_get(conn, database, table) do + {:error, _} = err -> err + t -> {:ok, t} + end + end + + @spec get!(Fluss.Connection.t(), String.t(), String.t()) :: t() + def get!(conn, database, table) do + case Native.table_get(conn, database, table) do + {:error, reason} -> raise "failed to get table: #{reason}" + t -> t + end + end + + @spec has_primary_key?(t()) :: boolean() + def has_primary_key?(table), do: Native.table_has_primary_key(table) + + @spec column_names(t()) :: [String.t()] + def column_names(table), do: Native.table_column_names(table) +end diff --git a/bindings/elixir/lib/fluss/table_descriptor.ex b/bindings/elixir/lib/fluss/table_descriptor.ex new file mode 100644 index 00000000..612d385e --- /dev/null +++ b/bindings/elixir/lib/fluss/table_descriptor.ex @@ -0,0 +1,54 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.TableDescriptor do + @moduledoc """ + Descriptor for creating a Fluss table. + + Options: `:bucket_count`, `:properties` (list of `{key, value}` string tuples). + + ## Examples + + Fluss.TableDescriptor.new!(schema) + Fluss.TableDescriptor.new!(schema, bucket_count: 3) + + """ + + alias Fluss.Native + + @type t :: reference() + + @spec new!(Fluss.Schema.t(), keyword()) :: t() + def new!(schema, opts \\ []) do + result = + cond do + Keyword.has_key?(opts, :bucket_count) -> + Native.table_descriptor_with_bucket_count(schema, opts[:bucket_count]) + + Keyword.has_key?(opts, :properties) -> + Native.table_descriptor_with_properties(schema, opts[:properties]) + + true -> + Native.table_descriptor_new(schema) + end + + case result do + {:error, reason} -> raise "failed to create table descriptor: #{reason}" + ref -> ref + end + end +end diff --git a/bindings/elixir/lib/fluss/write_handle.ex b/bindings/elixir/lib/fluss/write_handle.ex new file mode 100644 index 00000000..eb1e696b --- /dev/null +++ b/bindings/elixir/lib/fluss/write_handle.ex @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.WriteHandle do + @moduledoc """ + Handle for a pending write operation. + + Returned by `Fluss.AppendWriter.append/2`. Drop for fire-and-forget, + or call `wait/1` for per-record server acknowledgment. + """ + + alias Fluss.Native + + @type t :: reference() + + @spec wait(t()) :: :ok | {:error, String.t()} + def wait(handle), do: Native.write_handle_wait(handle) + + @spec wait!(t()) :: :ok + def wait!(handle) do + case wait(handle) do + :ok -> :ok + {:error, reason} -> raise "write failed: #{reason}" + end + end +end diff --git a/bindings/elixir/mix.exs b/bindings/elixir/mix.exs new file mode 100644 index 00000000..f5d416d8 --- /dev/null +++ b/bindings/elixir/mix.exs @@ -0,0 +1,61 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.MixProject do + use Mix.Project + + @version "0.1.0" + + def project do + [ + app: :fluss, + version: @version, + elixir: "~> 1.15", + start_permanent: Mix.env() == :prod, + elixirc_paths: elixirc_paths(Mix.env()), + deps: deps(), + description: "Elixir client for Apache Fluss", + package: package() + ] + end + + def application do + [ + extra_applications: [:logger] + ] + end + + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + defp deps do + [ + {:rustler, "~> 0.37"}, + {:ex_doc, "~> 0.31", only: :dev, runtime: false}, + {:credo, "~> 1.7", only: [:dev, :test], runtime: false} + ] + end + + defp package do + [ + licenses: ["Apache-2.0"], + links: %{ + "GitHub" => "https://github.com/apache/fluss-rust" + } + ] + end +end diff --git a/bindings/elixir/mix.lock b/bindings/elixir/mix.lock new file mode 100644 index 00000000..b1170d3f --- /dev/null +++ b/bindings/elixir/mix.lock @@ -0,0 +1,13 @@ +%{ + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "credo": {:hex, :credo, "1.7.17", "f92b6aa5b26301eaa5a35e4d48ebf5aa1e7094ac00ae38f87086c562caf8a22f", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1eb5645c835f0b6c9b5410f94b5a185057bcf6d62a9c2b476da971cde8749645"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, + "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "rustler": {:hex, :rustler, "0.37.3", "5f4e6634d43b26f0a69834dd1d3ed4e1710b022a053bf4a670220c9540c92602", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a6872c6f53dcf00486d1e7f9e046e20e01bf1654bdacc4193016c2e8002b32a2"}, +} diff --git a/bindings/elixir/native/fluss_nif/Cargo.toml b/bindings/elixir/native/fluss_nif/Cargo.toml new file mode 100644 index 00000000..90858606 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/Cargo.toml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +[package] +name = "fluss_nif" +version = "0.1.0" +edition = "2024" + +[lib] +name = "fluss_nif" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +fluss = { package = "fluss-rs", path = "../../../../crates/fluss" } +bigdecimal = "0.4" +rustler = "0.37" +tokio = { version = "1", features = ["full"] } diff --git a/bindings/elixir/native/fluss_nif/src/admin.rs b/bindings/elixir/native/fluss_nif/src/admin.rs new file mode 100644 index 00000000..984b4976 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/admin.rs @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::{self, to_nif_err}; +use crate::connection::ConnectionResource; +use crate::schema::TableDescriptorResource; +use crate::RUNTIME; +use fluss::client::FlussAdmin; +use fluss::metadata::TablePath; +use rustler::{Atom, ResourceArc}; +use std::sync::Arc; + +pub struct AdminResource(pub Arc); + +impl std::panic::RefUnwindSafe for AdminResource {} + +#[rustler::resource_impl] +impl rustler::Resource for AdminResource {} + +#[rustler::nif] +fn admin_new(conn: ResourceArc) -> Result, rustler::Error> { + let admin = conn.0.get_admin().map_err(to_nif_err)?; + Ok(ResourceArc::new(AdminResource(admin))) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn admin_create_database( + admin: ResourceArc, + database_name: String, + ignore_if_exists: bool, +) -> Result { + RUNTIME + .block_on(admin.0.create_database(&database_name, None, ignore_if_exists)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn admin_drop_database( + admin: ResourceArc, + database_name: String, + ignore_if_not_exists: bool, +) -> Result { + RUNTIME + .block_on(admin.0.drop_database(&database_name, ignore_if_not_exists, false)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn admin_list_databases(admin: ResourceArc) -> Result, rustler::Error> { + RUNTIME + .block_on(admin.0.list_databases()) + .map_err(to_nif_err) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn admin_create_table( + admin: ResourceArc, + database_name: String, + table_name: String, + descriptor: ResourceArc, + ignore_if_exists: bool, +) -> Result { + let path = TablePath::new(&database_name, &table_name); + RUNTIME + .block_on(admin.0.create_table(&path, &descriptor.0, ignore_if_exists)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn admin_drop_table( + admin: ResourceArc, + database_name: String, + table_name: String, + ignore_if_not_exists: bool, +) -> Result { + let path = TablePath::new(&database_name, &table_name); + RUNTIME + .block_on(admin.0.drop_table(&path, ignore_if_not_exists)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn admin_list_tables( + admin: ResourceArc, + database_name: String, +) -> Result, rustler::Error> { + RUNTIME + .block_on(admin.0.list_tables(&database_name)) + .map_err(to_nif_err) +} diff --git a/bindings/elixir/native/fluss_nif/src/append_writer.rs b/bindings/elixir/native/fluss_nif/src/append_writer.rs new file mode 100644 index 00000000..f60967ff --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/append_writer.rs @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::{self, to_nif_err}; +use crate::row_convert; +use crate::table::TableResource; +use crate::write_handle::WriteHandleResource; +use crate::RUNTIME; +use fluss::client::AppendWriter; +use fluss::metadata::Column; +use rustler::{Atom, Env, ResourceArc, Term}; + +pub struct AppendWriterResource { + pub writer: AppendWriter, + pub columns: Vec, +} + +impl std::panic::RefUnwindSafe for AppendWriterResource {} + +#[rustler::resource_impl] +impl rustler::Resource for AppendWriterResource {} + +#[rustler::nif] +fn append_writer_new( + table: ResourceArc, +) -> Result, rustler::Error> { + // WriterClient::new() calls tokio::spawn internally. + let _guard = RUNTIME.enter(); + let (writer, columns) = table.with_table(|t| { + let writer = t + .new_append() + .map_err(to_nif_err)? + .create_writer() + .map_err(to_nif_err)?; + Ok((writer, t.get_table_info().schema.columns().to_vec())) + })?; + Ok(ResourceArc::new(AppendWriterResource { writer, columns })) +} + +#[rustler::nif] +fn append_writer_append<'a>( + env: Env<'a>, + writer: ResourceArc, + values: Term<'a>, +) -> Result, rustler::Error> { + let row = row_convert::term_to_row(env, values, &writer.columns).map_err(to_nif_err)?; + let future = writer.writer.append(&row).map_err(to_nif_err)?; + Ok(ResourceArc::new(WriteHandleResource::new(future))) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn append_writer_flush(writer: ResourceArc) -> Result { + RUNTIME + .block_on(writer.writer.flush()) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} diff --git a/bindings/elixir/native/fluss_nif/src/atoms.rs b/bindings/elixir/native/fluss_nif/src/atoms.rs new file mode 100644 index 00000000..11e7f771 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/atoms.rs @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +rustler::atoms! { + ok, + nil, + + // Change types + append_only, + insert, + update_before, + update_after, + delete, + + // Poll result message tags + fluss_records, + fluss_poll_error, + + // Record map keys + offset, + timestamp, + change_type, + row, +} + +/// Convert any `Display` error into `rustler::Error::Term`, which the NIF +/// framework encodes as `{:error, reason_string}`. +pub fn to_nif_err(e: impl std::fmt::Display) -> rustler::Error { + rustler::Error::Term(Box::new(e.to_string())) +} diff --git a/bindings/elixir/native/fluss_nif/src/config.rs b/bindings/elixir/native/fluss_nif/src/config.rs new file mode 100644 index 00000000..fde21ed4 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/config.rs @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 fluss::config::Config; +use rustler::ResourceArc; + +pub struct ConfigResource(pub Config); + +impl std::panic::RefUnwindSafe for ConfigResource {} + +#[rustler::resource_impl] +impl rustler::Resource for ConfigResource {} + +#[rustler::nif] +fn config_new(bootstrap_servers: String) -> ResourceArc { + let mut config = Config::default(); + config.bootstrap_servers = bootstrap_servers; + ResourceArc::new(ConfigResource(config)) +} + +#[rustler::nif] +fn config_default() -> ResourceArc { + ResourceArc::new(ConfigResource(Config::default())) +} + +#[rustler::nif] +fn config_set_bootstrap_servers( + config: ResourceArc, + servers: String, +) -> ResourceArc { + let mut new_config = config.0.clone(); + new_config.bootstrap_servers = servers; + ResourceArc::new(ConfigResource(new_config)) +} + +#[rustler::nif] +fn config_set_writer_batch_size( + config: ResourceArc, + size: i32, +) -> ResourceArc { + let mut new_config = config.0.clone(); + new_config.writer_batch_size = size; + ResourceArc::new(ConfigResource(new_config)) +} + +#[rustler::nif] +fn config_set_writer_batch_timeout_ms( + config: ResourceArc, + timeout_ms: i64, +) -> ResourceArc { + let mut new_config = config.0.clone(); + new_config.writer_batch_timeout_ms = timeout_ms; + ResourceArc::new(ConfigResource(new_config)) +} + +#[rustler::nif] +fn config_get_bootstrap_servers(config: ResourceArc) -> String { + config.0.bootstrap_servers.clone() +} diff --git a/bindings/elixir/native/fluss_nif/src/connection.rs b/bindings/elixir/native/fluss_nif/src/connection.rs new file mode 100644 index 00000000..0c2db643 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/connection.rs @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::to_nif_err; +use crate::config::ConfigResource; +use crate::RUNTIME; +use fluss::client::FlussConnection; +use rustler::ResourceArc; +use std::sync::Arc; + +pub struct ConnectionResource(pub Arc); + +impl std::panic::RefUnwindSafe for ConnectionResource {} + +#[rustler::resource_impl] +impl rustler::Resource for ConnectionResource {} + +#[rustler::nif(schedule = "DirtyIo")] +fn connection_new( + config: ResourceArc, +) -> Result, rustler::Error> { + let conn = RUNTIME + .block_on(FlussConnection::new(config.0.clone())) + .map_err(to_nif_err)?; + Ok(ResourceArc::new(ConnectionResource(Arc::new(conn)))) +} diff --git a/bindings/elixir/native/fluss_nif/src/lib.rs b/bindings/elixir/native/fluss_nif/src/lib.rs new file mode 100644 index 00000000..31ee3625 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/lib.rs @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +// Rustler 0.37 wraps every NIF body in `std::panic::catch_unwind`, which requires +// all captured values (including `ResourceArc`) to be `RefUnwindSafe`. +// `ResourceArc` contains `*mut T`, so it is only `RefUnwindSafe` when `T` is. +// Our resource types contain `parking_lot` locks (`UnsafeCell`) which opt out of +// the auto-trait. We manually impl `RefUnwindSafe` on each resource type because +// panic safety is already guaranteed by the NIF boundary — a panic is caught and +// converted to an Erlang exception, never observed by Rust code. + +mod admin; +mod append_writer; +mod atoms; +mod config; +mod connection; +mod log_scanner; +mod row_convert; +mod schema; +mod table; +mod write_handle; + +use std::sync::LazyLock; + +static RUNTIME: LazyLock = LazyLock::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("failed to create tokio runtime") +}); + +rustler::init!("Elixir.Fluss.Native"); diff --git a/bindings/elixir/native/fluss_nif/src/log_scanner.rs b/bindings/elixir/native/fluss_nif/src/log_scanner.rs new file mode 100644 index 00000000..c6021d46 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/log_scanner.rs @@ -0,0 +1,170 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::{self, to_nif_err}; +use crate::row_convert; +use crate::table::TableResource; +use crate::RUNTIME; +use fluss::client::LogScanner; +use fluss::metadata::Column; +use fluss::record::ChangeType; +use rustler::env::OwnedEnv; +use rustler::types::LocalPid; +use rustler::{Atom, Encoder, Env, ResourceArc}; +use std::collections::HashMap; +use std::time::Duration; + +pub struct LogScannerResource { + pub scanner: LogScanner, + pub columns: Vec, +} + +impl std::panic::RefUnwindSafe for LogScannerResource {} + +#[rustler::resource_impl] +impl rustler::Resource for LogScannerResource {} + +#[rustler::nif] +fn log_scanner_new( + table: ResourceArc, +) -> Result, rustler::Error> { + let _guard = RUNTIME.enter(); + let (scanner, columns) = table.with_table(|t| { + let scanner = t + .new_scan() + .create_log_scanner() + .map_err(to_nif_err)?; + Ok((scanner, t.get_table_info().schema.columns().to_vec())) + })?; + Ok(ResourceArc::new(LogScannerResource { scanner, columns })) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn log_scanner_subscribe( + scanner: ResourceArc, + bucket: i32, + offset: i64, +) -> Result { + RUNTIME + .block_on(scanner.scanner.subscribe(bucket, offset)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn log_scanner_subscribe_buckets( + scanner: ResourceArc, + bucket_offsets: Vec<(i32, i64)>, +) -> Result { + let map: HashMap = bucket_offsets.into_iter().collect(); + RUNTIME + .block_on(scanner.scanner.subscribe_buckets(&map)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif(schedule = "DirtyIo")] +fn log_scanner_unsubscribe( + scanner: ResourceArc, + bucket: i32, +) -> Result { + RUNTIME + .block_on(scanner.scanner.unsubscribe(bucket)) + .map_err(to_nif_err)?; + Ok(atoms::ok()) +} + +#[rustler::nif] +fn log_scanner_poll( + env: Env, + scanner: ResourceArc, + timeout_ms: u64, +) -> Atom { + let pid = env.pid(); + let scanner = scanner.clone(); + + std::thread::spawn(move || { + let result = RUNTIME.block_on(scanner.scanner.poll(Duration::from_millis(timeout_ms))); + send_poll_result(&pid, result, &scanner.columns); + }); + + atoms::ok() +} + +fn send_poll_result( + pid: &LocalPid, + result: Result, + columns: &[Column], +) { + let mut msg_env = OwnedEnv::new(); + + match result { + Ok(scan_records) => { + let _ = msg_env.send_and_clear(pid, |env| { + let records = encode_scan_records(env, scan_records, columns); + (atoms::fluss_records(), records).encode(env) + }); + } + Err(e) => { + let _ = msg_env.send_and_clear(pid, |env| { + (atoms::fluss_poll_error(), e.to_string()).encode(env) + }); + } + } +} + +fn encode_scan_records<'a>( + env: Env<'a>, + scan_records: fluss::record::ScanRecords, + columns: &[Column], +) -> rustler::Term<'a> { + let column_atoms = row_convert::intern_column_atoms(env, columns); + let mut result = Vec::new(); + + for record in scan_records { + let row_map = match row_convert::row_to_term(env, record.row(), columns, &column_atoms) { + Ok(m) => m, + Err(_) => continue, + }; + let change_type_atom = match record.change_type() { + ChangeType::AppendOnly => atoms::append_only().encode(env), + ChangeType::Insert => atoms::insert().encode(env), + ChangeType::UpdateBefore => atoms::update_before().encode(env), + ChangeType::UpdateAfter => atoms::update_after().encode(env), + ChangeType::Delete => atoms::delete().encode(env), + }; + + if let Ok(record_map) = rustler::Term::map_from_pairs( + env, + &[ + (atoms::offset().encode(env), record.offset().encode(env)), + (atoms::timestamp().encode(env), record.timestamp().encode(env)), + (atoms::change_type().encode(env), change_type_atom), + (atoms::row().encode(env), row_map), + ], + ) { + result.push(record_map); + } + } + + result.encode(env) +} + +#[rustler::nif] +fn earliest_offset() -> i64 { + fluss::client::EARLIEST_OFFSET +} diff --git a/bindings/elixir/native/fluss_nif/src/row_convert.rs b/bindings/elixir/native/fluss_nif/src/row_convert.rs new file mode 100644 index 00000000..3fcdcad2 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/row_convert.rs @@ -0,0 +1,256 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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::str::FromStr; + +use fluss::metadata::{Column, DataType}; +use fluss::row::{Date, Decimal, GenericRow, InternalRow, Time, TimestampLtz, TimestampNtz}; +use rustler::types::binary::NewBinary; +use rustler::{Encoder, Env, Term}; + +use crate::atoms; + +pub fn intern_column_atoms<'a>(env: Env<'a>, columns: &[Column]) -> Vec { + columns + .iter() + .map(|col| rustler::Atom::from_str(env, col.name()).expect("valid atom")) + .collect() +} + +pub fn row_to_term<'a>( + env: Env<'a>, + row: &dyn InternalRow, + columns: &[Column], + column_atoms: &[rustler::Atom], +) -> Result, String> { + let pairs: Vec<(Term<'a>, Term<'a>)> = columns + .iter() + .enumerate() + .map(|(i, col)| { + let key = column_atoms[i].encode(env); + let value = field_to_term(env, row, i, col.data_type())?; + Ok((key, value)) + }) + .collect::>()?; + Term::map_from_pairs(env, &pairs).map_err(|_| "failed to create map".to_string()) +} + +fn field_to_term<'a>( + env: Env<'a>, + row: &dyn InternalRow, + pos: usize, + data_type: &DataType, +) -> Result, String> { + if row.is_null_at(pos).map_err(|e| e.to_string())? { + return Ok(atoms::nil().encode(env)); + } + + match data_type { + DataType::Boolean(_) => { + let v = row.get_boolean(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::TinyInt(_) => { + let v = row.get_byte(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::SmallInt(_) => { + let v = row.get_short(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::Int(_) => { + let v = row.get_int(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::BigInt(_) => { + let v = row.get_long(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::Float(_) => { + let v = row.get_float(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::Double(_) => { + let v = row.get_double(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::String(_) => { + let v = row.get_string(pos).map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::Char(ct) => { + let v = row + .get_char(pos, ct.length() as usize) + .map_err(|e| e.to_string())?; + Ok(v.encode(env)) + } + DataType::Bytes(_) => { + let v = row.get_bytes(pos).map_err(|e| e.to_string())?; + let mut bin = NewBinary::new(env, v.len()); + bin.as_mut_slice().copy_from_slice(v); + let binary: rustler::Binary = bin.into(); + Ok(binary.encode(env)) + } + DataType::Binary(bt) => { + let v = row + .get_binary(pos, bt.length()) + .map_err(|e| e.to_string())?; + let mut bin = NewBinary::new(env, v.len()); + bin.as_mut_slice().copy_from_slice(v); + let binary: rustler::Binary = bin.into(); + Ok(binary.encode(env)) + } + DataType::Date(_) => { + let v = row.get_date(pos).map_err(|e| e.to_string())?; + Ok(v.get_inner().encode(env)) + } + DataType::Time(_) => { + let v = row.get_time(pos).map_err(|e| e.to_string())?; + Ok(v.get_inner().encode(env)) + } + DataType::Timestamp(ts) => { + let v = row + .get_timestamp_ntz(pos, ts.precision()) + .map_err(|e| e.to_string())?; + Ok((v.get_millisecond(), v.get_nano_of_millisecond()).encode(env)) + } + DataType::TimestampLTz(ts) => { + let v = row + .get_timestamp_ltz(pos, ts.precision()) + .map_err(|e| e.to_string())?; + Ok((v.get_epoch_millisecond(), v.get_nano_of_millisecond()).encode(env)) + } + DataType::Decimal(dt) => { + let v = row + .get_decimal(pos, dt.precision() as usize, dt.scale() as usize) + .map_err(|e| e.to_string())?; + Ok(v.to_string().encode(env)) + } + _ => Err(format!("unsupported data type: {data_type:?}")), + } +} + +pub fn term_to_row<'a>( + env: Env<'a>, + values: Term<'a>, + columns: &[Column], +) -> Result, String> { + let list: Vec> = values + .decode() + .map_err(|_| "expected a list of values".to_string())?; + if list.len() != columns.len() { + return Err(format!( + "expected {} values, got {}", + columns.len(), + list.len() + )); + } + + let mut row = GenericRow::new(columns.len()); + for (i, (term, col)) in list.iter().zip(columns.iter()).enumerate() { + if term.is_atom() { + if let Ok(atom) = term.decode::() { + if atom == atoms::nil() { + continue; // leave as null + } + } + } + set_field_from_term(env, &mut row, i, *term, col.data_type())?; + } + Ok(row) +} + +fn set_field_from_term<'a>( + _env: Env<'a>, + row: &mut GenericRow<'static>, + pos: usize, + term: Term<'a>, + data_type: &DataType, +) -> Result<(), String> { + match data_type { + DataType::Boolean(_) => { + let v: bool = term.decode().map_err(|_| "expected boolean")?; + row.set_field(pos, v); + } + DataType::TinyInt(_) => { + let v: i8 = term.decode().map_err(|_| "expected integer for tinyint")?; + row.set_field(pos, v); + } + DataType::SmallInt(_) => { + let v: i16 = term + .decode() + .map_err(|_| "expected integer for smallint")?; + row.set_field(pos, v); + } + DataType::Int(_) => { + let v: i32 = term.decode().map_err(|_| "expected integer")?; + row.set_field(pos, v); + } + DataType::BigInt(_) => { + let v: i64 = term.decode().map_err(|_| "expected integer")?; + row.set_field(pos, v); + } + DataType::Date(_) => { + let v: i32 = term.decode().map_err(|_| "expected integer (days since epoch)")?; + row.set_field(pos, Date::new(v)); + } + DataType::Time(_) => { + let v: i32 = term.decode().map_err(|_| "expected integer (millis since midnight)")?; + row.set_field(pos, Time::new(v)); + } + DataType::Timestamp(_) => { + let (millis, nanos): (i64, i32) = term + .decode() + .map_err(|_| "expected {millis, nanos} tuple for timestamp")?; + let ts = TimestampNtz::from_millis_nanos(millis, nanos).map_err(|e| e.to_string())?; + row.set_field(pos, ts); + } + DataType::TimestampLTz(_) => { + let (millis, nanos): (i64, i32) = term + .decode() + .map_err(|_| "expected {millis, nanos} tuple for timestamp_ltz")?; + let ts = TimestampLtz::from_millis_nanos(millis, nanos).map_err(|e| e.to_string())?; + row.set_field(pos, ts); + } + DataType::Float(_) => { + let v: f64 = term.decode().map_err(|_| "expected number for float")?; + row.set_field(pos, v as f32); + } + DataType::Double(_) => { + let v: f64 = term.decode().map_err(|_| "expected number for double")?; + row.set_field(pos, v); + } + DataType::String(_) | DataType::Char(_) => { + let v: String = term.decode().map_err(|_| "expected string")?; + row.set_field(pos, v); + } + DataType::Decimal(dt) => { + let v: String = term.decode().map_err(|_| "expected string for decimal")?; + let bd = bigdecimal::BigDecimal::from_str(&v) + .map_err(|e| format!("failed to parse decimal '{v}': {e}"))?; + let decimal = Decimal::from_big_decimal(bd, dt.precision(), dt.scale()) + .map_err(|e| e.to_string())?; + row.set_field(pos, decimal); + } + DataType::Bytes(_) | DataType::Binary(_) => { + let bin: rustler::Binary = term.decode().map_err(|_| "expected binary")?; + row.set_field(pos, bin.as_slice().to_vec()); + } + _ => return Err(format!("unsupported data type for writing: {data_type:?}")), + } + Ok(()) +} diff --git a/bindings/elixir/native/fluss_nif/src/schema.rs b/bindings/elixir/native/fluss_nif/src/schema.rs new file mode 100644 index 00000000..11f5287c --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/schema.rs @@ -0,0 +1,160 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::to_nif_err; +use fluss::metadata::{DataTypes, Schema, SchemaBuilder, TableDescriptor}; +use rustler::{NifTaggedEnum, ResourceArc}; +use std::sync::Mutex; + +pub struct SchemaBuilderResource(pub Mutex>); +pub struct SchemaResource(pub Schema); +pub struct TableDescriptorResource(pub TableDescriptor); + +impl std::panic::RefUnwindSafe for SchemaBuilderResource {} +impl std::panic::RefUnwindSafe for SchemaResource {} +impl std::panic::RefUnwindSafe for TableDescriptorResource {} + +#[rustler::resource_impl] +impl rustler::Resource for SchemaBuilderResource {} + +#[rustler::resource_impl] +impl rustler::Resource for SchemaResource {} + +#[rustler::resource_impl] +impl rustler::Resource for TableDescriptorResource {} + +/// Fluss data type for NIF interop. +/// +/// Simple types map to atoms: `:int`, `:string`, etc. +/// Parameterized types map to tuples: `{:decimal, 10, 2}`, `{:char, 20}`. +#[derive(NifTaggedEnum)] +pub enum DataType { + Boolean, + Tinyint, + Smallint, + Int, + Bigint, + Float, + Double, + String, + Bytes, + Date, + Time, + Timestamp, + TimestampLtz, + Decimal(u32, u32), + Char(u32), + Binary(usize), +} + +fn to_fluss_type(dt: &DataType) -> fluss::metadata::DataType { + match dt { + DataType::Boolean => DataTypes::boolean(), + DataType::Tinyint => DataTypes::tinyint(), + DataType::Smallint => DataTypes::smallint(), + DataType::Int => DataTypes::int(), + DataType::Bigint => DataTypes::bigint(), + DataType::Float => DataTypes::float(), + DataType::Double => DataTypes::double(), + DataType::String => DataTypes::string(), + DataType::Bytes => DataTypes::bytes(), + DataType::Date => DataTypes::date(), + DataType::Time => DataTypes::time(), + DataType::Timestamp => DataTypes::timestamp(), + DataType::TimestampLtz => DataTypes::timestamp_ltz(), + DataType::Decimal(precision, scale) => DataTypes::decimal(*precision, *scale), + DataType::Char(length) => DataTypes::char(*length), + DataType::Binary(length) => DataTypes::binary(*length), + } +} + +#[rustler::nif] +fn schema_builder_new() -> ResourceArc { + ResourceArc::new(SchemaBuilderResource(Mutex::new(Some(Schema::builder())))) +} + +#[rustler::nif] +fn schema_builder_column( + builder: ResourceArc, + name: String, + data_type: DataType, +) -> Result, rustler::Error> { + let mut guard = builder.0.lock().unwrap(); + let b = guard.take().ok_or_else(|| to_nif_err("schema builder already consumed"))?; + *guard = Some(b.column(&name, to_fluss_type(&data_type))); + drop(guard); + Ok(builder) +} + +#[rustler::nif] +fn schema_builder_primary_key( + builder: ResourceArc, + keys: Vec, +) -> Result, rustler::Error> { + let mut guard = builder.0.lock().unwrap(); + let b = guard.take().ok_or_else(|| to_nif_err("schema builder already consumed"))?; + *guard = Some(b.primary_key(keys)); + drop(guard); + Ok(builder) +} + +#[rustler::nif] +fn schema_builder_build( + builder: ResourceArc, +) -> Result, rustler::Error> { + let mut guard = builder.0.lock().unwrap(); + let b = guard.take().ok_or_else(|| to_nif_err("schema builder already consumed"))?; + let schema = b.build().map_err(to_nif_err)?; + Ok(ResourceArc::new(SchemaResource(schema))) +} + +#[rustler::nif] +fn table_descriptor_new( + schema: ResourceArc, +) -> Result, rustler::Error> { + let descriptor = TableDescriptor::builder() + .schema(schema.0.clone()) + .build() + .map_err(to_nif_err)?; + Ok(ResourceArc::new(TableDescriptorResource(descriptor))) +} + +#[rustler::nif] +fn table_descriptor_with_bucket_count( + schema: ResourceArc, + bucket_count: i32, +) -> Result, rustler::Error> { + let descriptor = TableDescriptor::builder() + .schema(schema.0.clone()) + .distributed_by(Some(bucket_count), vec![]) + .build() + .map_err(to_nif_err)?; + Ok(ResourceArc::new(TableDescriptorResource(descriptor))) +} + +#[rustler::nif] +fn table_descriptor_with_properties( + schema: ResourceArc, + properties: Vec<(String, String)>, +) -> Result, rustler::Error> { + let mut builder = TableDescriptor::builder().schema(schema.0.clone()); + for (key, value) in properties { + builder = builder.property(&key, &value); + } + let descriptor = builder.build().map_err(to_nif_err)?; + Ok(ResourceArc::new(TableDescriptorResource(descriptor))) +} diff --git a/bindings/elixir/native/fluss_nif/src/table.rs b/bindings/elixir/native/fluss_nif/src/table.rs new file mode 100644 index 00000000..621f2f96 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/table.rs @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::to_nif_err; +use crate::connection::ConnectionResource; +use crate::RUNTIME; +use fluss::client::{FlussConnection, FlussTable, Metadata}; +use fluss::metadata::{Column, TableInfo, TablePath}; +use rustler::ResourceArc; +use std::sync::Arc; + +/// Holds the data needed to reconstruct FlussTable (which has a lifetime +/// tied to FlussConnection). We store the Arc to keep +/// it alive and reconstruct short-lived FlussTable instances on demand. +pub struct TableResource { + pub connection: Arc, + pub metadata: Arc, + pub table_info: TableInfo, +} + +impl std::panic::RefUnwindSafe for TableResource {} + +#[rustler::resource_impl] +impl rustler::Resource for TableResource {} + +impl TableResource { + pub fn columns(&self) -> &[Column] { + self.table_info.schema.columns() + } + + pub fn with_table(&self, f: impl FnOnce(&FlussTable<'_>) -> T) -> T { + let table = FlussTable::new( + &self.connection, + self.metadata.clone(), + self.table_info.clone(), + ); + f(&table) + } +} + +#[rustler::nif(schedule = "DirtyIo")] +fn table_get( + conn: ResourceArc, + database_name: String, + table_name: String, +) -> Result, rustler::Error> { + let path = TablePath::new(&database_name, &table_name); + let table = RUNTIME + .block_on(conn.0.get_table(&path)) + .map_err(to_nif_err)?; + + Ok(ResourceArc::new(TableResource { + connection: conn.0.clone(), + metadata: table.metadata().clone(), + table_info: table.get_table_info().clone(), + })) +} + +#[rustler::nif] +fn table_has_primary_key(table: ResourceArc) -> bool { + table.table_info.has_primary_key() +} + +#[rustler::nif] +fn table_column_names(table: ResourceArc) -> Vec { + table + .columns() + .iter() + .map(|c| c.name().to_string()) + .collect() +} diff --git a/bindings/elixir/native/fluss_nif/src/write_handle.rs b/bindings/elixir/native/fluss_nif/src/write_handle.rs new file mode 100644 index 00000000..72ead9fa --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/write_handle.rs @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 crate::atoms::{self, to_nif_err}; +use crate::RUNTIME; +use fluss::client::WriteResultFuture; +use rustler::{Atom, ResourceArc}; +use std::sync::Mutex; + +pub struct WriteHandleResource(Mutex>); + +impl std::panic::RefUnwindSafe for WriteHandleResource {} + +#[rustler::resource_impl] +impl rustler::Resource for WriteHandleResource {} + +impl WriteHandleResource { + pub fn new(future: WriteResultFuture) -> Self { + Self(Mutex::new(Some(future))) + } +} + +#[rustler::nif(schedule = "DirtyIo")] +fn write_handle_wait(handle: ResourceArc) -> Result { + let future = handle + .0 + .lock() + .map_err(|e| to_nif_err(format!("lock poisoned: {e}")))? + .take() + .ok_or_else(|| to_nif_err("WriteHandle already consumed"))?; + + RUNTIME.block_on(future).map_err(to_nif_err)?; + Ok(atoms::ok()) +} diff --git a/bindings/elixir/test/fluss_test.exs b/bindings/elixir/test/fluss_test.exs new file mode 100644 index 00000000..2304cb06 --- /dev/null +++ b/bindings/elixir/test/fluss_test.exs @@ -0,0 +1,112 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule FlussTest do + use ExUnit.Case + + describe "Config" do + test "creates config with bootstrap servers" do + config = Fluss.Config.new("localhost:9123") + assert Fluss.Config.get_bootstrap_servers(config) == "localhost:9123" + end + + test "default config uses localhost" do + config = Fluss.Config.default() + assert Fluss.Config.get_bootstrap_servers(config) == "127.0.0.1:9123" + end + + test "config chaining" do + config = + Fluss.Config.default() + |> Fluss.Config.set_bootstrap_servers("host1:9123,host2:9123") + + assert Fluss.Config.get_bootstrap_servers(config) == "host1:9123,host2:9123" + end + end + + describe "Schema" do + test "builds a simple log table schema" do + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("ts", :bigint) + |> Fluss.Schema.column("message", :string) + |> Fluss.Schema.build!() + + assert is_reference(schema) + end + + test "builds a schema with all simple types" do + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("a", :boolean) + |> Fluss.Schema.column("b", :tinyint) + |> Fluss.Schema.column("c", :smallint) + |> Fluss.Schema.column("d", :int) + |> Fluss.Schema.column("e", :bigint) + |> Fluss.Schema.column("f", :float) + |> Fluss.Schema.column("g", :double) + |> Fluss.Schema.column("h", :string) + |> Fluss.Schema.column("i", :bytes) + |> Fluss.Schema.column("j", :date) + |> Fluss.Schema.column("k", :time) + |> Fluss.Schema.column("l", :timestamp) + |> Fluss.Schema.column("m", :timestamp_ltz) + |> Fluss.Schema.build!() + + assert is_reference(schema) + end + + test "builds a schema with parameterized types" do + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("amount", {:decimal, 10, 2}) + |> Fluss.Schema.column("code", {:char, 5}) + |> Fluss.Schema.column("data", {:binary, 16}) + |> Fluss.Schema.build!() + + assert is_reference(schema) + end + end + + describe "TableDescriptor" do + test "creates descriptor from schema" do + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + assert is_reference(descriptor) + end + + test "creates descriptor with bucket count" do + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema, bucket_count: 3) + assert is_reference(descriptor) + end + end + + describe "earliest_offset/0" do + test "returns -2" do + assert Fluss.earliest_offset() == -2 + end + end +end diff --git a/bindings/elixir/test/integration/log_table_test.exs b/bindings/elixir/test/integration/log_table_test.exs new file mode 100644 index 00000000..6dec7ed4 --- /dev/null +++ b/bindings/elixir/test/integration/log_table_test.exs @@ -0,0 +1,407 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Integration.LogTableTest do + use ExUnit.Case, async: false + + alias Fluss.Test.Cluster + + @moduletag :integration + + @database "fluss" + + setup_all do + case Cluster.ensure_started() do + {:ok, servers} -> + config = Fluss.Config.new(servers) + + # Wait for cluster to be fully ready (connection + admin working) + {conn, admin} = connect_with_retry(config, 90) + + %{conn: conn, admin: admin, config: config} + + {:error, reason} -> + raise "Failed to start Fluss cluster: #{reason}" + end + end + + describe "append and scan" do + test "append rows and scan with log scanner", %{conn: conn, admin: admin} do + table_name = "ex_test_append_and_scan_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("c1", :int) + |> Fluss.Schema.column("c2", :string) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + writer = Fluss.AppendWriter.new!(table) + + # Append 6 rows + for {c1, c2} <- [{1, "a1"}, {2, "a2"}, {3, "a3"}, {4, "a4"}, {5, "a5"}, {6, "a6"}] do + {:ok, _} = Fluss.AppendWriter.append(writer, [c1, c2]) + end + + :ok = Fluss.AppendWriter.flush(writer) + + # Scan all records + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) + + records = poll_records(scanner, 6) + + assert length(records) == 6 + + sorted = Enum.sort_by(records, fn r -> r[:row][:c1] end) + + for {record, i} <- Enum.with_index(sorted, 1) do + assert record[:row][:c1] == i + assert record[:row][:c2] == "a#{i}" + assert record[:change_type] == :append_only + end + + # Unsubscribe should not error + :ok = Fluss.LogScanner.unsubscribe(scanner, 0) + + cleanup_table(admin, table_name) + end + + test "append with nil values", %{conn: conn, admin: admin} do + table_name = "ex_test_append_nil_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.column("name", :string) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + writer = Fluss.AppendWriter.new!(table) + + {:ok, _} = Fluss.AppendWriter.append(writer, [1, nil]) + {:ok, _} = Fluss.AppendWriter.append(writer, [2, "present"]) + :ok = Fluss.AppendWriter.flush(writer) + + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) + + records = poll_records(scanner, 2) + assert length(records) == 2 + + sorted = Enum.sort_by(records, fn r -> r[:row][:id] end) + assert Enum.at(sorted, 0)[:row][:name] == nil + assert Enum.at(sorted, 1)[:row][:name] == "present" + + cleanup_table(admin, table_name) + end + end + + describe "multiple data types" do + test "int, bigint, float, double, string, boolean", %{conn: conn, admin: admin} do + table_name = "ex_test_data_types_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("a_int", :int) + |> Fluss.Schema.column("b_bigint", :bigint) + |> Fluss.Schema.column("c_float", :float) + |> Fluss.Schema.column("d_double", :double) + |> Fluss.Schema.column("e_string", :string) + |> Fluss.Schema.column("f_bool", :boolean) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + writer = Fluss.AppendWriter.new!(table) + + {:ok, _} = + Fluss.AppendWriter.append(writer, [ + 42, + 1_000_000_000_000, + 3.14, + 2.718281828, + "hello", + true + ]) + + {:ok, _} = Fluss.AppendWriter.append(writer, [-1, -999, 0.0, -1.5, "", false]) + :ok = Fluss.AppendWriter.flush(writer) + + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) + + records = poll_records(scanner, 2) + assert length(records) == 2 + + sorted = Enum.sort_by(records, fn r -> r[:row][:a_int] end) + row1 = Enum.at(sorted, 0)[:row] + row2 = Enum.at(sorted, 1)[:row] + + assert row1[:a_int] == -1 + assert row1[:b_bigint] == -999 + assert row1[:e_string] == "" + assert row1[:f_bool] == false + + assert row2[:a_int] == 42 + assert row2[:b_bigint] == 1_000_000_000_000 + assert row2[:e_string] == "hello" + assert row2[:f_bool] == true + + cleanup_table(admin, table_name) + end + end + + describe "subscribe_buckets" do + test "subscribe to multiple buckets at once", %{conn: conn, admin: admin} do + table_name = "ex_test_subscribe_buckets_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.column("val", :string) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema, bucket_count: 3) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + writer = Fluss.AppendWriter.new!(table) + + for i <- 1..9 do + {:ok, _} = Fluss.AppendWriter.append(writer, [i, "v#{i}"]) + end + + :ok = Fluss.AppendWriter.flush(writer) + + scanner = Fluss.LogScanner.new!(table) + earliest = Fluss.earliest_offset() + + :ok = + Fluss.LogScanner.subscribe_buckets(scanner, [ + {0, earliest}, + {1, earliest}, + {2, earliest} + ]) + + records = poll_records(scanner, 9) + assert length(records) == 9 + + ids = records |> Enum.map(fn r -> r[:row][:id] end) |> Enum.sort() + assert ids == Enum.to_list(1..9) + + cleanup_table(admin, table_name) + end + end + + describe "admin operations" do + test "create and drop database", %{admin: admin} do + db_name = "ex_test_db_#{:rand.uniform(100_000)}" + :ok = Fluss.Admin.create_database(admin, db_name, true) + + {:ok, databases} = Fluss.Admin.list_databases(admin) + assert db_name in databases + + :ok = Fluss.Admin.drop_database(admin, db_name, true) + end + + test "list tables", %{admin: admin} do + table_name = "ex_test_list_tables_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + {:ok, tables} = Fluss.Admin.list_tables(admin, @database) + assert table_name in tables + + cleanup_table(admin, table_name) + end + + test "table metadata", %{conn: conn, admin: admin} do + table_name = "ex_test_table_meta_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.column("name", :string) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + assert Fluss.Table.has_primary_key?(table) == false + assert Fluss.Table.column_names(table) == ["id", "name"] + + cleanup_table(admin, table_name) + end + end + + describe "scan from offset" do + test "subscribe from specific offset skips earlier records", %{conn: conn, admin: admin} do + table_name = "ex_test_scan_offset_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + writer = Fluss.AppendWriter.new!(table) + + for i <- 1..5 do + {:ok, _} = Fluss.AppendWriter.append(writer, [i]) + end + + :ok = Fluss.AppendWriter.flush(writer) + + # Subscribe from offset 3, should skip first 3 records + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, 3) + + records = poll_records(scanner, 2) + assert length(records) == 2 + + ids = records |> Enum.map(fn r -> r[:row][:id] end) |> Enum.sort() + assert ids == [4, 5] + + cleanup_table(admin, table_name) + end + end + + describe "multiple flushes" do + test "append, flush, append more, flush, scan all", %{conn: conn, admin: admin} do + table_name = "ex_test_multi_flush_#{:rand.uniform(100_000)}" + cleanup_table(admin, table_name) + + schema = + Fluss.Schema.build() + |> Fluss.Schema.column("id", :int) + |> Fluss.Schema.column("batch", :string) + |> Fluss.Schema.build!() + + descriptor = Fluss.TableDescriptor.new!(schema) + :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) + + table = Fluss.Table.get!(conn, @database, table_name) + writer = Fluss.AppendWriter.new!(table) + + # First batch + {:ok, _} = Fluss.AppendWriter.append(writer, [1, "first"]) + {:ok, _} = Fluss.AppendWriter.append(writer, [2, "first"]) + :ok = Fluss.AppendWriter.flush(writer) + + # Second batch + {:ok, _} = Fluss.AppendWriter.append(writer, [3, "second"]) + {:ok, _} = Fluss.AppendWriter.append(writer, [4, "second"]) + :ok = Fluss.AppendWriter.flush(writer) + + scanner = Fluss.LogScanner.new!(table) + :ok = Fluss.LogScanner.subscribe(scanner, 0, Fluss.earliest_offset()) + + records = poll_records(scanner, 4) + assert length(records) == 4 + + sorted = Enum.sort_by(records, fn r -> r[:row][:id] end) + assert Enum.at(sorted, 0)[:row][:batch] == "first" + assert Enum.at(sorted, 1)[:row][:batch] == "first" + assert Enum.at(sorted, 2)[:row][:batch] == "second" + assert Enum.at(sorted, 3)[:row][:batch] == "second" + + cleanup_table(admin, table_name) + end + end + + defp poll_records(scanner, expected_count, timeout_ms \\ 10_000) do + deadline = System.monotonic_time(:millisecond) + timeout_ms + do_poll(scanner, expected_count, deadline, []) + end + + defp do_poll(_scanner, expected_count, _deadline, acc) when length(acc) >= expected_count do + acc + end + + defp do_poll(scanner, expected_count, deadline, acc) do + remaining = deadline - System.monotonic_time(:millisecond) + + if remaining <= 0 do + acc + else + :ok = Fluss.LogScanner.poll(scanner, min(5_000, remaining)) + + receive do + {:fluss_records, records} -> + do_poll(scanner, expected_count, deadline, acc ++ records) + + {:fluss_poll_error, _reason} -> + do_poll(scanner, expected_count, deadline, acc) + after + min(6_000, remaining) -> + acc + end + end + end + + defp cleanup_table(admin, table_name) do + Fluss.Admin.drop_table(admin, @database, table_name, true) + end + + defp connect_with_retry(config, timeout_s) do + deadline = System.monotonic_time(:second) + timeout_s + do_connect_retry(config, deadline, nil) + end + + defp do_connect_retry(config, deadline, last_error) do + if System.monotonic_time(:second) >= deadline do + raise "Could not connect to Fluss cluster: #{inspect(last_error)}" + end + + try do + conn = Fluss.Connection.new!(config) + admin = Fluss.Admin.new!(conn) + {:ok, _databases} = Fluss.Admin.list_databases(admin) + {conn, admin} + rescue + e -> + Process.sleep(2_000) + do_connect_retry(config, deadline, e) + end + end +end diff --git a/bindings/elixir/test/support/cluster.ex b/bindings/elixir/test/support/cluster.ex new file mode 100644 index 00000000..59598b4e --- /dev/null +++ b/bindings/elixir/test/support/cluster.ex @@ -0,0 +1,211 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +defmodule Fluss.Test.Cluster do + @moduledoc false + + @fluss_image "apache/fluss" + @fluss_version "0.9.0-incubating" + + @network_name "fluss-elixir-test-network" + @zookeeper_name "zookeeper-elixir-test" + @coordinator_name "coordinator-server-elixir-test" + @tablet_server_name "tablet-server-elixir-test" + + # Same fixed ports used by Python/C++ integration tests. + @coordinator_sasl_port 9123 + @coordinator_plain_port 9223 + @tablet_sasl_port 9124 + @tablet_plain_port 9224 + + def bootstrap_servers, do: "127.0.0.1:#{@coordinator_plain_port}" + + def ensure_started do + case System.get_env("FLUSS_BOOTSTRAP_SERVERS") do + nil -> start_cluster() + servers -> {:ok, servers} + end + end + + def stop do + for name <- [@tablet_server_name, @coordinator_name, @zookeeper_name] do + System.cmd("docker", ["rm", "-f", name], stderr_to_stdout: true) + end + + System.cmd("docker", ["network", "rm", @network_name], stderr_to_stdout: true) + :ok + end + + defp start_cluster do + if port_open?(@coordinator_plain_port) do + IO.puts("Reusing existing Fluss cluster on port #{@coordinator_plain_port}") + {:ok, bootstrap_servers()} + else + do_start_cluster() + end + end + + defp do_start_cluster do + IO.puts("Starting Fluss cluster via Docker...") + + # Remove any leftover containers from previous runs + for name <- [@tablet_server_name, @coordinator_name, @zookeeper_name] do + System.cmd("docker", ["rm", "-f", name], stderr_to_stdout: true) + end + + System.cmd("docker", ["network", "create", @network_name], stderr_to_stdout: true) + + sasl_jaas = + ~s(org.apache.fluss.security.auth.sasl.plain.PlainLoginModule required user_admin="admin-secret" user_alice="alice-secret";) + + coordinator_props = + Enum.join( + [ + "zookeeper.address: #{@zookeeper_name}:2181", + "bind.listeners: INTERNAL://#{@coordinator_name}:0, CLIENT://#{@coordinator_name}:9123, PLAIN_CLIENT://#{@coordinator_name}:9223", + "advertised.listeners: CLIENT://localhost:#{@coordinator_sasl_port}, PLAIN_CLIENT://localhost:#{@coordinator_plain_port}", + "internal.listener.name: INTERNAL", + "security.protocol.map: CLIENT:sasl", + "security.sasl.enabled.mechanisms: plain", + "security.sasl.plain.jaas.config: #{sasl_jaas}", + "netty.server.num-network-threads: 1", + "netty.server.num-worker-threads: 3" + ], + "\n" + ) + + tablet_props = + Enum.join( + [ + "zookeeper.address: #{@zookeeper_name}:2181", + "bind.listeners: INTERNAL://#{@tablet_server_name}:0, CLIENT://#{@tablet_server_name}:9123, PLAIN_CLIENT://#{@tablet_server_name}:9223", + "advertised.listeners: CLIENT://localhost:#{@tablet_sasl_port}, PLAIN_CLIENT://localhost:#{@tablet_plain_port}", + "internal.listener.name: INTERNAL", + "security.protocol.map: CLIENT:sasl", + "security.sasl.enabled.mechanisms: plain", + "security.sasl.plain.jaas.config: #{sasl_jaas}", + "tablet-server.id: 0", + "netty.server.num-network-threads: 1", + "netty.server.num-worker-threads: 3" + ], + "\n" + ) + + docker_run([ + "--name", + @zookeeper_name, + "--network", + @network_name, + "-d", + "zookeeper:3.9.2" + ]) + + docker_run([ + "--name", + @coordinator_name, + "--network", + @network_name, + "-p", + "#{@coordinator_sasl_port}:9123", + "-p", + "#{@coordinator_plain_port}:9223", + "-e", + "FLUSS_PROPERTIES=#{coordinator_props}", + "-d", + "#{@fluss_image}:#{@fluss_version}", + "coordinatorServer" + ]) + + docker_run([ + "--name", + @tablet_server_name, + "--network", + @network_name, + "-p", + "#{@tablet_sasl_port}:9123", + "-p", + "#{@tablet_plain_port}:9223", + "-e", + "FLUSS_PROPERTIES=#{tablet_props}", + "-d", + "#{@fluss_image}:#{@fluss_version}", + "tabletServer" + ]) + + all_ports = [@coordinator_plain_port, @tablet_plain_port] + + if wait_for_ports(all_ports, 90) do + IO.puts("Fluss cluster started successfully.") + {:ok, bootstrap_servers()} + else + {:error, "Cluster ports did not become ready within timeout"} + end + end + + defp docker_run(args) do + {output, code} = System.cmd("docker", ["run" | args], stderr_to_stdout: true) + + if code != 0 do + IO.puts("Docker run warning (code #{code}): #{output}") + end + end + + defp wait_for_ports(ports, timeout_s) do + deadline = System.monotonic_time(:second) + timeout_s + + Enum.all?(ports, fn port -> + remaining = deadline - System.monotonic_time(:second) + remaining > 0 and wait_for_port(port, remaining) + end) + end + + defp wait_for_port(port, timeout_s) do + deadline = System.monotonic_time(:second) + timeout_s + + Stream.repeatedly(fn -> + case :gen_tcp.connect(~c"localhost", port, [], 1000) do + {:ok, socket} -> + :gen_tcp.close(socket) + :ok + + {:error, _} -> + Process.sleep(1000) + :retry + end + end) + |> Enum.reduce_while(false, fn + :ok, _acc -> + {:halt, true} + + :retry, _acc -> + if System.monotonic_time(:second) >= deadline, + do: {:halt, false}, + else: {:cont, false} + end) + end + + defp port_open?(port) do + case :gen_tcp.connect(~c"localhost", port, [], 1000) do + {:ok, socket} -> + :gen_tcp.close(socket) + true + + {:error, _} -> + false + end + end +end diff --git a/bindings/elixir/test/test_helper.exs b/bindings/elixir/test/test_helper.exs new file mode 100644 index 00000000..b15b1f44 --- /dev/null +++ b/bindings/elixir/test/test_helper.exs @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Exclude integration tests by default (they need a Docker cluster). +# Run with: mix test --include integration +ExUnit.start(exclude: [:integration]) + +# Stop Docker containers after all tests finish (matches Python's pytest_unconfigure). +ExUnit.after_suite(fn _ -> + unless System.get_env("FLUSS_BOOTSTRAP_SERVERS") do + Fluss.Test.Cluster.stop() + end +end) From ef62662ae977f1e4e6e36bae0be37086881b2e14 Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Sat, 28 Mar 2026 23:44:36 +0000 Subject: [PATCH 2/9] fix mix.lock --- .licenserc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.licenserc.yaml b/.licenserc.yaml index 27558537..5fcd9982 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -27,6 +27,7 @@ header: - 'NOTICE' - 'DISCLAIMER' - 'bindings/python/fluss/py.typed' + - '**/mix.lock' - 'website/**' - '**/*.md' - '**/DEPENDENCIES.*.tsv' From 7df0976dcca57bf4f306f29378a83038d5ead8c4 Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Sun, 5 Apr 2026 19:07:41 +0100 Subject: [PATCH 3/9] fluss_nif license --- bindings/elixir/native/fluss_nif/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/elixir/native/fluss_nif/Cargo.toml b/bindings/elixir/native/fluss_nif/Cargo.toml index 90858606..95296d1c 100644 --- a/bindings/elixir/native/fluss_nif/Cargo.toml +++ b/bindings/elixir/native/fluss_nif/Cargo.toml @@ -19,6 +19,7 @@ name = "fluss_nif" version = "0.1.0" edition = "2024" +license.workspace = true [lib] name = "fluss_nif" From dc36d279e763012819252995998183f794c62250 Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Fri, 10 Apr 2026 01:22:07 +0100 Subject: [PATCH 4/9] address comments --- Cargo.lock | 60 + bindings/elixir/lib/fluss/native.ex | 5 +- bindings/elixir/lib/fluss/table_descriptor.ex | 15 +- bindings/elixir/native/fluss_nif/Cargo.lock | 3323 +++++++++++++++++ bindings/elixir/native/fluss_nif/Cargo.toml | 11 +- bindings/elixir/native/fluss_nif/src/admin.rs | 18 +- .../native/fluss_nif/src/append_writer.rs | 2 +- .../elixir/native/fluss_nif/src/config.rs | 6 +- .../elixir/native/fluss_nif/src/connection.rs | 2 +- .../native/fluss_nif/src/log_scanner.rs | 49 +- .../native/fluss_nif/src/row_convert.rs | 29 +- .../elixir/native/fluss_nif/src/schema.rs | 40 +- bindings/elixir/native/fluss_nif/src/table.rs | 2 +- .../native/fluss_nif/src/write_handle.rs | 2 +- .../test/integration/log_table_test.exs | 49 +- 15 files changed, 3501 insertions(+), 112 deletions(-) create mode 100644 bindings/elixir/native/fluss_nif/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 388d1a25..ac5b27f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,6 +1136,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "fluss_nif" +version = "0.2.0" +dependencies = [ + "bigdecimal", + "fluss-rs", + "rustler", + "tokio", +] + [[package]] name = "fluss_python" version = "0.2.0" @@ -1745,6 +1755,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1916,6 +1935,16 @@ version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -2741,6 +2770,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" @@ -2870,6 +2905,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustler" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c779e2cbfa2987990205d0d8fc142163739e45a4c6592dc637896c77fec01280" +dependencies = [ + "inventory", + "libloading", + "regex-lite", + "rustler_codegen", +] + +[[package]] +name = "rustler_codegen" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e120f8936c779b6c2e09992a2dfa9a4e8bcd0794c02bb654fde03e03ce8c31" +dependencies = [ + "heck", + "inventory", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rustls" version = "0.23.37" diff --git a/bindings/elixir/lib/fluss/native.ex b/bindings/elixir/lib/fluss/native.ex index a69636ed..c163e1e9 100644 --- a/bindings/elixir/lib/fluss/native.ex +++ b/bindings/elixir/lib/fluss/native.ex @@ -54,10 +54,7 @@ defmodule Fluss.Native do def schema_builder_column(_builder, _name, _data_type), do: :erlang.nif_error(:nif_not_loaded) def schema_builder_primary_key(_builder, _keys), do: :erlang.nif_error(:nif_not_loaded) def schema_builder_build(_builder), do: :erlang.nif_error(:nif_not_loaded) - def table_descriptor_new(_schema), do: :erlang.nif_error(:nif_not_loaded) - def table_descriptor_with_bucket_count(_schema, _count), do: :erlang.nif_error(:nif_not_loaded) - - def table_descriptor_with_properties(_schema, _properties), + def table_descriptor_new(_schema, _bucket_count, _properties), do: :erlang.nif_error(:nif_not_loaded) # Table diff --git a/bindings/elixir/lib/fluss/table_descriptor.ex b/bindings/elixir/lib/fluss/table_descriptor.ex index 612d385e..40bc88d3 100644 --- a/bindings/elixir/lib/fluss/table_descriptor.ex +++ b/bindings/elixir/lib/fluss/table_descriptor.ex @@ -34,19 +34,10 @@ defmodule Fluss.TableDescriptor do @spec new!(Fluss.Schema.t(), keyword()) :: t() def new!(schema, opts \\ []) do - result = - cond do - Keyword.has_key?(opts, :bucket_count) -> - Native.table_descriptor_with_bucket_count(schema, opts[:bucket_count]) + bucket_count = Keyword.get(opts, :bucket_count) + properties = Keyword.get(opts, :properties, []) - Keyword.has_key?(opts, :properties) -> - Native.table_descriptor_with_properties(schema, opts[:properties]) - - true -> - Native.table_descriptor_new(schema) - end - - case result do + case Native.table_descriptor_new(schema, bucket_count, properties) do {:error, reason} -> raise "failed to create table descriptor: #{reason}" ref -> ref end diff --git a/bindings/elixir/native/fluss_nif/Cargo.lock b/bindings/elixir/native/fluss_nif/Cargo.lock new file mode 100644 index 00000000..9b24d8c8 --- /dev/null +++ b/bindings/elixir/native/fluss_nif/Cargo.lock @@ -0,0 +1,3323 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrow" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4754a624e5ae42081f464514be454b39711daae0458906dacde5f4c632f33a8" +dependencies = [ + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ipc", + "arrow-json", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", +] + +[[package]] +name = "arrow-arith" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b3141e0ec5145a22d8694ea8b6d6f69305971c4fa1c1a13ef0195aef2d678b" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "num-traits", +] + +[[package]] +name = "arrow-array" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8955af33b25f3b175ee10af580577280b4bd01f7e823d94c7cdef7cf8c9aef" +dependencies = [ + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "hashbrown 0.16.1", + "num-complex", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-buffer" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c697ddca96183182f35b3a18e50b9110b11e916d7b7799cbfd4d34662f2c56c2" +dependencies = [ + "bytes", + "half", + "num-bigint", + "num-traits", +] + +[[package]] +name = "arrow-cast" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "646bbb821e86fd57189c10b4fcdaa941deaf4181924917b0daa92735baa6ada5" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-ord", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "half", + "lexical-core", + "num-traits", + "ryu", +] + +[[package]] +name = "arrow-csv" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da746f4180004e3ce7b83c977daf6394d768332349d3d913998b10a120b790a" +dependencies = [ + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "regex", +] + +[[package]] +name = "arrow-data" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fdd994a9d28e6365aa78e15da3f3950c0fdcea6b963a12fa1c391afb637b304" +dependencies = [ + "arrow-buffer", + "arrow-schema", + "half", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrow-ipc" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf7df950701ab528bf7c0cf7eeadc0445d03ef5d6ffc151eaae6b38a58feff1" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "flatbuffers", + "lz4_flex", + "zstd", +] + +[[package]] +name = "arrow-json" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ff8357658bedc49792b13e2e862b80df908171275f8e6e075c460da5ee4bf86" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "indexmap", + "itoa", + "lexical-core", + "memchr", + "num-traits", + "ryu", + "serde_core", + "serde_json", + "simdutf8", +] + +[[package]] +name = "arrow-ord" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d8f1870e03d4cbed632959498bcc84083b5a24bded52905ae1695bd29da45b" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", +] + +[[package]] +name = "arrow-row" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18228633bad92bff92a95746bbeb16e5fc318e8382b75619dec26db79e4de4c0" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] + +[[package]] +name = "arrow-schema" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" + +[[package]] +name = "arrow-select" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bf3e3efbd1278f770d67e5dc410257300b161b93baedb3aae836144edcaf4b" +dependencies = [ + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num-traits", +] + +[[package]] +name = "arrow-string" +version = "57.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e968097061b3c0e9fe3079cf2e703e487890700546b5b0647f60fca1b5a8d8" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num-traits", + "regex", + "regex-syntax", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bigdecimal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" +dependencies = [ + "autocfg", + "libm", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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 = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" +dependencies = [ + "bitflags", + "rustc_version", +] + +[[package]] +name = "fluss-rs" +version = "0.2.0" +dependencies = [ + "arrow", + "arrow-schema", + "bigdecimal", + "bitvec", + "byteorder", + "bytes", + "clap", + "crc32c", + "dashmap", + "delegate", + "futures", + "jiff", + "linked-hash-map", + "log", + "opendal", + "ordered-float", + "parking_lot", + "parse-display", + "prost", + "prost-build", + "rand 0.9.2", + "scopeguard", + "serde", + "serde_json", + "snafu", + "strum", + "strum_macros", + "tempfile", + "thiserror 1.0.69", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "fluss_nif" +version = "0.2.0" +dependencies = [ + "bigdecimal", + "fluss-rs", + "rustler", + "tokio", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "js-sys", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "wasm-bindgen", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98c23545df7ecf1b16c303910a69b079e8e251d60f7dd2cc9b4177f2afaf1746" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opendal" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d075ab8a203a6ab4bc1bce0a4b9fe486a72bf8b939037f4b78d95386384bc80a" +dependencies = [ + "anyhow", + "backon", + "base64", + "bytes", + "futures", + "getrandom 0.2.17", + "http", + "http-body", + "jiff", + "log", + "md-5", + "percent-encoding", + "quick-xml", + "reqwest", + "serde", + "serde_json", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "parse-display" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287d8d3ebdce117b8539f59411e4ed9ec226e0a4153c7f55495c6070d68e6f72" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc048687be30d79502dea2f623d052f3a074012c6eac41726b7ab17213616b1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "serde", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustler" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c779e2cbfa2987990205d0d8fc142163739e45a4c6592dc637896c77fec01280" +dependencies = [ + "inventory", + "libloading", + "regex-lite", + "rustler_codegen", +] + +[[package]] +name = "rustler_codegen" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e120f8936c779b6c2e09992a2dfa9a4e8bcd0794c02bb654fde03e03ce8c31" +dependencies = [ + "heck", + "inventory", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "snafu" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sval" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb9318255ebd817902d7e279d8f8e39b35b1b9954decd5eb9ea0e30e5fd2b6a" + +[[package]] +name = "sval_buffer" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12571299185e653fdb0fbfe36cd7f6529d39d4e747a60b15a3f34574b7b97c61" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39526f24e997706c0de7f03fb7371f7f5638b66a504ded508e20ad173d0a3677" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dd3bb26965d682280fcc49400ac2a05036f4ee1e6dbd61bf8402d5a5c3a54" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0cda08f6d5c9948024a6551077557b1fdcc3880ff2f20ae839667d2ec2d87ed" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d49d5e6c1f9fd0e53515819b03a97ca4eb1bff5c8ee097c43391c09ecfb19f" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f876c5a78405375b4e19cbb9554407513b59c93dea12dc6a4af4e1d30899ca" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9ccd3b7f7200239a655e517dd3fd48d960b9111ad24bd6a5e055bef17607c7" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "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 = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "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-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/bindings/elixir/native/fluss_nif/Cargo.toml b/bindings/elixir/native/fluss_nif/Cargo.toml index 95296d1c..dd4d4535 100644 --- a/bindings/elixir/native/fluss_nif/Cargo.toml +++ b/bindings/elixir/native/fluss_nif/Cargo.toml @@ -17,9 +17,10 @@ [package] name = "fluss_nif" -version = "0.1.0" -edition = "2024" +version.workspace = true +edition.workspace = true license.workspace = true +rust-version.workspace = true [lib] name = "fluss_nif" @@ -27,7 +28,7 @@ path = "src/lib.rs" crate-type = ["cdylib"] [dependencies] -fluss = { package = "fluss-rs", path = "../../../../crates/fluss" } -bigdecimal = "0.4" +bigdecimal = { workspace = true } +fluss = { workspace = true } rustler = "0.37" -tokio = { version = "1", features = ["full"] } +tokio = { workspace = true } diff --git a/bindings/elixir/native/fluss_nif/src/admin.rs b/bindings/elixir/native/fluss_nif/src/admin.rs index 984b4976..cfcd2ccd 100644 --- a/bindings/elixir/native/fluss_nif/src/admin.rs +++ b/bindings/elixir/native/fluss_nif/src/admin.rs @@ -15,10 +15,10 @@ // specific language governing permissions and limitations // under the License. +use crate::RUNTIME; use crate::atoms::{self, to_nif_err}; use crate::connection::ConnectionResource; use crate::schema::TableDescriptorResource; -use crate::RUNTIME; use fluss::client::FlussAdmin; use fluss::metadata::TablePath; use rustler::{Atom, ResourceArc}; @@ -32,7 +32,9 @@ impl std::panic::RefUnwindSafe for AdminResource {} impl rustler::Resource for AdminResource {} #[rustler::nif] -fn admin_new(conn: ResourceArc) -> Result, rustler::Error> { +fn admin_new( + conn: ResourceArc, +) -> Result, rustler::Error> { let admin = conn.0.get_admin().map_err(to_nif_err)?; Ok(ResourceArc::new(AdminResource(admin))) } @@ -44,7 +46,11 @@ fn admin_create_database( ignore_if_exists: bool, ) -> Result { RUNTIME - .block_on(admin.0.create_database(&database_name, None, ignore_if_exists)) + .block_on( + admin + .0 + .create_database(&database_name, None, ignore_if_exists), + ) .map_err(to_nif_err)?; Ok(atoms::ok()) } @@ -56,7 +62,11 @@ fn admin_drop_database( ignore_if_not_exists: bool, ) -> Result { RUNTIME - .block_on(admin.0.drop_database(&database_name, ignore_if_not_exists, false)) + .block_on( + admin + .0 + .drop_database(&database_name, ignore_if_not_exists, false), + ) .map_err(to_nif_err)?; Ok(atoms::ok()) } diff --git a/bindings/elixir/native/fluss_nif/src/append_writer.rs b/bindings/elixir/native/fluss_nif/src/append_writer.rs index f60967ff..de9e90a8 100644 --- a/bindings/elixir/native/fluss_nif/src/append_writer.rs +++ b/bindings/elixir/native/fluss_nif/src/append_writer.rs @@ -15,11 +15,11 @@ // specific language governing permissions and limitations // under the License. +use crate::RUNTIME; use crate::atoms::{self, to_nif_err}; use crate::row_convert; use crate::table::TableResource; use crate::write_handle::WriteHandleResource; -use crate::RUNTIME; use fluss::client::AppendWriter; use fluss::metadata::Column; use rustler::{Atom, Env, ResourceArc, Term}; diff --git a/bindings/elixir/native/fluss_nif/src/config.rs b/bindings/elixir/native/fluss_nif/src/config.rs index fde21ed4..9afabceb 100644 --- a/bindings/elixir/native/fluss_nif/src/config.rs +++ b/bindings/elixir/native/fluss_nif/src/config.rs @@ -27,8 +27,10 @@ impl rustler::Resource for ConfigResource {} #[rustler::nif] fn config_new(bootstrap_servers: String) -> ResourceArc { - let mut config = Config::default(); - config.bootstrap_servers = bootstrap_servers; + let config = Config { + bootstrap_servers, + ..Config::default() + }; ResourceArc::new(ConfigResource(config)) } diff --git a/bindings/elixir/native/fluss_nif/src/connection.rs b/bindings/elixir/native/fluss_nif/src/connection.rs index 0c2db643..1ea8800d 100644 --- a/bindings/elixir/native/fluss_nif/src/connection.rs +++ b/bindings/elixir/native/fluss_nif/src/connection.rs @@ -15,9 +15,9 @@ // specific language governing permissions and limitations // under the License. +use crate::RUNTIME; use crate::atoms::to_nif_err; use crate::config::ConfigResource; -use crate::RUNTIME; use fluss::client::FlussConnection; use rustler::ResourceArc; use std::sync::Arc; diff --git a/bindings/elixir/native/fluss_nif/src/log_scanner.rs b/bindings/elixir/native/fluss_nif/src/log_scanner.rs index c6021d46..6289b263 100644 --- a/bindings/elixir/native/fluss_nif/src/log_scanner.rs +++ b/bindings/elixir/native/fluss_nif/src/log_scanner.rs @@ -15,10 +15,10 @@ // specific language governing permissions and limitations // under the License. +use crate::RUNTIME; use crate::atoms::{self, to_nif_err}; use crate::row_convert; use crate::table::TableResource; -use crate::RUNTIME; use fluss::client::LogScanner; use fluss::metadata::Column; use fluss::record::ChangeType; @@ -44,10 +44,7 @@ fn log_scanner_new( ) -> Result, rustler::Error> { let _guard = RUNTIME.enter(); let (scanner, columns) = table.with_table(|t| { - let scanner = t - .new_scan() - .create_log_scanner() - .map_err(to_nif_err)?; + let scanner = t.new_scan().create_log_scanner().map_err(to_nif_err)?; Ok((scanner, t.get_table_info().schema.columns().to_vec())) })?; Ok(ResourceArc::new(LogScannerResource { scanner, columns })) @@ -89,16 +86,15 @@ fn log_scanner_unsubscribe( } #[rustler::nif] -fn log_scanner_poll( - env: Env, - scanner: ResourceArc, - timeout_ms: u64, -) -> Atom { +fn log_scanner_poll(env: Env, scanner: ResourceArc, timeout_ms: u64) -> Atom { let pid = env.pid(); let scanner = scanner.clone(); - std::thread::spawn(move || { - let result = RUNTIME.block_on(scanner.scanner.poll(Duration::from_millis(timeout_ms))); + RUNTIME.spawn(async move { + let result = scanner + .scanner + .poll(Duration::from_millis(timeout_ms)) + .await; send_poll_result(&pid, result, &scanner.columns); }); @@ -115,8 +111,10 @@ fn send_poll_result( match result { Ok(scan_records) => { let _ = msg_env.send_and_clear(pid, |env| { - let records = encode_scan_records(env, scan_records, columns); - (atoms::fluss_records(), records).encode(env) + match encode_scan_records(env, scan_records, columns) { + Ok(records) => (atoms::fluss_records(), records).encode(env), + Err(e) => (atoms::fluss_poll_error(), e).encode(env), + } }); } Err(e) => { @@ -131,15 +129,13 @@ fn encode_scan_records<'a>( env: Env<'a>, scan_records: fluss::record::ScanRecords, columns: &[Column], -) -> rustler::Term<'a> { +) -> Result, String> { let column_atoms = row_convert::intern_column_atoms(env, columns); let mut result = Vec::new(); for record in scan_records { - let row_map = match row_convert::row_to_term(env, record.row(), columns, &column_atoms) { - Ok(m) => m, - Err(_) => continue, - }; + let row_map = row_convert::row_to_term(env, record.row(), columns, &column_atoms) + .map_err(|e| format!("failed to convert row at offset {}: {e}", record.offset()))?; let change_type_atom = match record.change_type() { ChangeType::AppendOnly => atoms::append_only().encode(env), ChangeType::Insert => atoms::insert().encode(env), @@ -148,20 +144,23 @@ fn encode_scan_records<'a>( ChangeType::Delete => atoms::delete().encode(env), }; - if let Ok(record_map) = rustler::Term::map_from_pairs( + let record_map = rustler::Term::map_from_pairs( env, &[ (atoms::offset().encode(env), record.offset().encode(env)), - (atoms::timestamp().encode(env), record.timestamp().encode(env)), + ( + atoms::timestamp().encode(env), + record.timestamp().encode(env), + ), (atoms::change_type().encode(env), change_type_atom), (atoms::row().encode(env), row_map), ], - ) { - result.push(record_map); - } + ) + .map_err(|_| "failed to create record map".to_string())?; + result.push(record_map); } - result.encode(env) + Ok(result.encode(env)) } #[rustler::nif] diff --git a/bindings/elixir/native/fluss_nif/src/row_convert.rs b/bindings/elixir/native/fluss_nif/src/row_convert.rs index 3fcdcad2..d58c2024 100644 --- a/bindings/elixir/native/fluss_nif/src/row_convert.rs +++ b/bindings/elixir/native/fluss_nif/src/row_convert.rs @@ -24,6 +24,12 @@ use rustler::{Encoder, Env, Term}; use crate::atoms; +/// Convert column names to BEAM atoms for use as map keys. +/// +/// Note: BEAM atoms are never garbage-collected. This is safe because column +/// names come from server-defined table schemas (bounded set), not arbitrary +/// user input. The BEAM deduplicates atoms, so repeated calls with the same +/// column names do not grow the atom table. pub fn intern_column_atoms<'a>(env: Env<'a>, columns: &[Column]) -> Vec { columns .iter() @@ -162,12 +168,11 @@ pub fn term_to_row<'a>( let mut row = GenericRow::new(columns.len()); for (i, (term, col)) in list.iter().zip(columns.iter()).enumerate() { - if term.is_atom() { - if let Ok(atom) = term.decode::() { - if atom == atoms::nil() { - continue; // leave as null - } - } + if term.is_atom() + && let Ok(atom) = term.decode::() + && atom == atoms::nil() + { + continue; // leave as null } set_field_from_term(env, &mut row, i, *term, col.data_type())?; } @@ -191,9 +196,7 @@ fn set_field_from_term<'a>( row.set_field(pos, v); } DataType::SmallInt(_) => { - let v: i16 = term - .decode() - .map_err(|_| "expected integer for smallint")?; + let v: i16 = term.decode().map_err(|_| "expected integer for smallint")?; row.set_field(pos, v); } DataType::Int(_) => { @@ -205,11 +208,15 @@ fn set_field_from_term<'a>( row.set_field(pos, v); } DataType::Date(_) => { - let v: i32 = term.decode().map_err(|_| "expected integer (days since epoch)")?; + let v: i32 = term + .decode() + .map_err(|_| "expected integer (days since epoch)")?; row.set_field(pos, Date::new(v)); } DataType::Time(_) => { - let v: i32 = term.decode().map_err(|_| "expected integer (millis since midnight)")?; + let v: i32 = term + .decode() + .map_err(|_| "expected integer (millis since midnight)")?; row.set_field(pos, Time::new(v)); } DataType::Timestamp(_) => { diff --git a/bindings/elixir/native/fluss_nif/src/schema.rs b/bindings/elixir/native/fluss_nif/src/schema.rs index 11f5287c..baf3f6cc 100644 --- a/bindings/elixir/native/fluss_nif/src/schema.rs +++ b/bindings/elixir/native/fluss_nif/src/schema.rs @@ -94,7 +94,9 @@ fn schema_builder_column( data_type: DataType, ) -> Result, rustler::Error> { let mut guard = builder.0.lock().unwrap(); - let b = guard.take().ok_or_else(|| to_nif_err("schema builder already consumed"))?; + let b = guard + .take() + .ok_or_else(|| to_nif_err("schema builder already consumed"))?; *guard = Some(b.column(&name, to_fluss_type(&data_type))); drop(guard); Ok(builder) @@ -106,7 +108,9 @@ fn schema_builder_primary_key( keys: Vec, ) -> Result, rustler::Error> { let mut guard = builder.0.lock().unwrap(); - let b = guard.take().ok_or_else(|| to_nif_err("schema builder already consumed"))?; + let b = guard + .take() + .ok_or_else(|| to_nif_err("schema builder already consumed"))?; *guard = Some(b.primary_key(keys)); drop(guard); Ok(builder) @@ -117,7 +121,9 @@ fn schema_builder_build( builder: ResourceArc, ) -> Result, rustler::Error> { let mut guard = builder.0.lock().unwrap(); - let b = guard.take().ok_or_else(|| to_nif_err("schema builder already consumed"))?; + let b = guard + .take() + .ok_or_else(|| to_nif_err("schema builder already consumed"))?; let schema = b.build().map_err(to_nif_err)?; Ok(ResourceArc::new(SchemaResource(schema))) } @@ -125,33 +131,13 @@ fn schema_builder_build( #[rustler::nif] fn table_descriptor_new( schema: ResourceArc, -) -> Result, rustler::Error> { - let descriptor = TableDescriptor::builder() - .schema(schema.0.clone()) - .build() - .map_err(to_nif_err)?; - Ok(ResourceArc::new(TableDescriptorResource(descriptor))) -} - -#[rustler::nif] -fn table_descriptor_with_bucket_count( - schema: ResourceArc, - bucket_count: i32, -) -> Result, rustler::Error> { - let descriptor = TableDescriptor::builder() - .schema(schema.0.clone()) - .distributed_by(Some(bucket_count), vec![]) - .build() - .map_err(to_nif_err)?; - Ok(ResourceArc::new(TableDescriptorResource(descriptor))) -} - -#[rustler::nif] -fn table_descriptor_with_properties( - schema: ResourceArc, + bucket_count: Option, properties: Vec<(String, String)>, ) -> Result, rustler::Error> { let mut builder = TableDescriptor::builder().schema(schema.0.clone()); + if let Some(count) = bucket_count { + builder = builder.distributed_by(Some(count), vec![]); + } for (key, value) in properties { builder = builder.property(&key, &value); } diff --git a/bindings/elixir/native/fluss_nif/src/table.rs b/bindings/elixir/native/fluss_nif/src/table.rs index 621f2f96..94fafc57 100644 --- a/bindings/elixir/native/fluss_nif/src/table.rs +++ b/bindings/elixir/native/fluss_nif/src/table.rs @@ -15,9 +15,9 @@ // specific language governing permissions and limitations // under the License. +use crate::RUNTIME; use crate::atoms::to_nif_err; use crate::connection::ConnectionResource; -use crate::RUNTIME; use fluss::client::{FlussConnection, FlussTable, Metadata}; use fluss::metadata::{Column, TableInfo, TablePath}; use rustler::ResourceArc; diff --git a/bindings/elixir/native/fluss_nif/src/write_handle.rs b/bindings/elixir/native/fluss_nif/src/write_handle.rs index 72ead9fa..31684569 100644 --- a/bindings/elixir/native/fluss_nif/src/write_handle.rs +++ b/bindings/elixir/native/fluss_nif/src/write_handle.rs @@ -15,8 +15,8 @@ // specific language governing permissions and limitations // under the License. -use crate::atoms::{self, to_nif_err}; use crate::RUNTIME; +use crate::atoms::{self, to_nif_err}; use fluss::client::WriteResultFuture; use rustler::{Atom, ResourceArc}; use std::sync::Mutex; diff --git a/bindings/elixir/test/integration/log_table_test.exs b/bindings/elixir/test/integration/log_table_test.exs index 6dec7ed4..6c52f03e 100644 --- a/bindings/elixir/test/integration/log_table_test.exs +++ b/bindings/elixir/test/integration/log_table_test.exs @@ -120,18 +120,23 @@ defmodule Fluss.Integration.LogTableTest do end describe "multiple data types" do - test "int, bigint, float, double, string, boolean", %{conn: conn, admin: admin} do + test "tinyint, smallint, int, bigint, float, double, string, boolean", %{ + conn: conn, + admin: admin + } do table_name = "ex_test_data_types_#{:rand.uniform(100_000)}" cleanup_table(admin, table_name) schema = Fluss.Schema.build() - |> Fluss.Schema.column("a_int", :int) - |> Fluss.Schema.column("b_bigint", :bigint) - |> Fluss.Schema.column("c_float", :float) - |> Fluss.Schema.column("d_double", :double) - |> Fluss.Schema.column("e_string", :string) - |> Fluss.Schema.column("f_bool", :boolean) + |> Fluss.Schema.column("a_tinyint", :tinyint) + |> Fluss.Schema.column("b_smallint", :smallint) + |> Fluss.Schema.column("c_int", :int) + |> Fluss.Schema.column("d_bigint", :bigint) + |> Fluss.Schema.column("e_float", :float) + |> Fluss.Schema.column("f_double", :double) + |> Fluss.Schema.column("g_string", :string) + |> Fluss.Schema.column("h_bool", :boolean) |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) @@ -142,6 +147,8 @@ defmodule Fluss.Integration.LogTableTest do {:ok, _} = Fluss.AppendWriter.append(writer, [ + 127, + 32_000, 42, 1_000_000_000_000, 3.14, @@ -150,7 +157,9 @@ defmodule Fluss.Integration.LogTableTest do true ]) - {:ok, _} = Fluss.AppendWriter.append(writer, [-1, -999, 0.0, -1.5, "", false]) + {:ok, _} = + Fluss.AppendWriter.append(writer, [-128, -32_000, -1, -999, 0.0, -1.5, "", false]) + :ok = Fluss.AppendWriter.flush(writer) scanner = Fluss.LogScanner.new!(table) @@ -159,19 +168,23 @@ defmodule Fluss.Integration.LogTableTest do records = poll_records(scanner, 2) assert length(records) == 2 - sorted = Enum.sort_by(records, fn r -> r[:row][:a_int] end) + sorted = Enum.sort_by(records, fn r -> r[:row][:c_int] end) row1 = Enum.at(sorted, 0)[:row] row2 = Enum.at(sorted, 1)[:row] - assert row1[:a_int] == -1 - assert row1[:b_bigint] == -999 - assert row1[:e_string] == "" - assert row1[:f_bool] == false - - assert row2[:a_int] == 42 - assert row2[:b_bigint] == 1_000_000_000_000 - assert row2[:e_string] == "hello" - assert row2[:f_bool] == true + assert row1[:a_tinyint] == -128 + assert row1[:b_smallint] == -32_000 + assert row1[:c_int] == -1 + assert row1[:d_bigint] == -999 + assert row1[:g_string] == "" + assert row1[:h_bool] == false + + assert row2[:a_tinyint] == 127 + assert row2[:b_smallint] == 32_000 + assert row2[:c_int] == 42 + assert row2[:d_bigint] == 1_000_000_000_000 + assert row2[:g_string] == "hello" + assert row2[:h_bool] == true cleanup_table(admin, table_name) end From 15ff73be068503a8826f4dbda87f92ba93654741 Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Fri, 10 Apr 2026 01:28:05 +0100 Subject: [PATCH 5/9] remove unnecessary lock --- bindings/elixir/native/fluss_nif/Cargo.lock | 3323 ------------------- 1 file changed, 3323 deletions(-) delete mode 100644 bindings/elixir/native/fluss_nif/Cargo.lock diff --git a/bindings/elixir/native/fluss_nif/Cargo.lock b/bindings/elixir/native/fluss_nif/Cargo.lock deleted file mode 100644 index 9b24d8c8..00000000 --- a/bindings/elixir/native/fluss_nif/Cargo.lock +++ /dev/null @@ -1,3323 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "const-random", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "arrow" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4754a624e5ae42081f464514be454b39711daae0458906dacde5f4c632f33a8" -dependencies = [ - "arrow-arith", - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-csv", - "arrow-data", - "arrow-ipc", - "arrow-json", - "arrow-ord", - "arrow-row", - "arrow-schema", - "arrow-select", - "arrow-string", -] - -[[package]] -name = "arrow-arith" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b3141e0ec5145a22d8694ea8b6d6f69305971c4fa1c1a13ef0195aef2d678b" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "chrono", - "num-traits", -] - -[[package]] -name = "arrow-array" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8955af33b25f3b175ee10af580577280b4bd01f7e823d94c7cdef7cf8c9aef" -dependencies = [ - "ahash", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "chrono", - "half", - "hashbrown 0.16.1", - "num-complex", - "num-integer", - "num-traits", -] - -[[package]] -name = "arrow-buffer" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c697ddca96183182f35b3a18e50b9110b11e916d7b7799cbfd4d34662f2c56c2" -dependencies = [ - "bytes", - "half", - "num-bigint", - "num-traits", -] - -[[package]] -name = "arrow-cast" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "646bbb821e86fd57189c10b4fcdaa941deaf4181924917b0daa92735baa6ada5" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-ord", - "arrow-schema", - "arrow-select", - "atoi", - "base64", - "chrono", - "half", - "lexical-core", - "num-traits", - "ryu", -] - -[[package]] -name = "arrow-csv" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da746f4180004e3ce7b83c977daf6394d768332349d3d913998b10a120b790a" -dependencies = [ - "arrow-array", - "arrow-cast", - "arrow-schema", - "chrono", - "csv", - "csv-core", - "regex", -] - -[[package]] -name = "arrow-data" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fdd994a9d28e6365aa78e15da3f3950c0fdcea6b963a12fa1c391afb637b304" -dependencies = [ - "arrow-buffer", - "arrow-schema", - "half", - "num-integer", - "num-traits", -] - -[[package]] -name = "arrow-ipc" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf7df950701ab528bf7c0cf7eeadc0445d03ef5d6ffc151eaae6b38a58feff1" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", - "flatbuffers", - "lz4_flex", - "zstd", -] - -[[package]] -name = "arrow-json" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ff8357658bedc49792b13e2e862b80df908171275f8e6e075c460da5ee4bf86" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-cast", - "arrow-data", - "arrow-schema", - "chrono", - "half", - "indexmap", - "itoa", - "lexical-core", - "memchr", - "num-traits", - "ryu", - "serde_core", - "serde_json", - "simdutf8", -] - -[[package]] -name = "arrow-ord" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d8f1870e03d4cbed632959498bcc84083b5a24bded52905ae1695bd29da45b" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", -] - -[[package]] -name = "arrow-row" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18228633bad92bff92a95746bbeb16e5fc318e8382b75619dec26db79e4de4c0" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "half", -] - -[[package]] -name = "arrow-schema" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" - -[[package]] -name = "arrow-select" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bf3e3efbd1278f770d67e5dc410257300b161b93baedb3aae836144edcaf4b" -dependencies = [ - "ahash", - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "num-traits", -] - -[[package]] -name = "arrow-string" -version = "57.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e968097061b3c0e9fe3079cf2e703e487890700546b5b0647f60fca1b5a8d8" -dependencies = [ - "arrow-array", - "arrow-buffer", - "arrow-data", - "arrow-schema", - "arrow-select", - "memchr", - "num-traits", - "regex", - "regex-syntax", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", - "gloo-timers", - "tokio", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bigdecimal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" -dependencies = [ - "autocfg", - "libm", - "num-bigint", - "num-integer", - "num-traits", - "serde", -] - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "num-traits", - "windows-link", -] - -[[package]] -name = "clap" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crc32c" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "csv" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde_core", -] - -[[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" -dependencies = [ - "memchr", -] - -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "delegate" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[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 = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flatbuffers" -version = "25.12.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" -dependencies = [ - "bitflags", - "rustc_version", -] - -[[package]] -name = "fluss-rs" -version = "0.2.0" -dependencies = [ - "arrow", - "arrow-schema", - "bigdecimal", - "bitvec", - "byteorder", - "bytes", - "clap", - "crc32c", - "dashmap", - "delegate", - "futures", - "jiff", - "linked-hash-map", - "log", - "opendal", - "ordered-float", - "parking_lot", - "parse-display", - "prost", - "prost-build", - "rand 0.9.2", - "scopeguard", - "serde", - "serde_json", - "snafu", - "strum", - "strum_macros", - "tempfile", - "thiserror 1.0.69", - "tokio", - "url", - "uuid", -] - -[[package]] -name = "fluss_nif" -version = "0.2.0" -dependencies = [ - "bigdecimal", - "fluss-rs", - "rustler", - "tokio", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "num-traits", - "zerocopy", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hashbrown" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.0", - "serde", - "serde_core", -] - -[[package]] -name = "inventory" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jiff" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" -dependencies = [ - "jiff-static", - "jiff-tzdb-platform", - "js-sys", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "wasm-bindgen", - "windows-sys 0.61.2", -] - -[[package]] -name = "jiff-static" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "jiff-tzdb" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "lexical-core" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" -dependencies = [ - "lexical-parse-integer", - "lexical-util", -] - -[[package]] -name = "lexical-parse-integer" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" -dependencies = [ - "lexical-util", -] - -[[package]] -name = "lexical-util" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" - -[[package]] -name = "lexical-write-float" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" -dependencies = [ - "lexical-util", - "lexical-write-integer", -] - -[[package]] -name = "lexical-write-integer" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" -dependencies = [ - "lexical-util", -] - -[[package]] -name = "libc" -version = "0.2.184" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" - -[[package]] -name = "libloading" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[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.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "value-bag", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "lz4_flex" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c23545df7ecf1b16c303910a69b079e8e251d60f7dd2cc9b4177f2afaf1746" -dependencies = [ - "twox-hash", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "opendal" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d075ab8a203a6ab4bc1bce0a4b9fe486a72bf8b939037f4b78d95386384bc80a" -dependencies = [ - "anyhow", - "backon", - "base64", - "bytes", - "futures", - "getrandom 0.2.17", - "http", - "http-body", - "jiff", - "log", - "md-5", - "percent-encoding", - "quick-xml", - "reqwest", - "serde", - "serde_json", - "tokio", - "url", - "uuid", -] - -[[package]] -name = "ordered-float" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" -dependencies = [ - "num-traits", - "rand 0.8.5", - "serde", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "parse-display" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287d8d3ebdce117b8539f59411e4ed9ec226e0a4153c7f55495c6070d68e6f72" -dependencies = [ - "parse-display-derive", - "regex", - "regex-syntax", -] - -[[package]] -name = "parse-display-derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc048687be30d79502dea2f623d052f3a074012c6eac41726b7ab17213616b1" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "regex-syntax", - "structmeta", - "syn", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "portable-atomic-util" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" -dependencies = [ - "prost", -] - -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core 0.6.4", - "serde", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "serde", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustler" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c779e2cbfa2987990205d0d8fc142163739e45a4c6592dc637896c77fec01280" -dependencies = [ - "inventory", - "libloading", - "regex-lite", - "rustler_codegen", -] - -[[package]] -name = "rustler_codegen" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e120f8936c779b6c2e09992a2dfa9a4e8bcd0794c02bb654fde03e03ce8c31" -dependencies = [ - "heck", - "inventory", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_fmt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "structmeta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" -dependencies = [ - "proc-macro2", - "quote", - "structmeta-derive", - "syn", -] - -[[package]] -name = "structmeta-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "sval" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb9318255ebd817902d7e279d8f8e39b35b1b9954decd5eb9ea0e30e5fd2b6a" - -[[package]] -name = "sval_buffer" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12571299185e653fdb0fbfe36cd7f6529d39d4e747a60b15a3f34574b7b97c61" -dependencies = [ - "sval", - "sval_ref", -] - -[[package]] -name = "sval_dynamic" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39526f24e997706c0de7f03fb7371f7f5638b66a504ded508e20ad173d0a3677" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933dd3bb26965d682280fcc49400ac2a05036f4ee1e6dbd61bf8402d5a5c3a54" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0cda08f6d5c9948024a6551077557b1fdcc3880ff2f20ae839667d2ec2d87ed" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_nested" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d49d5e6c1f9fd0e53515819b03a97ca4eb1bff5c8ee097c43391c09ecfb19f" -dependencies = [ - "sval", - "sval_buffer", - "sval_ref", -] - -[[package]] -name = "sval_ref" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f876c5a78405375b4e19cbb9554407513b59c93dea12dc6a4af4e1d30899ca" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_serde" -version = "2.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9ccd3b7f7200239a655e517dd3fd48d960b9111ad24bd6a5e055bef17607c7" -dependencies = [ - "serde_core", - "sval", - "sval_nested", -] - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" -dependencies = [ - "erased-serde", - "serde_core", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "web-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" -dependencies = [ - "js-sys", - "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 = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "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-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] From 0d03793a12714e795b78bd62db43477bdb3ab28b Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Sun, 12 Apr 2026 23:57:15 +0100 Subject: [PATCH 6/9] Address comments --- bindings/elixir/native/fluss_nif/src/row_convert.rs | 8 ++++++-- bindings/elixir/test/integration/log_table_test.exs | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bindings/elixir/native/fluss_nif/src/row_convert.rs b/bindings/elixir/native/fluss_nif/src/row_convert.rs index d58c2024..c72395e9 100644 --- a/bindings/elixir/native/fluss_nif/src/row_convert.rs +++ b/bindings/elixir/native/fluss_nif/src/row_convert.rs @@ -192,11 +192,15 @@ fn set_field_from_term<'a>( row.set_field(pos, v); } DataType::TinyInt(_) => { - let v: i8 = term.decode().map_err(|_| "expected integer for tinyint")?; + let v: i8 = term + .decode() + .map_err(|_| "expected integer in range -128..127 for tinyint")?; row.set_field(pos, v); } DataType::SmallInt(_) => { - let v: i16 = term.decode().map_err(|_| "expected integer for smallint")?; + let v: i16 = term + .decode() + .map_err(|_| "expected integer in range -32768..32767 for smallint")?; row.set_field(pos, v); } DataType::Int(_) => { diff --git a/bindings/elixir/test/integration/log_table_test.exs b/bindings/elixir/test/integration/log_table_test.exs index 6c52f03e..3f538649 100644 --- a/bindings/elixir/test/integration/log_table_test.exs +++ b/bindings/elixir/test/integration/log_table_test.exs @@ -383,7 +383,8 @@ defmodule Fluss.Integration.LogTableTest do {:fluss_records, records} -> do_poll(scanner, expected_count, deadline, acc ++ records) - {:fluss_poll_error, _reason} -> + {:fluss_poll_error, reason} -> + IO.warn("poll error during test: #{inspect(reason)}") do_poll(scanner, expected_count, deadline, acc) after min(6_000, remaining) -> From f821128f83ee7f8236f10d9e1e52ba980cbe1b0b Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Mon, 13 Apr 2026 19:32:06 +0100 Subject: [PATCH 7/9] address review --- bindings/elixir/lib/fluss.ex | 3 +- bindings/elixir/lib/fluss/admin.ex | 54 +++++---- bindings/elixir/lib/fluss/append_writer.ex | 10 +- bindings/elixir/lib/fluss/config.ex | 37 +++--- bindings/elixir/lib/fluss/connection.ex | 19 +-- bindings/elixir/lib/fluss/log_scanner.ex | 25 ++-- bindings/elixir/lib/fluss/native.ex | 21 ++-- bindings/elixir/lib/fluss/schema.ex | 43 +++---- bindings/elixir/lib/fluss/table.ex | 11 +- bindings/elixir/lib/fluss/table_descriptor.ex | 2 +- bindings/elixir/lib/fluss/write_handle.ex | 6 +- bindings/elixir/native/fluss_nif/src/admin.rs | 113 +++++++++--------- .../native/fluss_nif/src/append_writer.rs | 14 +-- .../elixir/native/fluss_nif/src/async_nif.rs | 92 ++++++++++++++ bindings/elixir/native/fluss_nif/src/atoms.rs | 1 + .../elixir/native/fluss_nif/src/config.rs | 77 ++++-------- .../elixir/native/fluss_nif/src/connection.rs | 29 +++-- bindings/elixir/native/fluss_nif/src/lib.rs | 1 + .../native/fluss_nif/src/log_scanner.rs | 62 +++++----- .../elixir/native/fluss_nif/src/schema.rs | 88 ++++---------- bindings/elixir/native/fluss_nif/src/table.rs | 36 +++--- .../native/fluss_nif/src/write_handle.rs | 31 +++-- bindings/elixir/test/fluss_test.exs | 108 +++++------------ .../test/integration/log_table_test.exs | 24 ++-- 24 files changed, 453 insertions(+), 454 deletions(-) create mode 100644 bindings/elixir/native/fluss_nif/src/async_nif.rs diff --git a/bindings/elixir/lib/fluss.ex b/bindings/elixir/lib/fluss.ex index c6ceea36..25aa6491 100644 --- a/bindings/elixir/lib/fluss.ex +++ b/bindings/elixir/lib/fluss.ex @@ -26,10 +26,9 @@ defmodule Fluss do admin = Fluss.Admin.new!(conn) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("ts", :bigint) |> Fluss.Schema.column("message", :string) - |> Fluss.Schema.build!() :ok = Fluss.Admin.create_table(admin, "my_db", "events", Fluss.TableDescriptor.new!(schema)) diff --git a/bindings/elixir/lib/fluss/admin.ex b/bindings/elixir/lib/fluss/admin.ex index dfc1720b..7817b9fb 100644 --- a/bindings/elixir/lib/fluss/admin.ex +++ b/bindings/elixir/lib/fluss/admin.ex @@ -41,58 +41,68 @@ defmodule Fluss.Admin do @spec new!(Fluss.Connection.t()) :: t() def new!(conn) do - case Native.admin_new(conn) do + case new(conn) do + {:ok, admin} -> admin {:error, reason} -> raise "failed to create admin: #{reason}" - admin -> admin end end @spec create_database(t(), String.t(), boolean()) :: :ok | {:error, String.t()} - def create_database(admin, name, ignore_if_exists \\ true), - do: Native.admin_create_database(admin, name, ignore_if_exists) + def create_database(admin, name, ignore_if_exists \\ true) do + admin + |> Native.admin_create_database(name, ignore_if_exists) + |> Native.await_nif() + end @spec drop_database(t(), String.t(), boolean()) :: :ok | {:error, String.t()} - def drop_database(admin, name, ignore_if_not_exists \\ true), - do: Native.admin_drop_database(admin, name, ignore_if_not_exists) + def drop_database(admin, name, ignore_if_not_exists \\ true) do + admin + |> Native.admin_drop_database(name, ignore_if_not_exists) + |> Native.await_nif() + end @spec list_databases(t()) :: {:ok, [String.t()]} | {:error, String.t()} def list_databases(admin) do - case Native.admin_list_databases(admin) do - {:error, _} = err -> err - dbs -> {:ok, dbs} - end + admin + |> Native.admin_list_databases() + |> Native.await_nif() end @spec list_databases!(t()) :: [String.t()] def list_databases!(admin) do - case Native.admin_list_databases(admin) do + case list_databases(admin) do + {:ok, dbs} -> dbs {:error, reason} -> raise "failed to list databases: #{reason}" - dbs -> dbs end end @spec create_table(t(), String.t(), String.t(), Fluss.TableDescriptor.t(), boolean()) :: :ok | {:error, String.t()} - def create_table(admin, database, table, descriptor, ignore_if_exists \\ true), - do: Native.admin_create_table(admin, database, table, descriptor, ignore_if_exists) + def create_table(admin, database, table, descriptor, ignore_if_exists \\ true) do + admin + |> Native.admin_create_table(database, table, descriptor, ignore_if_exists) + |> Native.await_nif() + end @spec drop_table(t(), String.t(), String.t(), boolean()) :: :ok | {:error, String.t()} - def drop_table(admin, database, table, ignore_if_not_exists \\ true), - do: Native.admin_drop_table(admin, database, table, ignore_if_not_exists) + def drop_table(admin, database, table, ignore_if_not_exists \\ true) do + admin + |> Native.admin_drop_table(database, table, ignore_if_not_exists) + |> Native.await_nif() + end @spec list_tables(t(), String.t()) :: {:ok, [String.t()]} | {:error, String.t()} def list_tables(admin, database) do - case Native.admin_list_tables(admin, database) do - {:error, _} = err -> err - tables -> {:ok, tables} - end + admin + |> Native.admin_list_tables(database) + |> Native.await_nif() end @spec list_tables!(t(), String.t()) :: [String.t()] def list_tables!(admin, database) do - case Native.admin_list_tables(admin, database) do + case list_tables(admin, database) do + {:ok, tables} -> tables {:error, reason} -> raise "failed to list tables: #{reason}" - tables -> tables end end end diff --git a/bindings/elixir/lib/fluss/append_writer.ex b/bindings/elixir/lib/fluss/append_writer.ex index 5e62b3f3..767802ac 100644 --- a/bindings/elixir/lib/fluss/append_writer.ex +++ b/bindings/elixir/lib/fluss/append_writer.ex @@ -52,9 +52,9 @@ defmodule Fluss.AppendWriter do @spec new!(Fluss.Table.t()) :: t() def new!(table) do - case Native.append_writer_new(table) do + case new(table) do + {:ok, w} -> w {:error, reason} -> raise "failed to create append writer: #{reason}" - w -> w end end @@ -67,5 +67,9 @@ defmodule Fluss.AppendWriter do end @spec flush(t()) :: :ok | {:error, String.t()} - def flush(writer), do: Native.append_writer_flush(writer) + def flush(writer) do + writer + |> Native.append_writer_flush() + |> Native.await_nif() + end end diff --git a/bindings/elixir/lib/fluss/config.ex b/bindings/elixir/lib/fluss/config.ex index daeec1f6..1120a5f4 100644 --- a/bindings/elixir/lib/fluss/config.ex +++ b/bindings/elixir/lib/fluss/config.ex @@ -19,40 +19,49 @@ defmodule Fluss.Config do @moduledoc """ Client configuration for connecting to a Fluss cluster. + Fields left as `nil` use the client's defaults. + ## Examples config = Fluss.Config.new("localhost:9123") config = - Fluss.Config.default() - |> Fluss.Config.set_bootstrap_servers("host1:9123,host2:9123") + Fluss.Config.new("host1:9123,host2:9123") |> Fluss.Config.set_writer_batch_size(1_048_576) """ - alias Fluss.Native + @enforce_keys [:bootstrap_servers] + defstruct bootstrap_servers: nil, + writer_batch_size: nil, + writer_batch_timeout_ms: nil - @type t :: reference() + @type t :: %__MODULE__{ + bootstrap_servers: String.t(), + writer_batch_size: non_neg_integer() | nil, + writer_batch_timeout_ms: non_neg_integer() | nil + } @spec new(String.t()) :: t() def new(bootstrap_servers) when is_binary(bootstrap_servers) do - Native.config_new(bootstrap_servers) + %__MODULE__{bootstrap_servers: bootstrap_servers} end @spec default() :: t() - def default, do: Native.config_default() + def default, do: %__MODULE__{bootstrap_servers: ""} @spec set_bootstrap_servers(t(), String.t()) :: t() - def set_bootstrap_servers(config, servers), - do: Native.config_set_bootstrap_servers(config, servers) + def set_bootstrap_servers(%__MODULE__{} = config, servers) when is_binary(servers), + do: %{config | bootstrap_servers: servers} - @spec set_writer_batch_size(t(), integer()) :: t() - def set_writer_batch_size(config, size), do: Native.config_set_writer_batch_size(config, size) + @spec set_writer_batch_size(t(), non_neg_integer()) :: t() + def set_writer_batch_size(%__MODULE__{} = config, size) when is_integer(size), + do: %{config | writer_batch_size: size} - @spec set_writer_batch_timeout_ms(t(), integer()) :: t() - def set_writer_batch_timeout_ms(config, ms), - do: Native.config_set_writer_batch_timeout_ms(config, ms) + @spec set_writer_batch_timeout_ms(t(), non_neg_integer()) :: t() + def set_writer_batch_timeout_ms(%__MODULE__{} = config, ms) when is_integer(ms), + do: %{config | writer_batch_timeout_ms: ms} @spec get_bootstrap_servers(t()) :: String.t() - def get_bootstrap_servers(config), do: Native.config_get_bootstrap_servers(config) + def get_bootstrap_servers(%__MODULE__{bootstrap_servers: servers}), do: servers end diff --git a/bindings/elixir/lib/fluss/connection.ex b/bindings/elixir/lib/fluss/connection.ex index 20fb6b59..0fd66468 100644 --- a/bindings/elixir/lib/fluss/connection.ex +++ b/bindings/elixir/lib/fluss/connection.ex @@ -19,6 +19,10 @@ defmodule Fluss.Connection do @moduledoc """ A connection to a Fluss cluster. + Errors are per-operation, not per-connection. If the server becomes + unreachable, individual calls fail but the connection recovers + transparently — there is no need to recreate it. + ## Examples config = Fluss.Config.new("localhost:9123") @@ -31,18 +35,17 @@ defmodule Fluss.Connection do @type t :: reference() @spec new(Fluss.Config.t()) :: {:ok, t()} | {:error, String.t()} - def new(config) do - case Native.connection_new(config) do - {:error, _} = err -> err - conn -> {:ok, conn} - end + def new(%Fluss.Config{} = config) do + config + |> Native.connection_new() + |> Native.await_nif() end @spec new!(Fluss.Config.t()) :: t() - def new!(config) do - case Native.connection_new(config) do + def new!(%Fluss.Config{} = config) do + case new(config) do + {:ok, conn} -> conn {:error, reason} -> raise "failed to connect to Fluss: #{reason}" - conn -> conn end end end diff --git a/bindings/elixir/lib/fluss/log_scanner.ex b/bindings/elixir/lib/fluss/log_scanner.ex index f6c2d1be..a47ea92e 100644 --- a/bindings/elixir/lib/fluss/log_scanner.ex +++ b/bindings/elixir/lib/fluss/log_scanner.ex @@ -56,26 +56,35 @@ defmodule Fluss.LogScanner do @spec new!(Fluss.Table.t()) :: t() def new!(table) do - case Native.log_scanner_new(table) do + case new(table) do + {:ok, s} -> s {:error, reason} -> raise "failed to create log scanner: #{reason}" - s -> s end end @spec subscribe(t(), integer(), integer()) :: :ok | {:error, String.t()} - def subscribe(scanner, bucket, offset), - do: Native.log_scanner_subscribe(scanner, bucket, offset) + def subscribe(scanner, bucket, offset) do + scanner + |> Native.log_scanner_subscribe(bucket, offset) + |> Native.await_nif() + end @doc """ Subscribes to multiple buckets. Takes a list of `{bucket_id, offset}` tuples. """ @spec subscribe_buckets(t(), [{integer(), integer()}]) :: :ok | {:error, String.t()} - def subscribe_buckets(scanner, bucket_offsets) when is_list(bucket_offsets), - do: Native.log_scanner_subscribe_buckets(scanner, bucket_offsets) + def subscribe_buckets(scanner, bucket_offsets) when is_list(bucket_offsets) do + scanner + |> Native.log_scanner_subscribe_buckets(bucket_offsets) + |> Native.await_nif() + end @spec unsubscribe(t(), integer()) :: :ok | {:error, String.t()} - def unsubscribe(scanner, bucket), - do: Native.log_scanner_unsubscribe(scanner, bucket) + def unsubscribe(scanner, bucket) do + scanner + |> Native.log_scanner_unsubscribe(bucket) + |> Native.await_nif() + end @doc """ Starts a non-blocking poll. Returns `:ok` immediately. diff --git a/bindings/elixir/lib/fluss/native.ex b/bindings/elixir/lib/fluss/native.ex index c163e1e9..865dda14 100644 --- a/bindings/elixir/lib/fluss/native.ex +++ b/bindings/elixir/lib/fluss/native.ex @@ -19,14 +19,6 @@ defmodule Fluss.Native do @moduledoc false use Rustler, otp_app: :fluss, crate: "fluss_nif" - # Config - def config_new(_bootstrap_servers), do: :erlang.nif_error(:nif_not_loaded) - def config_default, do: :erlang.nif_error(:nif_not_loaded) - def config_set_bootstrap_servers(_config, _servers), do: :erlang.nif_error(:nif_not_loaded) - def config_set_writer_batch_size(_config, _size), do: :erlang.nif_error(:nif_not_loaded) - def config_set_writer_batch_timeout_ms(_config, _ms), do: :erlang.nif_error(:nif_not_loaded) - def config_get_bootstrap_servers(_config), do: :erlang.nif_error(:nif_not_loaded) - # Connection def connection_new(_config), do: :erlang.nif_error(:nif_not_loaded) @@ -49,11 +41,7 @@ defmodule Fluss.Native do def admin_list_tables(_admin, _database), do: :erlang.nif_error(:nif_not_loaded) - # Schema - def schema_builder_new, do: :erlang.nif_error(:nif_not_loaded) - def schema_builder_column(_builder, _name, _data_type), do: :erlang.nif_error(:nif_not_loaded) - def schema_builder_primary_key(_builder, _keys), do: :erlang.nif_error(:nif_not_loaded) - def schema_builder_build(_builder), do: :erlang.nif_error(:nif_not_loaded) + # Schema / TableDescriptor def table_descriptor_new(_schema, _bucket_count, _properties), do: :erlang.nif_error(:nif_not_loaded) @@ -82,4 +70,11 @@ defmodule Fluss.Native do # Constants def earliest_offset, do: :erlang.nif_error(:nif_not_loaded) + + @doc false + def await_nif(ref) do + receive do + {^ref, result} -> result + end + end end diff --git a/bindings/elixir/lib/fluss/schema.ex b/bindings/elixir/lib/fluss/schema.ex index 566a9b0c..e11911ee 100644 --- a/bindings/elixir/lib/fluss/schema.ex +++ b/bindings/elixir/lib/fluss/schema.ex @@ -17,7 +17,7 @@ defmodule Fluss.Schema do @moduledoc """ - Schema builder for defining table columns and primary keys. + Schema definition for a Fluss table. Simple types: `:boolean`, `:tinyint`, `:smallint`, `:int`, `:bigint`, `:float`, `:double`, `:string`, `:bytes`, `:date`, `:time`, `:timestamp`, `:timestamp_ltz` @@ -27,18 +27,14 @@ defmodule Fluss.Schema do ## Examples schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) |> Fluss.Schema.column("name", :string) |> Fluss.Schema.column("amount", {:decimal, 10, 2}) - |> Fluss.Schema.build!() """ - alias Fluss.Native - - @type t :: reference() - @type builder :: reference() + defstruct columns: [], primary_key: [] @type data_type :: :boolean @@ -58,30 +54,21 @@ defmodule Fluss.Schema do | {:char, non_neg_integer()} | {:binary, non_neg_integer()} - @spec build() :: builder() - def build, do: Native.schema_builder_new() + @type t :: %__MODULE__{ + columns: [{String.t(), data_type()}], + primary_key: [String.t()] + } - @spec column(builder(), String.t(), data_type()) :: builder() - def column(builder, name, data_type) do - case Native.schema_builder_column(builder, name, data_type) do - {:error, reason} -> raise "failed to add column: #{reason}" - ref -> ref - end - end + @spec new() :: t() + def new, do: %__MODULE__{} - @spec primary_key(builder(), [String.t()]) :: builder() - def primary_key(builder, keys) do - case Native.schema_builder_primary_key(builder, keys) do - {:error, reason} -> raise "failed to set primary key: #{reason}" - ref -> ref - end + @spec column(t(), String.t(), data_type()) :: t() + def column(%__MODULE__{} = schema, name, data_type) when is_binary(name) do + %{schema | columns: schema.columns ++ [{name, data_type}]} end - @spec build!(builder()) :: t() - def build!(builder) do - case Native.schema_builder_build(builder) do - {:error, reason} -> raise "failed to build schema: #{reason}" - ref -> ref - end + @spec primary_key(t(), [String.t()]) :: t() + def primary_key(%__MODULE__{} = schema, keys) when is_list(keys) do + %{schema | primary_key: keys} end end diff --git a/bindings/elixir/lib/fluss/table.ex b/bindings/elixir/lib/fluss/table.ex index 7d469244..59f864af 100644 --- a/bindings/elixir/lib/fluss/table.ex +++ b/bindings/elixir/lib/fluss/table.ex @@ -26,17 +26,16 @@ defmodule Fluss.Table do @spec get(Fluss.Connection.t(), String.t(), String.t()) :: {:ok, t()} | {:error, String.t()} def get(conn, database, table) do - case Native.table_get(conn, database, table) do - {:error, _} = err -> err - t -> {:ok, t} - end + conn + |> Native.table_get(database, table) + |> Native.await_nif() end @spec get!(Fluss.Connection.t(), String.t(), String.t()) :: t() def get!(conn, database, table) do - case Native.table_get(conn, database, table) do + case get(conn, database, table) do + {:ok, t} -> t {:error, reason} -> raise "failed to get table: #{reason}" - t -> t end end diff --git a/bindings/elixir/lib/fluss/table_descriptor.ex b/bindings/elixir/lib/fluss/table_descriptor.ex index 40bc88d3..96c3e1df 100644 --- a/bindings/elixir/lib/fluss/table_descriptor.ex +++ b/bindings/elixir/lib/fluss/table_descriptor.ex @@ -33,7 +33,7 @@ defmodule Fluss.TableDescriptor do @type t :: reference() @spec new!(Fluss.Schema.t(), keyword()) :: t() - def new!(schema, opts \\ []) do + def new!(%Fluss.Schema{} = schema, opts \\ []) do bucket_count = Keyword.get(opts, :bucket_count) properties = Keyword.get(opts, :properties, []) diff --git a/bindings/elixir/lib/fluss/write_handle.ex b/bindings/elixir/lib/fluss/write_handle.ex index eb1e696b..f0ec295b 100644 --- a/bindings/elixir/lib/fluss/write_handle.ex +++ b/bindings/elixir/lib/fluss/write_handle.ex @@ -28,7 +28,11 @@ defmodule Fluss.WriteHandle do @type t :: reference() @spec wait(t()) :: :ok | {:error, String.t()} - def wait(handle), do: Native.write_handle_wait(handle) + def wait(handle) do + handle + |> Native.write_handle_wait() + |> Native.await_nif() + end @spec wait!(t()) :: :ok def wait!(handle) do diff --git a/bindings/elixir/native/fluss_nif/src/admin.rs b/bindings/elixir/native/fluss_nif/src/admin.rs index cfcd2ccd..e3f29aeb 100644 --- a/bindings/elixir/native/fluss_nif/src/admin.rs +++ b/bindings/elixir/native/fluss_nif/src/admin.rs @@ -15,16 +15,18 @@ // specific language governing permissions and limitations // under the License. -use crate::RUNTIME; -use crate::atoms::{self, to_nif_err}; +use crate::async_nif; +use crate::atoms::to_nif_err; use crate::connection::ConnectionResource; use crate::schema::TableDescriptorResource; use fluss::client::FlussAdmin; use fluss::metadata::TablePath; -use rustler::{Atom, ResourceArc}; +use rustler::{Env, ResourceArc, Term}; use std::sync::Arc; -pub struct AdminResource(pub Arc); +pub struct AdminResource { + pub inner: Arc, +} impl std::panic::RefUnwindSafe for AdminResource {} @@ -35,84 +37,85 @@ impl rustler::Resource for AdminResource {} fn admin_new( conn: ResourceArc, ) -> Result, rustler::Error> { - let admin = conn.0.get_admin().map_err(to_nif_err)?; - Ok(ResourceArc::new(AdminResource(admin))) + let inner = conn.inner.get_admin().map_err(to_nif_err)?; + Ok(ResourceArc::new(AdminResource { inner })) } -#[rustler::nif(schedule = "DirtyIo")] -fn admin_create_database( +#[rustler::nif] +fn admin_create_database<'a>( + env: Env<'a>, admin: ResourceArc, database_name: String, ignore_if_exists: bool, -) -> Result { - RUNTIME - .block_on( - admin - .0 - .create_database(&database_name, None, ignore_if_exists), - ) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +) -> Term<'a> { + async_nif::spawn_task(env, async move { + admin + .inner + .create_database(&database_name, None, ignore_if_exists) + .await + }) } -#[rustler::nif(schedule = "DirtyIo")] -fn admin_drop_database( +#[rustler::nif] +fn admin_drop_database<'a>( + env: Env<'a>, admin: ResourceArc, database_name: String, ignore_if_not_exists: bool, -) -> Result { - RUNTIME - .block_on( - admin - .0 - .drop_database(&database_name, ignore_if_not_exists, false), - ) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +) -> Term<'a> { + async_nif::spawn_task(env, async move { + admin + .inner + .drop_database(&database_name, ignore_if_not_exists, false) + .await + }) } -#[rustler::nif(schedule = "DirtyIo")] -fn admin_list_databases(admin: ResourceArc) -> Result, rustler::Error> { - RUNTIME - .block_on(admin.0.list_databases()) - .map_err(to_nif_err) +#[rustler::nif] +fn admin_list_databases<'a>(env: Env<'a>, admin: ResourceArc) -> Term<'a> { + async_nif::spawn_task_with_result(env, async move { admin.inner.list_databases().await }) } -#[rustler::nif(schedule = "DirtyIo")] -fn admin_create_table( +#[rustler::nif] +fn admin_create_table<'a>( + env: Env<'a>, admin: ResourceArc, database_name: String, table_name: String, descriptor: ResourceArc, ignore_if_exists: bool, -) -> Result { - let path = TablePath::new(&database_name, &table_name); - RUNTIME - .block_on(admin.0.create_table(&path, &descriptor.0, ignore_if_exists)) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +) -> Term<'a> { + async_nif::spawn_task(env, async move { + let path = TablePath::new(&database_name, &table_name); + admin + .inner + .create_table(&path, &descriptor.inner, ignore_if_exists) + .await + }) } -#[rustler::nif(schedule = "DirtyIo")] -fn admin_drop_table( +#[rustler::nif] +fn admin_drop_table<'a>( + env: Env<'a>, admin: ResourceArc, database_name: String, table_name: String, ignore_if_not_exists: bool, -) -> Result { - let path = TablePath::new(&database_name, &table_name); - RUNTIME - .block_on(admin.0.drop_table(&path, ignore_if_not_exists)) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +) -> Term<'a> { + async_nif::spawn_task(env, async move { + let path = TablePath::new(&database_name, &table_name); + admin.inner.drop_table(&path, ignore_if_not_exists).await + }) } -#[rustler::nif(schedule = "DirtyIo")] -fn admin_list_tables( +#[rustler::nif] +fn admin_list_tables<'a>( + env: Env<'a>, admin: ResourceArc, database_name: String, -) -> Result, rustler::Error> { - RUNTIME - .block_on(admin.0.list_tables(&database_name)) - .map_err(to_nif_err) +) -> Term<'a> { + async_nif::spawn_task_with_result( + env, + async move { admin.inner.list_tables(&database_name).await }, + ) } diff --git a/bindings/elixir/native/fluss_nif/src/append_writer.rs b/bindings/elixir/native/fluss_nif/src/append_writer.rs index de9e90a8..adb1fb0d 100644 --- a/bindings/elixir/native/fluss_nif/src/append_writer.rs +++ b/bindings/elixir/native/fluss_nif/src/append_writer.rs @@ -16,13 +16,14 @@ // under the License. use crate::RUNTIME; -use crate::atoms::{self, to_nif_err}; +use crate::async_nif; +use crate::atoms::to_nif_err; use crate::row_convert; use crate::table::TableResource; use crate::write_handle::WriteHandleResource; use fluss::client::AppendWriter; use fluss::metadata::Column; -use rustler::{Atom, Env, ResourceArc, Term}; +use rustler::{Env, ResourceArc, Term}; pub struct AppendWriterResource { pub writer: AppendWriter, @@ -62,10 +63,7 @@ fn append_writer_append<'a>( Ok(ResourceArc::new(WriteHandleResource::new(future))) } -#[rustler::nif(schedule = "DirtyIo")] -fn append_writer_flush(writer: ResourceArc) -> Result { - RUNTIME - .block_on(writer.writer.flush()) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +#[rustler::nif] +fn append_writer_flush<'a>(env: Env<'a>, writer: ResourceArc) -> Term<'a> { + async_nif::spawn_task(env, async move { writer.writer.flush().await }) } diff --git a/bindings/elixir/native/fluss_nif/src/async_nif.rs b/bindings/elixir/native/fluss_nif/src/async_nif.rs new file mode 100644 index 00000000..fd6e495b --- /dev/null +++ b/bindings/elixir/native/fluss_nif/src/async_nif.rs @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +//! Async NIF helpers — spawn on tokio, send `{ref, result}` back +//! as a BEAM message instead of blocking dirty schedulers. + +use crate::RUNTIME; +use crate::atoms; +use rustler::env::OwnedEnv; +use rustler::{Encoder, Env, Term}; +use std::future::Future; + +/// Sends `{ref, :ok}` or `{ref, {:error, reason}}` on completion. +pub fn spawn_task<'a, F, E>(env: Env<'a>, future: F) -> Term<'a> +where + F: Future> + Send + 'static, + E: std::fmt::Display + Send + 'static, +{ + let pid = env.pid(); + let ref_term: Term<'a> = *env.make_ref(); + let mut task_env = OwnedEnv::new(); + let saved_ref = task_env.save(ref_term); + + RUNTIME.spawn(async move { + let result = future.await; + let _ = task_env.send_and_clear(&pid, |env| { + let r = saved_ref.load(env); + match result { + Ok(()) => (r, atoms::ok()).encode(env), + Err(e) => (r, (atoms::error(), e.to_string())).encode(env), + } + }); + }); + + ref_term +} + +/// Sends `{ref, {:ok, value}}` or `{ref, {:error, reason}}` on completion. +pub fn spawn_task_with_result<'a, F, T, E>(env: Env<'a>, future: F) -> Term<'a> +where + F: Future> + Send + 'static, + T: Encoder + Send + 'static, + E: std::fmt::Display + Send + 'static, +{ + let pid = env.pid(); + let ref_term: Term<'a> = *env.make_ref(); + let mut task_env = OwnedEnv::new(); + let saved_ref = task_env.save(ref_term); + + RUNTIME.spawn(async move { + let result = future.await; + let _ = task_env.send_and_clear(&pid, |env| { + let r = saved_ref.load(env); + match result { + Ok(val) => (r, (atoms::ok(), val)).encode(env), + Err(e) => (r, (atoms::error(), e.to_string())).encode(env), + } + }); + }); + + ref_term +} + +/// Sends `{ref, {:error, reason}}` immediately (no async work). +pub fn send_error<'a>(env: Env<'a>, msg: &str) -> Term<'a> { + let pid = env.pid(); + let ref_term: Term<'a> = *env.make_ref(); + let mut task_env = OwnedEnv::new(); + let saved_ref = task_env.save(ref_term); + let msg = msg.to_string(); + + let _ = task_env.send_and_clear(&pid, |env| { + let r = saved_ref.load(env); + (r, (atoms::error(), msg)).encode(env) + }); + + ref_term +} diff --git a/bindings/elixir/native/fluss_nif/src/atoms.rs b/bindings/elixir/native/fluss_nif/src/atoms.rs index 11e7f771..20beec71 100644 --- a/bindings/elixir/native/fluss_nif/src/atoms.rs +++ b/bindings/elixir/native/fluss_nif/src/atoms.rs @@ -17,6 +17,7 @@ rustler::atoms! { ok, + error, nil, // Change types diff --git a/bindings/elixir/native/fluss_nif/src/config.rs b/bindings/elixir/native/fluss_nif/src/config.rs index 9afabceb..536ed68f 100644 --- a/bindings/elixir/native/fluss_nif/src/config.rs +++ b/bindings/elixir/native/fluss_nif/src/config.rs @@ -16,60 +16,29 @@ // under the License. use fluss::config::Config; -use rustler::ResourceArc; - -pub struct ConfigResource(pub Config); - -impl std::panic::RefUnwindSafe for ConfigResource {} - -#[rustler::resource_impl] -impl rustler::Resource for ConfigResource {} - -#[rustler::nif] -fn config_new(bootstrap_servers: String) -> ResourceArc { - let config = Config { - bootstrap_servers, - ..Config::default() - }; - ResourceArc::new(ConfigResource(config)) -} - -#[rustler::nif] -fn config_default() -> ResourceArc { - ResourceArc::new(ConfigResource(Config::default())) -} - -#[rustler::nif] -fn config_set_bootstrap_servers( - config: ResourceArc, - servers: String, -) -> ResourceArc { - let mut new_config = config.0.clone(); - new_config.bootstrap_servers = servers; - ResourceArc::new(ConfigResource(new_config)) -} - -#[rustler::nif] -fn config_set_writer_batch_size( - config: ResourceArc, - size: i32, -) -> ResourceArc { - let mut new_config = config.0.clone(); - new_config.writer_batch_size = size; - ResourceArc::new(ConfigResource(new_config)) -} - -#[rustler::nif] -fn config_set_writer_batch_timeout_ms( - config: ResourceArc, - timeout_ms: i64, -) -> ResourceArc { - let mut new_config = config.0.clone(); - new_config.writer_batch_timeout_ms = timeout_ms; - ResourceArc::new(ConfigResource(new_config)) +use rustler::NifStruct; + +/// Decoded from `%Fluss.Config{}` Elixir struct. +#[derive(NifStruct)] +#[module = "Fluss.Config"] +pub struct NifConfig { + pub bootstrap_servers: String, + pub writer_batch_size: Option, + pub writer_batch_timeout_ms: Option, } -#[rustler::nif] -fn config_get_bootstrap_servers(config: ResourceArc) -> String { - config.0.bootstrap_servers.clone() +impl NifConfig { + pub fn into_core(self) -> Config { + let mut config = Config { + bootstrap_servers: self.bootstrap_servers, + ..Config::default() + }; + if let Some(size) = self.writer_batch_size { + config.writer_batch_size = size; + } + if let Some(ms) = self.writer_batch_timeout_ms { + config.writer_batch_timeout_ms = ms; + } + config + } } diff --git a/bindings/elixir/native/fluss_nif/src/connection.rs b/bindings/elixir/native/fluss_nif/src/connection.rs index 1ea8800d..4c788eee 100644 --- a/bindings/elixir/native/fluss_nif/src/connection.rs +++ b/bindings/elixir/native/fluss_nif/src/connection.rs @@ -15,26 +15,29 @@ // specific language governing permissions and limitations // under the License. -use crate::RUNTIME; -use crate::atoms::to_nif_err; -use crate::config::ConfigResource; +use crate::async_nif; +use crate::config::NifConfig; use fluss::client::FlussConnection; -use rustler::ResourceArc; +use rustler::{Env, ResourceArc, Term}; use std::sync::Arc; -pub struct ConnectionResource(pub Arc); +pub struct ConnectionResource { + pub inner: Arc, +} impl std::panic::RefUnwindSafe for ConnectionResource {} #[rustler::resource_impl] impl rustler::Resource for ConnectionResource {} -#[rustler::nif(schedule = "DirtyIo")] -fn connection_new( - config: ResourceArc, -) -> Result, rustler::Error> { - let conn = RUNTIME - .block_on(FlussConnection::new(config.0.clone())) - .map_err(to_nif_err)?; - Ok(ResourceArc::new(ConnectionResource(Arc::new(conn)))) +#[rustler::nif] +fn connection_new<'a>(env: Env<'a>, config: NifConfig) -> Term<'a> { + let core_config = config.into_core(); + async_nif::spawn_task_with_result(env, async move { + FlussConnection::new(core_config).await.map(|conn| { + ResourceArc::new(ConnectionResource { + inner: Arc::new(conn), + }) + }) + }) } diff --git a/bindings/elixir/native/fluss_nif/src/lib.rs b/bindings/elixir/native/fluss_nif/src/lib.rs index 31ee3625..a843d65f 100644 --- a/bindings/elixir/native/fluss_nif/src/lib.rs +++ b/bindings/elixir/native/fluss_nif/src/lib.rs @@ -25,6 +25,7 @@ mod admin; mod append_writer; +mod async_nif; mod atoms; mod config; mod connection; diff --git a/bindings/elixir/native/fluss_nif/src/log_scanner.rs b/bindings/elixir/native/fluss_nif/src/log_scanner.rs index 6289b263..f0aafc2e 100644 --- a/bindings/elixir/native/fluss_nif/src/log_scanner.rs +++ b/bindings/elixir/native/fluss_nif/src/log_scanner.rs @@ -16,15 +16,17 @@ // under the License. use crate::RUNTIME; +use crate::async_nif; use crate::atoms::{self, to_nif_err}; use crate::row_convert; use crate::table::TableResource; -use fluss::client::LogScanner; +use fluss::client::{EARLIEST_OFFSET, LogScanner}; +use fluss::error::Error; use fluss::metadata::Column; -use fluss::record::ChangeType; +use fluss::record::{ChangeType, ScanRecords}; use rustler::env::OwnedEnv; use rustler::types::LocalPid; -use rustler::{Atom, Encoder, Env, ResourceArc}; +use rustler::{Atom, Encoder, Env, ResourceArc, Term}; use std::collections::HashMap; use std::time::Duration; @@ -50,39 +52,41 @@ fn log_scanner_new( Ok(ResourceArc::new(LogScannerResource { scanner, columns })) } -#[rustler::nif(schedule = "DirtyIo")] -fn log_scanner_subscribe( +#[rustler::nif] +fn log_scanner_subscribe<'a>( + env: Env<'a>, scanner: ResourceArc, bucket: i32, offset: i64, -) -> Result { - RUNTIME - .block_on(scanner.scanner.subscribe(bucket, offset)) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +) -> Term<'a> { + async_nif::spawn_task(env, async move { + scanner.scanner.subscribe(bucket, offset).await + }) } -#[rustler::nif(schedule = "DirtyIo")] -fn log_scanner_subscribe_buckets( +#[rustler::nif] +fn log_scanner_subscribe_buckets<'a>( + env: Env<'a>, scanner: ResourceArc, bucket_offsets: Vec<(i32, i64)>, -) -> Result { +) -> Term<'a> { let map: HashMap = bucket_offsets.into_iter().collect(); - RUNTIME - .block_on(scanner.scanner.subscribe_buckets(&map)) - .map_err(to_nif_err)?; - Ok(atoms::ok()) + async_nif::spawn_task( + env, + async move { scanner.scanner.subscribe_buckets(&map).await }, + ) } -#[rustler::nif(schedule = "DirtyIo")] -fn log_scanner_unsubscribe( +#[rustler::nif] +fn log_scanner_unsubscribe<'a>( + env: Env<'a>, scanner: ResourceArc, bucket: i32, -) -> Result { - RUNTIME - .block_on(scanner.scanner.unsubscribe(bucket)) - .map_err(to_nif_err)?; - Ok(atoms::ok()) +) -> Term<'a> { + async_nif::spawn_task( + env, + async move { scanner.scanner.unsubscribe(bucket).await }, + ) } #[rustler::nif] @@ -101,11 +105,7 @@ fn log_scanner_poll(env: Env, scanner: ResourceArc, timeout_ atoms::ok() } -fn send_poll_result( - pid: &LocalPid, - result: Result, - columns: &[Column], -) { +fn send_poll_result(pid: &LocalPid, result: Result, columns: &[Column]) { let mut msg_env = OwnedEnv::new(); match result { @@ -127,7 +127,7 @@ fn send_poll_result( fn encode_scan_records<'a>( env: Env<'a>, - scan_records: fluss::record::ScanRecords, + scan_records: ScanRecords, columns: &[Column], ) -> Result, String> { let column_atoms = row_convert::intern_column_atoms(env, columns); @@ -165,5 +165,5 @@ fn encode_scan_records<'a>( #[rustler::nif] fn earliest_offset() -> i64 { - fluss::client::EARLIEST_OFFSET + EARLIEST_OFFSET } diff --git a/bindings/elixir/native/fluss_nif/src/schema.rs b/bindings/elixir/native/fluss_nif/src/schema.rs index baf3f6cc..5d61d29d 100644 --- a/bindings/elixir/native/fluss_nif/src/schema.rs +++ b/bindings/elixir/native/fluss_nif/src/schema.rs @@ -16,24 +16,15 @@ // under the License. use crate::atoms::to_nif_err; -use fluss::metadata::{DataTypes, Schema, SchemaBuilder, TableDescriptor}; -use rustler::{NifTaggedEnum, ResourceArc}; -use std::sync::Mutex; +use fluss::metadata::{self, DataTypes, Schema, TableDescriptor}; +use rustler::{NifStruct, NifTaggedEnum, ResourceArc}; -pub struct SchemaBuilderResource(pub Mutex>); -pub struct SchemaResource(pub Schema); -pub struct TableDescriptorResource(pub TableDescriptor); +pub struct TableDescriptorResource { + pub inner: TableDescriptor, +} -impl std::panic::RefUnwindSafe for SchemaBuilderResource {} -impl std::panic::RefUnwindSafe for SchemaResource {} impl std::panic::RefUnwindSafe for TableDescriptorResource {} -#[rustler::resource_impl] -impl rustler::Resource for SchemaBuilderResource {} - -#[rustler::resource_impl] -impl rustler::Resource for SchemaResource {} - #[rustler::resource_impl] impl rustler::Resource for TableDescriptorResource {} @@ -61,7 +52,7 @@ pub enum DataType { Binary(usize), } -fn to_fluss_type(dt: &DataType) -> fluss::metadata::DataType { +fn to_fluss_type(dt: &DataType) -> metadata::DataType { match dt { DataType::Boolean => DataTypes::boolean(), DataType::Tinyint => DataTypes::tinyint(), @@ -82,59 +73,30 @@ fn to_fluss_type(dt: &DataType) -> fluss::metadata::DataType { } } -#[rustler::nif] -fn schema_builder_new() -> ResourceArc { - ResourceArc::new(SchemaBuilderResource(Mutex::new(Some(Schema::builder())))) -} - -#[rustler::nif] -fn schema_builder_column( - builder: ResourceArc, - name: String, - data_type: DataType, -) -> Result, rustler::Error> { - let mut guard = builder.0.lock().unwrap(); - let b = guard - .take() - .ok_or_else(|| to_nif_err("schema builder already consumed"))?; - *guard = Some(b.column(&name, to_fluss_type(&data_type))); - drop(guard); - Ok(builder) -} - -#[rustler::nif] -fn schema_builder_primary_key( - builder: ResourceArc, - keys: Vec, -) -> Result, rustler::Error> { - let mut guard = builder.0.lock().unwrap(); - let b = guard - .take() - .ok_or_else(|| to_nif_err("schema builder already consumed"))?; - *guard = Some(b.primary_key(keys)); - drop(guard); - Ok(builder) -} - -#[rustler::nif] -fn schema_builder_build( - builder: ResourceArc, -) -> Result, rustler::Error> { - let mut guard = builder.0.lock().unwrap(); - let b = guard - .take() - .ok_or_else(|| to_nif_err("schema builder already consumed"))?; - let schema = b.build().map_err(to_nif_err)?; - Ok(ResourceArc::new(SchemaResource(schema))) +/// Decoded from `%Fluss.Schema{}` Elixir struct. +#[derive(NifStruct)] +#[module = "Fluss.Schema"] +pub struct NifSchema { + pub columns: Vec<(String, DataType)>, + pub primary_key: Vec, } #[rustler::nif] fn table_descriptor_new( - schema: ResourceArc, + schema: NifSchema, bucket_count: Option, properties: Vec<(String, String)>, ) -> Result, rustler::Error> { - let mut builder = TableDescriptor::builder().schema(schema.0.clone()); + let mut schema_builder = Schema::builder(); + for (name, dt) in &schema.columns { + schema_builder = schema_builder.column(name, to_fluss_type(dt)); + } + if !schema.primary_key.is_empty() { + schema_builder = schema_builder.primary_key(schema.primary_key); + } + let built_schema = schema_builder.build().map_err(to_nif_err)?; + + let mut builder = TableDescriptor::builder().schema(built_schema); if let Some(count) = bucket_count { builder = builder.distributed_by(Some(count), vec![]); } @@ -142,5 +104,7 @@ fn table_descriptor_new( builder = builder.property(&key, &value); } let descriptor = builder.build().map_err(to_nif_err)?; - Ok(ResourceArc::new(TableDescriptorResource(descriptor))) + Ok(ResourceArc::new(TableDescriptorResource { + inner: descriptor, + })) } diff --git a/bindings/elixir/native/fluss_nif/src/table.rs b/bindings/elixir/native/fluss_nif/src/table.rs index 94fafc57..d48ff7ab 100644 --- a/bindings/elixir/native/fluss_nif/src/table.rs +++ b/bindings/elixir/native/fluss_nif/src/table.rs @@ -15,12 +15,12 @@ // specific language governing permissions and limitations // under the License. -use crate::RUNTIME; -use crate::atoms::to_nif_err; +use crate::async_nif; use crate::connection::ConnectionResource; use fluss::client::{FlussConnection, FlussTable, Metadata}; +use fluss::error::Error; use fluss::metadata::{Column, TableInfo, TablePath}; -use rustler::ResourceArc; +use rustler::{Env, ResourceArc, Term}; use std::sync::Arc; /// Holds the data needed to reconstruct FlussTable (which has a lifetime @@ -52,22 +52,26 @@ impl TableResource { } } -#[rustler::nif(schedule = "DirtyIo")] -fn table_get( +#[rustler::nif] +fn table_get<'a>( + env: Env<'a>, conn: ResourceArc, database_name: String, table_name: String, -) -> Result, rustler::Error> { - let path = TablePath::new(&database_name, &table_name); - let table = RUNTIME - .block_on(conn.0.get_table(&path)) - .map_err(to_nif_err)?; - - Ok(ResourceArc::new(TableResource { - connection: conn.0.clone(), - metadata: table.metadata().clone(), - table_info: table.get_table_info().clone(), - })) +) -> Term<'a> { + let conn_arc = conn.inner.clone(); + async_nif::spawn_task_with_result(env, async move { + let path = TablePath::new(&database_name, &table_name); + let (metadata, table_info) = { + let table = conn_arc.get_table(&path).await?; + (table.metadata().clone(), table.get_table_info().clone()) + }; + Ok::<_, Error>(ResourceArc::new(TableResource { + connection: conn_arc, + metadata, + table_info, + })) + }) } #[rustler::nif] diff --git a/bindings/elixir/native/fluss_nif/src/write_handle.rs b/bindings/elixir/native/fluss_nif/src/write_handle.rs index 31684569..e3b325d0 100644 --- a/bindings/elixir/native/fluss_nif/src/write_handle.rs +++ b/bindings/elixir/native/fluss_nif/src/write_handle.rs @@ -15,13 +15,14 @@ // specific language governing permissions and limitations // under the License. -use crate::RUNTIME; -use crate::atoms::{self, to_nif_err}; +use crate::async_nif; use fluss::client::WriteResultFuture; -use rustler::{Atom, ResourceArc}; +use rustler::{Env, ResourceArc, Term}; use std::sync::Mutex; -pub struct WriteHandleResource(Mutex>); +pub struct WriteHandleResource { + inner: Mutex>, +} impl std::panic::RefUnwindSafe for WriteHandleResource {} @@ -30,19 +31,17 @@ impl rustler::Resource for WriteHandleResource {} impl WriteHandleResource { pub fn new(future: WriteResultFuture) -> Self { - Self(Mutex::new(Some(future))) + Self { + inner: Mutex::new(Some(future)), + } } } -#[rustler::nif(schedule = "DirtyIo")] -fn write_handle_wait(handle: ResourceArc) -> Result { - let future = handle - .0 - .lock() - .map_err(|e| to_nif_err(format!("lock poisoned: {e}")))? - .take() - .ok_or_else(|| to_nif_err("WriteHandle already consumed"))?; - - RUNTIME.block_on(future).map_err(to_nif_err)?; - Ok(atoms::ok()) +#[rustler::nif] +fn write_handle_wait<'a>(env: Env<'a>, handle: ResourceArc) -> Term<'a> { + let future = handle.inner.lock().unwrap().take(); + match future { + Some(f) => async_nif::spawn_task(env, f), + None => async_nif::send_error(env, "WriteHandle already consumed"), + } } diff --git a/bindings/elixir/test/fluss_test.exs b/bindings/elixir/test/fluss_test.exs index 2304cb06..3eee2734 100644 --- a/bindings/elixir/test/fluss_test.exs +++ b/bindings/elixir/test/fluss_test.exs @@ -18,89 +18,43 @@ defmodule FlussTest do use ExUnit.Case - describe "Config" do - test "creates config with bootstrap servers" do - config = Fluss.Config.new("localhost:9123") - assert Fluss.Config.get_bootstrap_servers(config) == "localhost:9123" - end - - test "default config uses localhost" do - config = Fluss.Config.default() - assert Fluss.Config.get_bootstrap_servers(config) == "127.0.0.1:9123" - end - - test "config chaining" do - config = - Fluss.Config.default() - |> Fluss.Config.set_bootstrap_servers("host1:9123,host2:9123") - - assert Fluss.Config.get_bootstrap_servers(config) == "host1:9123,host2:9123" - end - end - - describe "Schema" do - test "builds a simple log table schema" do - schema = - Fluss.Schema.build() - |> Fluss.Schema.column("ts", :bigint) - |> Fluss.Schema.column("message", :string) - |> Fluss.Schema.build!() - - assert is_reference(schema) - end - - test "builds a schema with all simple types" do - schema = - Fluss.Schema.build() - |> Fluss.Schema.column("a", :boolean) - |> Fluss.Schema.column("b", :tinyint) - |> Fluss.Schema.column("c", :smallint) - |> Fluss.Schema.column("d", :int) - |> Fluss.Schema.column("e", :bigint) - |> Fluss.Schema.column("f", :float) - |> Fluss.Schema.column("g", :double) - |> Fluss.Schema.column("h", :string) - |> Fluss.Schema.column("i", :bytes) - |> Fluss.Schema.column("j", :date) - |> Fluss.Schema.column("k", :time) - |> Fluss.Schema.column("l", :timestamp) - |> Fluss.Schema.column("m", :timestamp_ltz) - |> Fluss.Schema.build!() - - assert is_reference(schema) - end - - test "builds a schema with parameterized types" do - schema = - Fluss.Schema.build() - |> Fluss.Schema.column("amount", {:decimal, 10, 2}) - |> Fluss.Schema.column("code", {:char, 5}) - |> Fluss.Schema.column("data", {:binary, 16}) - |> Fluss.Schema.build!() - - assert is_reference(schema) - end - end - describe "TableDescriptor" do test "creates descriptor from schema" do - schema = - Fluss.Schema.build() - |> Fluss.Schema.column("id", :int) - |> Fluss.Schema.build!() - - descriptor = Fluss.TableDescriptor.new!(schema) - assert is_reference(descriptor) + Fluss.Schema.new() + |> Fluss.Schema.column("id", :int) + |> Fluss.TableDescriptor.new!() end test "creates descriptor with bucket count" do - schema = - Fluss.Schema.build() - |> Fluss.Schema.column("id", :int) - |> Fluss.Schema.build!() + Fluss.Schema.new() + |> Fluss.Schema.column("id", :int) + |> Fluss.TableDescriptor.new!(bucket_count: 3) + end + + test "accepts all simple data types" do + Fluss.Schema.new() + |> Fluss.Schema.column("a", :boolean) + |> Fluss.Schema.column("b", :tinyint) + |> Fluss.Schema.column("c", :smallint) + |> Fluss.Schema.column("d", :int) + |> Fluss.Schema.column("e", :bigint) + |> Fluss.Schema.column("f", :float) + |> Fluss.Schema.column("g", :double) + |> Fluss.Schema.column("h", :string) + |> Fluss.Schema.column("i", :bytes) + |> Fluss.Schema.column("j", :date) + |> Fluss.Schema.column("k", :time) + |> Fluss.Schema.column("l", :timestamp) + |> Fluss.Schema.column("m", :timestamp_ltz) + |> Fluss.TableDescriptor.new!() + end - descriptor = Fluss.TableDescriptor.new!(schema, bucket_count: 3) - assert is_reference(descriptor) + test "accepts parameterized data types" do + Fluss.Schema.new() + |> Fluss.Schema.column("amount", {:decimal, 10, 2}) + |> Fluss.Schema.column("code", {:char, 5}) + |> Fluss.Schema.column("data", {:binary, 16}) + |> Fluss.TableDescriptor.new!() end end diff --git a/bindings/elixir/test/integration/log_table_test.exs b/bindings/elixir/test/integration/log_table_test.exs index 3f538649..b3041b95 100644 --- a/bindings/elixir/test/integration/log_table_test.exs +++ b/bindings/elixir/test/integration/log_table_test.exs @@ -45,10 +45,9 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("c1", :int) |> Fluss.Schema.column("c2", :string) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -90,10 +89,9 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) |> Fluss.Schema.column("name", :string) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -128,7 +126,7 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("a_tinyint", :tinyint) |> Fluss.Schema.column("b_smallint", :smallint) |> Fluss.Schema.column("c_int", :int) @@ -137,7 +135,6 @@ defmodule Fluss.Integration.LogTableTest do |> Fluss.Schema.column("f_double", :double) |> Fluss.Schema.column("g_string", :string) |> Fluss.Schema.column("h_bool", :boolean) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -196,10 +193,9 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) |> Fluss.Schema.column("val", :string) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema, bucket_count: 3) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -249,9 +245,8 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -267,10 +262,9 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) |> Fluss.Schema.column("name", :string) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -289,9 +283,8 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) @@ -325,10 +318,9 @@ defmodule Fluss.Integration.LogTableTest do cleanup_table(admin, table_name) schema = - Fluss.Schema.build() + Fluss.Schema.new() |> Fluss.Schema.column("id", :int) |> Fluss.Schema.column("batch", :string) - |> Fluss.Schema.build!() descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, @database, table_name, descriptor, false) From 65ce959bee257d10c0560026ba5c294a1c5742e4 Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Mon, 13 Apr 2026 19:52:28 +0100 Subject: [PATCH 8/9] fix mothful names -> inner --- .../native/fluss_nif/src/append_writer.rs | 14 ++++----- .../native/fluss_nif/src/log_scanner.rs | 29 ++++++++----------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/bindings/elixir/native/fluss_nif/src/append_writer.rs b/bindings/elixir/native/fluss_nif/src/append_writer.rs index adb1fb0d..d78f564e 100644 --- a/bindings/elixir/native/fluss_nif/src/append_writer.rs +++ b/bindings/elixir/native/fluss_nif/src/append_writer.rs @@ -26,7 +26,7 @@ use fluss::metadata::Column; use rustler::{Env, ResourceArc, Term}; pub struct AppendWriterResource { - pub writer: AppendWriter, + pub inner: AppendWriter, pub columns: Vec, } @@ -41,15 +41,15 @@ fn append_writer_new( ) -> Result, rustler::Error> { // WriterClient::new() calls tokio::spawn internally. let _guard = RUNTIME.enter(); - let (writer, columns) = table.with_table(|t| { - let writer = t + let (inner, columns) = table.with_table(|t| { + let inner = t .new_append() .map_err(to_nif_err)? .create_writer() .map_err(to_nif_err)?; - Ok((writer, t.get_table_info().schema.columns().to_vec())) + Ok((inner, t.get_table_info().schema.columns().to_vec())) })?; - Ok(ResourceArc::new(AppendWriterResource { writer, columns })) + Ok(ResourceArc::new(AppendWriterResource { inner, columns })) } #[rustler::nif] @@ -59,11 +59,11 @@ fn append_writer_append<'a>( values: Term<'a>, ) -> Result, rustler::Error> { let row = row_convert::term_to_row(env, values, &writer.columns).map_err(to_nif_err)?; - let future = writer.writer.append(&row).map_err(to_nif_err)?; + let future = writer.inner.append(&row).map_err(to_nif_err)?; Ok(ResourceArc::new(WriteHandleResource::new(future))) } #[rustler::nif] fn append_writer_flush<'a>(env: Env<'a>, writer: ResourceArc) -> Term<'a> { - async_nif::spawn_task(env, async move { writer.writer.flush().await }) + async_nif::spawn_task(env, async move { writer.inner.flush().await }) } diff --git a/bindings/elixir/native/fluss_nif/src/log_scanner.rs b/bindings/elixir/native/fluss_nif/src/log_scanner.rs index f0aafc2e..93f1d04e 100644 --- a/bindings/elixir/native/fluss_nif/src/log_scanner.rs +++ b/bindings/elixir/native/fluss_nif/src/log_scanner.rs @@ -31,7 +31,7 @@ use std::collections::HashMap; use std::time::Duration; pub struct LogScannerResource { - pub scanner: LogScanner, + pub inner: LogScanner, pub columns: Vec, } @@ -45,11 +45,11 @@ fn log_scanner_new( table: ResourceArc, ) -> Result, rustler::Error> { let _guard = RUNTIME.enter(); - let (scanner, columns) = table.with_table(|t| { - let scanner = t.new_scan().create_log_scanner().map_err(to_nif_err)?; - Ok((scanner, t.get_table_info().schema.columns().to_vec())) + let (inner, columns) = table.with_table(|t| { + let inner = t.new_scan().create_log_scanner().map_err(to_nif_err)?; + Ok((inner, t.get_table_info().schema.columns().to_vec())) })?; - Ok(ResourceArc::new(LogScannerResource { scanner, columns })) + Ok(ResourceArc::new(LogScannerResource { inner, columns })) } #[rustler::nif] @@ -59,9 +59,10 @@ fn log_scanner_subscribe<'a>( bucket: i32, offset: i64, ) -> Term<'a> { - async_nif::spawn_task(env, async move { - scanner.scanner.subscribe(bucket, offset).await - }) + async_nif::spawn_task( + env, + async move { scanner.inner.subscribe(bucket, offset).await }, + ) } #[rustler::nif] @@ -73,7 +74,7 @@ fn log_scanner_subscribe_buckets<'a>( let map: HashMap = bucket_offsets.into_iter().collect(); async_nif::spawn_task( env, - async move { scanner.scanner.subscribe_buckets(&map).await }, + async move { scanner.inner.subscribe_buckets(&map).await }, ) } @@ -83,10 +84,7 @@ fn log_scanner_unsubscribe<'a>( scanner: ResourceArc, bucket: i32, ) -> Term<'a> { - async_nif::spawn_task( - env, - async move { scanner.scanner.unsubscribe(bucket).await }, - ) + async_nif::spawn_task(env, async move { scanner.inner.unsubscribe(bucket).await }) } #[rustler::nif] @@ -95,10 +93,7 @@ fn log_scanner_poll(env: Env, scanner: ResourceArc, timeout_ let scanner = scanner.clone(); RUNTIME.spawn(async move { - let result = scanner - .scanner - .poll(Duration::from_millis(timeout_ms)) - .await; + let result = scanner.inner.poll(Duration::from_millis(timeout_ms)).await; send_poll_result(&pid, result, &scanner.columns); }); From 76b9ad8ce7c922af284c46961280440e28d7fd13 Mon Sep 17 00:00:00 2001 From: Anton Borisov Date: Mon, 13 Apr 2026 23:45:15 +0100 Subject: [PATCH 9/9] add missing descriptor to example --- bindings/elixir/lib/fluss/admin.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/elixir/lib/fluss/admin.ex b/bindings/elixir/lib/fluss/admin.ex index 7817b9fb..a40f2d65 100644 --- a/bindings/elixir/lib/fluss/admin.ex +++ b/bindings/elixir/lib/fluss/admin.ex @@ -23,6 +23,9 @@ defmodule Fluss.Admin do admin = Fluss.Admin.new!(conn) :ok = Fluss.Admin.create_database(admin, "my_db") + + schema = Fluss.Schema.new() |> Fluss.Schema.column("ts", :bigint) + descriptor = Fluss.TableDescriptor.new!(schema) :ok = Fluss.Admin.create_table(admin, "my_db", "events", descriptor) """