Packages
ex_aliyun_ots
0.1.3
0.15.3
0.15.2
0.15.1
0.15.0
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.0
0.8.0
0.7.1
0.7.0
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Aliyun TableStore SDK for Elixir/Erlang
Current section
Files
Jump to
Current section
Files
ex_aliyun_ots
README.md
README.md
# ExAliyunOts
Aliyun TableStore SDK for Elixir/Erlang
## Installation
```elixir
def deps do
[
{:ex_aliyun_ots, "~> 0.1.0"}
]
end
```
## Configuration
Add `YOUR` tablestore instance(s) configuration into `config/config.exs`.
```elixir
config :ex_aliyun_ots,
instances: %{
"YOUR_INSTANCE_NAME" => %{
endpoint: "YOUR_INSTANCE_ENDPOINT",
access_key_id: "YOUR_ACCESS_KEY_ID",
access_key_secret: "YOUR_ACCESS_KEY_SECRET",
},
},
clients_pool: [size: 100, max_overflow: 20]
```
## Supported API
* [CreateTable](#CreateTable)
* [ListTable](#ListTable)
* [DeleteTable](#DeleteTable)
* [UpdateTable](#UpdateTable)
* [DescribeTable](#DescribeTable)
* [PutRow](#PutRow)
* [GetRow](#GetRow)
* [UpdateRow](#UpdateRow)
* [DeleteRow](#DeleteRow)
* [GetRange](#GetRange)
* [BatchGetRow](#BatchGetRow)
* [BatchWriteRow](#BatchWriteRow)
## Other
* [IterateAllRange](#IterateAllRange)
* [Sequence](#Sequence)
## Operation
添加 `use ExAliyunOts.Mixin` 在任意`Elixir`模块的定义当中
### Sample
*更多参考请见测试用例(在 [test](https://github.com/xinz/ex_aliyun_ots/tree/master/test) 目录)*
<a name="CreateTable"></a>CreateTable
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
alias ExAliyunOts.Const.PKType
require PKType
@instance "YOUR_INSTANCE_NAME"
#
# Optional settings
#
# `reserved_throughput_write`,创建的表的初始预留写吞吐量设定,默认值为0
# `reserved_throughput_read`,创建的表的初始预留读吞吐量设定,默认值为0
# `time_to_live`,保存的数据的存活时间,单位秒,默认值为"-1"(永久)
# `max_versions`,表保留的最大版本,默认值为1,只设定一个版本
# `deviation_cell_version_in_sec`,最大版本偏差,默认值为86400秒,即1天
# `stream_spec`,是否打开stream相关的属性
# [
# `is_enabled`, 是否打开stream
# `expiration_time`, 表的stream过期时间
# ]
#
def test() do
create_table @instance,
"table_name1",
[{"key1", PKType.integer}, {"key2", PKType.string}]
create_table @instance,
"table_name2",
[{"key1", PKType.string}, {"key2", PKType.auto_increment}]
create_table @instance_name,
"table_name3",
[{"key1", PKType.string}],
reserved_throughput_write: 1,
reserved_throughput_read: 1,
time_to_live: 100_000,
max_versions: 3,
deviation_cell_version_in_sec: 6_400,
stream_spec: [is_enabled: true, expiration_time: 2]
end
end
```
<a name="ListTable"></a>ListTable
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
def test() do
list_table(@instance_name)
end
end
```
<a name="DeleteTable"></a>DeleteTable
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
def test() do
delete_table @instance_name, "table_name"
end
end
```
<a name="UpdateTable"></a>UpdateTable
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# 可选项设定
#
# `reserved_throughput_write`,创建的表的初始预留写吞吐量设定,默认不设定
# `reserved_throughput_read`,创建的表的初始预留读吞吐量设定,默认不设定
# `time_to_live`,保存的数据的存活时间,单位秒,默认值为"-1"(永久)
# `max_versions`,表保留的最大版本,默认值为1,只设定一个版本
# `deviation_cell_version_in_sec`,最大版本偏差,默认值为86400秒,即1天
# `stream_spec`,是否打开stream相关的属性
# [
# `is_enabled`,是否打开stream
# `expiration_time`,表的stream过期时间
# ]
#
#
def test() do
update_table @instance_name, "table_name",
reserved_throughput_write: 10,
time_to_live: 200_000,
stream_spec: [is_enabled: false]
end
end
```
<a name="DescribeTable"></a>DescribeTable
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
def test() do
describe_table @instance_name, table_name
end
end
```
<a name="PutRow"></a>PutRow
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
def test() do
put_row @instance_name, "table_name", [{"key1", "id1"}],
[{"name", "name1"}, {"age", 20}],
condition: condition(:expect_not_exist),
return_type: :pk
end
end
```
<a name="GetRow"></a>GetRow
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# 支持按条件过滤查询(通过使用`filter`操作)
# 更便于理解的表达式, ">"、"<"、"=="、"and"、"or"、"()"
# 通过[ignore_if_missing: true]的语法,设定为true时,表示当匹配条件不满足时进行忽略该匹配逻辑;设定为false时,表示该匹配条件必须满足,否则查询结果将返回没有可匹配的结果
# 通过[latest_version_only: true]的语法,如果为true,则表示只检测最新版本的值是否满足条件;如果是false,则会检测所有版本的值是否满足条件
#
# 可通过`filter`支持属性列上的分页读取操作(通过使用`pagination`操作),主要用于宽行读取
#
# 可选设定项
# `columns_to_get`,指定获取的属性列
# `start_column`,指定读取时的起始列,主要用于宽行读,返回的结果中包含当前起始列
# `end_column`,指定读取时的结束列,主要用于宽行读,返回的结果中不包含当前结束列
# `filter`,支持按条件过滤查询(通过使用`filter`操作)
# `max_versions`,和time_range只能存在一个,读取数据时,返回的最多版本个数,默认值为1
# `time_range`,和max_versions只能存在一个,读取数据的版本时间戳范围,支持2种定义方式,如:
# time_range: {start_timestamp, end_timestamp}
# time_range: specail_timestamp
#
#
def test() do
get_row @instance_name, table_name1,
[{"key1", "id1"}, {"key2", "id2"}],
columns_to_get: ["name", "level"],
filter: filter(("name[ignore_if_missing: true, latest_version_only: true]" == var_name and "age" > 1) or ("class" == "1"))
get_row @instance_name, table_name,
[{"key", "1"}],
start_column: "room",
filter: pagination(offset: 0, limit: 3)
end
end
```
<a name="UpdateRow"></a>UpdateRow
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# `put`,如果设定该操作,此时value必须为有效的属性列值。语意为如果该列不存在,则新增一列;如果该列存在,则覆盖该字段列。
# `delete`,如果设定该操作,值必须设定为nil,同时需要指定该列一值的timestamp
# `delete_all`,如果设定该操作,只需要设定需要删除操作的列值列表
# `return_type`,支持`:pk`返回主键信息,`:none`不返回任何信息
# `condition`,在数据更新前是否进行存在性检查
# `:expect_exist`,表示期望行存在
# `:ignore`,表示不做行存在性检查
# 同时支持在condition()进行条件查询,见`filter`操作
#
#
def test() do
value = "value1"
update_row @instance_name, table_name1,
[{"key1", 2}, {"key2", "2"}],
delete: [{"attr2", nil, 1524464460}],
delete_all: ["attr1"],
put: [{"attr3", "put_attr3"}],
return_type: :pk,
condition: condition(:expect_exist, "attr2" == value)
end
end
```
<a name="DeleteRow"></a>DeleteRow
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# 删除行操作支持按条件筛选,通过condition()
#
#
def test() do
delete_row @instance_name, table_name1,
[{"key1", 3}, {"key2", "3"}],
condition: condition(:expect_exist, "attr2" == "value2")
end
end
```
<a name="GetRange"></a>GetRange
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
alias ExAliyunOts.Const.PKType
require PKType
@instance "YOUR_INSTANCE_NAME"
#
#
# 读取指定主键范围内的数据
# `direction`,查询数据的顺序,默认值为forward
# 设定为`:forward`,表示此次查询按照主键由小到大的顺序进行
# 设定为`:backward`,表示此次查询按照主键由大到小的顺序进行
#
#
# 可选设定项
# `columns_to_get`,指定获取的属性列
# `start_column`,指定读取时的起始列,主要用于宽行读,返回的结果中包含当前起始列
# `end_column`,指定读取时的结束列,主要用于宽行读,返回的结果中不包含当前结束列
# `filter`,支持按条件过滤查询(通过使用`filter`操作)
# `max_versions`,和time_range只能存在一个,读取数据时,返回的最多版本个数,默认值为1
# `time_range`,和max_versions只能存在一个,读取数据的版本时间戳范围,支持2种定义方式,如:
# time_range: {start_timestamp, end_timestamp}
# time_range: specail_timestamp
#
#
def test() do
#
# [{"key1", 1}, {"key2", PKType.inf_min}] 作为 inclusive_start_primary_key
# [{"key1", 4}, {"key2", PKType.inf_max}] 作为 exclusive_end_primary_keys
#
get_range @instance_name, "table_name",
[{"key1", 1}, {"key2", PKType.inf_min}],
[{"key1", 4}, {"key2", PKType.inf_max}],
direction: :forward
get_range @instance_name, table_name1,
[{"key1", 1}, {"key2", PKType.inf_min}],
[{"key1", 4}, {"key2", PKType.inf_max}],
time_range: {1525922253224, 1525923253224},
direction: :forward
get_range @instance_name, table_name1,
[{"key1", 1}, {"key2", PKType.inf_min}],
[{"key1", 4}, {"key2", PKType.inf_max}],
time_range: 1525942123224,
direction: :forward
end
end
```
<a name="BatchGetRow"></a>BatchGetRow
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# 批量读取一个或多个表中的若干行数据
# 通过多个get()查询多个表中的记录,get()中的支持的选项与`GetRow`一致
#
#
def test() do
batch_get @instance_name, [
get(table_name1, [[{"key1", 1}, {"key2", "1"}]]),
get(table_name2, [{"key1", "tab2_id1"}],
columns_to_get: ["name", "age"],
filter: filter "age" >= 10),
]
end
```
<a name="BatchWriteRow"></a>BatchWriteRow
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# 批量插入、修改或删除一个或多个表中的若干行数据,为多个 PutRow、UpdateRow、DeleteRow 操作的集合
# 通过write_delete()、write_put()、write_update()进行更新操作,支持的选项可参考`UpdateRow`
#
#
def test() do
batch_write @instance_name, [{table_name1, [
write_delete([{"key1", 5}, {"key2", "5"}],
return_type: :pk,
condition: condition(:expect_exist, "attr1" == 5)),
write_put([{"key1", 6}, {"key2", "6"}],
[{"new_put_val1", "val1"}, {"new_put_val2", "val2"}],
condition: condition(:expect_not_exist),
return_type: :pk)
]}, {table_name2, [
write_update([{"key1", "new_tab3_id2"}],
put: [{"new_put1", "u1"}, {"new_put2", 2.5}],
condition: condition(:expect_not_exist)),
write_put([{"key1", "new_tab3_id3"}],
[{"new_put1", "put1"}, {"new_put2", 10}],
condition: condition(:expect_not_exist))
]}]
end
end
```
<a name="IterateAllRange"></a>IterateAllRange
```elixir
defmodule CRUDSample do
use ExAliyunOts.Mixin
@instance "YOUR_INSTANCE_NAME"
#
#
# 读取指定主键范围内的数据,当如果有大量数据时,可通过该方法读取完整的数据
# 该方法是对`GetRange`的包装处理后的接口,相关配置项与`GetRange`一致
#
#
def test() do
batch_get @instance_name, [
get(table_name1, [[{"key1", 1}, {"key2", "1"}]]),
get(table_name2, [{"key1", "tab2_id1"}],
columns_to_get: ["name", "age"],
filter: filter "age" >= 10),
]
end
```
<a name="Sequence"></a>Sequence
```elixir
defmodule SequenceSample do
use ExUnit.Case
require Logger
@instance_name "YOUR_INSTANCE_NAME"
alias ExAliyunOts.Var
alias ExAliyunOts.Sequence
#
#
# 基于现有的表格存储支持程度,在表操作层面上提供一种方式可以获取到唯一自增步长序列
# Sequence依旧是对表记录的操作,但它会提供更可靠安全的原子操作粒度
# 创建一张Sequence表允许有多个event,表明所使用序列表的场景,event也将用于表的分区键
#
# `GetSequenceNextValue`可选参数
# `starter`,首次调用next_value()初始创建的值,默认值为0
# `increment_offset`,每次调用next_value()自增步长,默认值为1
# `event`,使用序列表的场景,默认值为"default"
#
#
test "next value" do
cur_timestamp = Timex.to_unix(Timex.now())
sequence_name = "test_seq_#{cur_timestamp}"
var_new_seq = %Var.NewSequence{
name: sequence_name
}
# 创建Sequence
result = Sequence.create(@instance_name, var_new_seq)
assert result == :ok
Process.sleep(3_000)
# 并发测试
concurrency_size = 200
stream = Task.async_stream(1..concurrency_size, fn(_index) ->
var_next_val = %Var.GetSequenceNextValue{
name: sequence_name,
}
Sequence.next_value(@instance_name, var_next_val)
end, timeout: :infinity, max_concurrency: concurrency_size)
result = Enum.map(stream, fn({:ok, item}) -> item end)
assert length(result) == concurrency_size
distinct_length = MapSet.new(result) |> MapSet.to_list |> length
assert distinct_length == concurrency_size
assert Enum.sort(result) == Enum.map(1..concurrency_size, fn(item) -> item end)
# 删除"default" event
del_result = Sequence.delete_event(@instance_name, sequence_name, "default")
assert {:ok, _delete_response} = del_result
# 删除整个序列表
result = Sequence.delete(@instance_name, sequence_name)
assert result == :ok
end
end
```
## License
MIT