You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fluss.apache.org by ab...@apache.org on 2026/10/14 07:52:18 UTC

(fluss) branch main updated: [elixir] Set up HexDocs and add usage guides (#4633)

This is an automated email from the ASF dual-hosted git repository.

fresh-borzoni pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new 6af19e6fb1 [elixir] Set up HexDocs and add usage guides (#4633)
6af19e6fb1 is described below

commit 6af19e6fb195ca3bbb7deeef35ad4c82bc5fc1da
Author: Nicoleta Lazar <ni...@fresha.com>
AuthorDate: Wed Oct 14 08:52:13 2026 +0100

    [elixir] Set up HexDocs and add usage guides (#4633)
    
    Configure ExDoc for the Elixir client and add the first guides.
    
    - mix.exs: README as the landing page, modules grouped by area, and
      source links into the monorepo path.
    - Guides: log tables (create, write, and a GenServer consumer) and data
      types (Fluss type, schema atom and Elixir value, with conversions to
      standard library types).
    - Fluss.Admin: document that create/drop calls ignore an existing or
      missing database or table by default, and that drop_database doesn't
      cascade.
    - Ignore the generated doc/ directory.
---
 fluss-rust/bindings/elixir/.gitignore           |  1 +
 fluss-rust/bindings/elixir/guides/data_types.md | 46 +++++++++++++++
 fluss-rust/bindings/elixir/guides/log_tables.md | 76 +++++++++++++++++++++++++
 fluss-rust/bindings/elixir/lib/fluss/admin.ex   |  4 ++
 fluss-rust/bindings/elixir/mix.exs              | 29 ++++++++++
 5 files changed, 156 insertions(+)

diff --git a/fluss-rust/bindings/elixir/.gitignore b/fluss-rust/bindings/elixir/.gitignore
index dd00538b13..64d2fd4e94 100644
--- a/fluss-rust/bindings/elixir/.gitignore
+++ b/fluss-rust/bindings/elixir/.gitignore
@@ -1,6 +1,7 @@
 # Elixir build artifacts
 _build/
 deps/
+doc/
 
 # Generated NIF shared library
 priv/native/
diff --git a/fluss-rust/bindings/elixir/guides/data_types.md b/fluss-rust/bindings/elixir/guides/data_types.md
new file mode 100644
index 0000000000..2822de6652
--- /dev/null
+++ b/fluss-rust/bindings/elixir/guides/data_types.md
@@ -0,0 +1,46 @@
+# Data types
+
+How each Fluss type is declared in a `Fluss.Schema` and represented as an Elixir value. Values
+are the same for writing and reading.
+
+| Fluss type | Schema | Elixir value |
+|---|---|---|
+| BOOLEAN | `:boolean` | `true` / `false` |
+| TINYINT, SMALLINT, INT, BIGINT | `:tinyint`, `:smallint`, `:int`, `:bigint` | integer, range-checked on write |
+| FLOAT, DOUBLE | `:float`, `:double` | float |
+| STRING | `:string` | `String.t()` |
+| CHAR(n) | `{:char, n}` | `String.t()` |
+| DECIMAL(p, s) | `{:decimal, p, s}` | string, e.g. `"12.34"`; rounded half-up to `s` on write |
+| BYTES | `:bytes` | binary |
+| BINARY(n) | `{:binary, n}` | binary |
+| DATE | `:date` | integer: days since 1970-01-01 |
+| TIME | `:time` | integer: milliseconds since midnight |
+| TIMESTAMP | `:timestamp` | `{epoch_millis, nanos_of_milli}`, no time zone |
+| TIMESTAMP_LTZ | `:timestamp_ltz` | `{epoch_millis, nanos_of_milli}`, a UTC instant |
+| ARRAY | `{:array, type}` | list |
+| MAP | `{:map, key_type, value_type}` | map |
+| ROW | `{:row, [{"field", type}]}` | map keyed by field-name atoms |
+
+Converting to standard library types:
+
+```elixir
+date = Date.add(~D[1970-01-01], days)
+time = Time.add(~T[00:00:00], millis_of_day, :millisecond)
+
+{millis, nanos} = timestamp
+utc = DateTime.add(DateTime.from_unix!(millis, :millisecond), nanos, :nanosecond)  # :timestamp_ltz
+naive = DateTime.to_naive(utc)                                                   # :timestamp
+```
+
+## Nullability
+
+`nil` is null; `{:not_null, type}` forbids it (see `Fluss.Schema`). Primary key columns and map
+keys are always `NOT NULL`: `Fluss.Admin.get_table_schema/4` returns them wrapped even if
+declared bare.
+
+## Not supported yet
+
+- TIME and TIMESTAMP precision can't be declared; `:time` is TIME(0) and `:timestamp` /
+  `:timestamp_ltz` are precision 6.
+- A FLOAT or DOUBLE holding NaN or ±Infinity (writable from other clients) can't be read:
+  Erlang has no such values.
diff --git a/fluss-rust/bindings/elixir/guides/log_tables.md b/fluss-rust/bindings/elixir/guides/log_tables.md
new file mode 100644
index 0000000000..110ad02811
--- /dev/null
+++ b/fluss-rust/bindings/elixir/guides/log_tables.md
@@ -0,0 +1,76 @@
+# Log tables
+
+A log table has no primary key: records are appended and never change.
+
+## Create
+
+```elixir
+schema =
+  Fluss.Schema.new()
+  |> Fluss.Schema.column("ts", :bigint)
+  |> Fluss.Schema.column("message", :string)
+
+descriptor = Fluss.TableDescriptor.new!(schema, bucket_count: 3)
+:ok = Fluss.Admin.create_table(admin, "my_db", "events", descriptor)
+
+table = Fluss.Table.get!(conn, "my_db", "events")
+```
+
+## Write
+
+Values are a list in column order; `nil` is null. See `Fluss.AppendWriter` for per-record acks.
+
+```elixir
+writer = Fluss.AppendWriter.new!(table)
+Fluss.AppendWriter.append(writer, [1_700_000_000, "hello"])
+:ok = Fluss.AppendWriter.flush(writer)
+```
+
+## Read
+
+A scanner subscribes to buckets, each from a starting offset; `Fluss.LogScanner.poll/2`
+delivers records as messages. `{:fluss_records, []}` means nothing arrived before the timeout,
+and `:change_type` is always `:append_only` for log tables.
+
+A GenServer that reads every bucket from the start:
+
+```elixir
+defmodule MyApp.EventsConsumer do
+  use GenServer
+
+  def start_link({admin, table}), do: GenServer.start_link(__MODULE__, {admin, table})
+
+  @impl true
+  def init({admin, table}) do
+    info = Fluss.Admin.get_table_info!(admin, "my_db", "events")
+    scanner = Fluss.LogScanner.new!(table)
+    buckets = for b <- 0..(info.num_buckets - 1), do: {b, Fluss.earliest_offset()}
+    :ok = Fluss.LogScanner.subscribe_buckets(scanner, buckets)
+    send(self(), :poll)
+    {:ok, scanner}
+  end
+
+  @impl true
+  def handle_info(:poll, scanner) do
+    :ok = Fluss.LogScanner.poll(scanner, 1_000)
+    {:noreply, scanner}
+  end
+
+  def handle_info({:fluss_records, records}, scanner) do
+    Enum.each(records, &IO.inspect(&1.row))
+    send(self(), :poll)
+    {:noreply, scanner}
+  end
+
+  def handle_info({:fluss_poll_error, error}, scanner) do
+    IO.warn("poll failed: #{Exception.message(error)}")
+    send(self(), :poll)
+    {:noreply, scanner}
+  end
+end
+```
+
+Poll messages don't say which scanner they came from, so give each scanner its own process,
+and poll again only after the previous result arrives.
+
+Partitioned log tables can't be scanned yet.
diff --git a/fluss-rust/bindings/elixir/lib/fluss/admin.ex b/fluss-rust/bindings/elixir/lib/fluss/admin.ex
index 11bc2ce8e6..82d6276c9e 100644
--- a/fluss-rust/bindings/elixir/lib/fluss/admin.ex
+++ b/fluss-rust/bindings/elixir/lib/fluss/admin.ex
@@ -19,6 +19,10 @@ defmodule Fluss.Admin do
   @moduledoc """
   Admin client for DDL operations (create/drop databases and tables).
 
+  `create_*` and `drop_*` default to ignoring an existing or missing database or table, and
+  return `:ok`; pass `false` as the last argument to get an error instead. `drop_database/3`
+  doesn't cascade: drop the tables first.
+
   ## Examples
 
       admin = Fluss.Admin.new!(conn)
diff --git a/fluss-rust/bindings/elixir/mix.exs b/fluss-rust/bindings/elixir/mix.exs
index cf22f29156..03f9361baf 100644
--- a/fluss-rust/bindings/elixir/mix.exs
+++ b/fluss-rust/bindings/elixir/mix.exs
@@ -19,6 +19,7 @@ defmodule Fluss.MixProject do
   use Mix.Project
 
   @version "1.0.0"
+  @source_url "https://github.com/apache/fluss"
 
   def project do
     [
@@ -30,6 +31,10 @@ defmodule Fluss.MixProject do
       aliases: aliases(),
       deps: deps(),
       description: "Elixir client for Apache Fluss",
+      name: "Fluss",
+      source_url: @source_url,
+      homepage_url: "https://fluss.apache.org",
+      docs: docs(),
       dialyzer: [
         plt_add_apps: [:ex_unit, :mix],
         plt_file: {:no_warn, "priv/plts/dialyzer.plt"},
@@ -72,6 +77,30 @@ defmodule Fluss.MixProject do
     ]
   end
 
+  defp docs do
+    [
+      main: "readme",
+      extras: ["README.md", "guides/log_tables.md", "guides/data_types.md"],
+      groups_for_extras: [Guides: ~r{guides/}],
+      # The binding lives in a monorepo subdirectory.
+      source_url_pattern: "#{@source_url}/blob/main/fluss-rust/bindings/elixir/%{path}#L%{line}",
+      groups_for_modules: [
+        "Connection & Configuration": [Fluss, Fluss.Config, Fluss.Connection, Fluss.Error],
+        Admin: [
+          Fluss.Admin,
+          Fluss.DatabaseDescriptor,
+          Fluss.DatabaseInfo,
+          Fluss.Schema,
+          Fluss.SchemaInfo,
+          Fluss.ServerNode,
+          Fluss.TableDescriptor,
+          Fluss.TableInfo
+        ],
+        "Log Tables": [Fluss.Table, Fluss.AppendWriter, Fluss.WriteHandle, Fluss.LogScanner]
+      ]
+    ]
+  end
+
   defp package do
     [
       licenses: ["Apache-2.0"],