{"id":15010108,"url":"https://github.com/edragonconnect/otto","last_synced_at":"2025-04-09T18:05:57.969Z","repository":{"id":62430223,"uuid":"177937571","full_name":"edragonconnect/otto","owner":"edragonconnect","description":"A simple wrapper for ex_aliyun_ots, works well with Ecto","archived":false,"fork":false,"pushed_at":"2019-09-16T11:11:02.000Z","size":29,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-18T19:51:54.013Z","etag":null,"topics":["aliyun-ots","aliyun-tablestore-sdk","elixir","tablestore"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/otto","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edragonconnect.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-27T07:07:01.000Z","updated_at":"2022-04-07T07:42:23.000Z","dependencies_parsed_at":"2022-11-01T20:17:48.894Z","dependency_job_id":null,"html_url":"https://github.com/edragonconnect/otto","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edragonconnect%2Fotto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edragonconnect%2Fotto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edragonconnect%2Fotto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edragonconnect%2Fotto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edragonconnect","download_url":"https://codeload.github.com/edragonconnect/otto/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248084293,"owners_count":21045124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aliyun-ots","aliyun-tablestore-sdk","elixir","tablestore"],"created_at":"2024-09-24T19:30:17.866Z","updated_at":"2025-04-09T18:05:57.931Z","avatar_url":"https://github.com/edragonconnect.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Otto\n\n## NOTICE:\n\nThis project is no longer maintained, if you are seeking for a replacement of this Ecto adapter, we would like to recommend [`ecto_tablestore`](https://hex.pm/packages/ecto_tablestore) for your reference.\n\n## Introduce\n\nOtto is an easy-to-use wrapper for accessing [Aliyun Table Store(OTS)](https://www.aliyun.com/product/ots), a distributed NoSQL database. It is based on package [`ex_aliyun_ots`](https://github.com/xinz/ex_aliyun_ots). It works well with `ecto`, which means you can define struct and field types in ecto, then otto will handle it.\n\nUsing otto, you can:\n* Easily create and update ots table.\n* CURD in an ecto-like way.\n* Encrypt fields if neccessary, using `AES` encryption algorithm.\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `otto` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:otto, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\n## Configuration\n\n#### Add ex_aliyun_ots configuration.\n\nOtto depends on `ex_aliyun_ots`, so you should add config for it first, without this, your app cannot run.\n```elixir\nconfig :ex_aliyun_ots, instances: [Instance1, Instance2]\n\nconfig :ex_aliyun_ots, Instance1,\n  name: \"instance1_name\",\n  endpoint: \"YOUR-OTS-ENDPOINT\",\n  access_key_id: \"YOUR-ACCESS-KEY-ID\",\n  access_key_secret: \"YOUR-ACCESS-KEY-SECRET\"\n\nconfig :ex_aliyun_ots, Instance2,\n  name: \"instance2_name\",\n  endpoint: \"YOUR-OTS-ENDPOINT\",\n  access_key_id: \"YOUR-ACCESS-KEY-ID\",\n  access_key_secret: \"YOUR-ACCESS-KEY-SECRET\"\n```\n#### Add otto configuration.\n\nHere is an example of otto configuration.\n\n`:ciphers` is a keyword list of cipher configs. A cipher contains three parts:\n* tag: such as `aes_gcm_v2`, `aes_gcm_v1` in the config. It is the key of each cipher. Tag will be used to get key.\n* module: the real aes algorithm you use. Now we implemented AES-GCM and AES-CDR. You can also define your own, but it must implement behaviour `Otto.Cipher`.\n* key: the key to use when encrypting and decrypting in aes. You can generate a key by `Otto.Cipher.generate_key/0`, or run `32 |\u003e :crypto.strong_rand_bytes() |\u003e Base.encode64()`.\n\nWhen a new row need encryption, it will use the first cipher in the list. An `iv` is generated for each encryption, iv is similar to salt. We will add `__aes_iv__` and `__aes_tag__` in your ots row. When updating or decrypting the row, we will use the same iv and tag.\n\n```elixir\nconfig :otto,\n  ciphers: [\n    aes_gcm_v2: [\n      module: Otto.Cipher.AES.GCM,\n      key: \"2DR+mrNKNv3bGsQA2VnvTy8WrUwtNiO28/VXgWwAYEE=\" |\u003e Base.decode64!()\n    ],\n    aes_gcm_v1: [\n      module: Otto.Cipher.AES.GCM,\n      key: \"QLHEOuMbWAQVkfe3u14gNOZYajKOgz0q0mB7cyjdBTo=\" |\u003e Base.decode64!()\n    ]\n  ]\n```\n\n## Usage\n\n#### Define a Table\nYou can define a table using `Otto.Table` with some options, could look like this:\n```elixir\ndefmodule DemoTableCreate do\n  use Otto.Table,\n    instance: Instance1,\n    table: \"test_table\",\n    primary: [:pk1, :pk2],\n    encrypt: [:enc1, :enc2],\n    reserved_throughput_read: 10,\n    index: [\n      index_name1: [field_name1: :long, field_name2: :text],\n      index_name2: [field_name1: :keyword, field_name2: :text],\n    ]\nend\n```\nrequired fields:\n* `table`: the ots table name, it should be unique in one instance.\n* `primary`: the primary keys atom list.\n\noptional fields:\n* `encrypt`: fields to encrypt, encrypt should not be in primary.\n* `index`: search index information, one table can have multiple indexes.\n* `reserved_throughput_write`: integer, table write performance data.\n* `reserved_throughput_read`: integer, table read performance data.\n* `time_to_live`: integer, live seconds of the table data stored.\n* `max_versions`: integer, max versions of table.\n* `deviation_cell_version_in_sec`: integer.\n* `stream_spec`: keyword list, define stream specs of the table, such as [enable_stream: true, expiration_time: 9999999999999]\n\nWith the configuration, table \"test_table\" will be created by `Otto.Table.create_table(DemoTableCreate)`, then you can get a function `__ots__/0` with the instance name and all the metadata defined in options. And `__ots__/1` with some useful functions.\n\n###### Attention\n`@behaviour Otto.Table` is already added when using Otto.Table, you need to implement the two callbacks in your table module.\n```elixir\n@callback __schema__(:type, field) :: atom()\n@callback __schema__(:fields) :: list(atom)\n```\nBut if you use Ecto.Schema, it already did it.\n\n#### Do CURD with Otto.Query\nOtto.Query has two macro called `filter` and `condition`, which can be used when using get_row or get_range. So if you use the filter, you'd better import Otto.Query.\n\nHere is a sample:\n```elixir\ndefmodule DemoTable do\n  use Otto.Table,\n    instance: Instance1,\n    table: \"demo\",\n    primary: [:pk1, :pk2],\n    attrs: [:attr1, :attr2, :attr3, :attr4, :attr5, :attr6]\n    encrypt: [:attr2, :attr4, :attr6]\n\n  use Ecto.Schema\n  import Ecto.Changeset\n  import Otto.Query\n  alias DemoTable\n\n  schema \"test_query\" do\n    field(:pk1, :string)\n    field(:pk2, :integer)\n\n    field(:attr1, :string)\n    field(:attr2, :integer)\n    field(:attr3, :string)\n    field(:attr4, :map)\n    field(:attr5, :float)\n    field(:attr6, :boolean)\n  end\n\n  def changeset(query_test, attrs) do\n    query_test\n    |\u003e cast(attrs, __MODULE__.__schema__(:fields))\n  end\n\n  def test do\n    attrs = %{\n      pk1: \"pk1\",\n      pk2: 3,\n      attr1: \"attr1\",\n      attr2: 2,\n      attr3: \"attr3\",\n      attr4: %{a: 1, b: 2},\n      attr5: 1.32,\n      attr6: false\n    }\n\n    put_row_data = struct(DemoTable, attrs)\n    update_row_data = %DemoTable{\n      pk1: \"pk1\",\n      pk2: 3,\n      attr3: \"attr3_update\",\n      attr4: %{a: 3, b: 4},\n      attr6: true\n    }\n    get_row_data = %{\n      pk1: \"pk1\",\n      pk2: 3\n    }\n    get_row_data2 = %{\n      pk1: \"pk12\",\n      pk2: 3\n    }\n    get_range_data1 = %{pk1: \"pk1\", pk2: :__inf_min__}\n    get_range_data2 = %{pk1: \"pk2\", pk2: :__inf_max__}\n    get_range_data3 = %{pk1: :__inf_max__, pk2: :__inf_min__}\n    get_range_data4 = %{pk1: :__inf_min__, pk2: :__inf_max__}\n\n    put_row(put_row_data)\n    put_row(put_row_data |\u003e Map.merge(%{pk1: \"pk12\", attr2: 10}))\n    put_row(put_row_data |\u003e Map.merge(%{pk1: \"pk13\", attr2: 100}))\n\n    update_row(update_row_data)\n    update_row(update_row_data, delete_fields: [:attr2, :attr5])\n\n    get_row(DemoTable, get_row_data)\n    get_row(DemoTable, get_row_data2)\n    get_row(DemoTable, get_row_data, filter: filter(\"attr2\" == \"9\"))\n    get_row(DemoTable, %{pk1: \"pk2\", pk2: 3})\n\n    get_range(DemoTable, get_range_data1, get_range_data2)\n    get_range(DemoTable, get_range_data1, get_range_data3)\n    get_range(DemoTable, get_range_data4, get_range_data3, direction: :forward, limit: 1)\n    assert {:ok, nil} = get_range(DemoTable, get_range_data1, get_range_data4, direction: :backward)\n\n    delete_row(%DemoTable{pk1: \"pk1\", pk2: 3})\n    delete_row(%DemoTable{pk1: \"pk12\", pk2: 3})\n    delete_row(%DemoTable{pk1: \"pk13\", pk2: 3})\n  end\nend\n```\nIf the table has encrypt_fields, the encrypt fields will be stored encrypted.\n\n## Docs\nRun `mix docs`\n\nDocumentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)\nand published on [HexDocs](https://hexdocs.pm). Once published, the docs can\nbe found at [https://hexdocs.pm/ots_wrapper](https://hexdocs.pm/otto).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedragonconnect%2Fotto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedragonconnect%2Fotto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedragonconnect%2Fotto/lists"}