Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ header:
- 'NOTICE'
- 'DISCLAIMER'
- 'bindings/python/fluss/py.typed'
- '**/mix.lock'
- 'website/**'
- '**/*.md'
- '**/DEPENDENCIES.*.tsv'
Expand Down
60 changes: 60 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
20 changes: 20 additions & 0 deletions bindings/elixir/.formatter.exs
Original file line number Diff line number Diff line change
@@ -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}"]
]
9 changes: 9 additions & 0 deletions bindings/elixir/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Elixir build artifacts
_build/
deps/

# Generated NIF shared library
priv/native/

# Crash dumps
erl_crash.dump
60 changes: 60 additions & 0 deletions bindings/elixir/README.md
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions bindings/elixir/lib/fluss.ex
Original file line number Diff line number Diff line change
@@ -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
98 changes: 98 additions & 0 deletions bindings/elixir/lib/fluss/admin.ex
Original file line number Diff line number Diff line change
@@ -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()}
Comment thread
fresh-borzoni marked this conversation as resolved.
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
Loading
Loading