Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:ecto]
]
22 changes: 5 additions & 17 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,14 @@ cache:
- _build
matrix:
include:
- elixir: 1.11.1
otp_release: 23.0
env: MIX_EXS=ci/mix-ecto-3.5.exs
- elixir: 1.8.0
otp_release: 21.2
env: MIX_EXS=ci/mix-ecto-3.0.exs
env: MIX_EXS=ci/mix-ecto-3.5.exs
- elixir: 1.7.4
otp_release: 21.0
env: MIX_EXS=ci/mix-ecto-3.0.exs
- elixir: 1.6.6
otp_release: 20.3
env: MIX_EXS=ci/mix-ecto-2.2.exs
- elixir: 1.6.6
otp_release: 20.0
env: MIX_EXS=ci/mix-ecto-2.2.exs
- elixir: 1.5.3
otp_release: 19.3
env: MIX_EXS=ci/mix-ecto-2.2.exs
- elixir: 1.5.3
otp_release: 19.0
env: MIX_EXS=ci/mix-ecto-2.1.exs
- elixir: 1.4.5
otp_release: 18.3
env: MIX_EXS=ci/mix-ecto-2.0.exs
env: MIX_EXS=ci/mix-ecto-3.2.exs
after_success:
- mix bench
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
An `Ecto.Type` implementation of [ULID](https://github.com/ulid/spec).

`Ecto.ULID` should be compatible anywhere that `Ecto.UUID` is supported. It has been confirmed to
work with PostgreSQL and MySQL on Ecto 2.x and 3.x. Ecto 1.x is *not* supported.
work with PostgreSQL and MySQL on Ecto ~> 3.2.

ULID is a 128-bit universally unique lexicographically sortable identifier. ULID is
binary-compatible with UUID, so it can be stored in a `uuid` column in a database.

## Features

* Generate ULID in Base32 or binary format.
* Generate ULID for a given timestamp.
* Autogenerate ULID when used as a primary key.
* Supports reading and writing ULID in a database backed by its native `uuid` type (no database
- Generate ULID in Base32 or binary format.
- Generate ULID for a given timestamp.
- Autogenerate ULID when used as a primary key.
- Supports reading and writing ULID in a database backed by its native `uuid` type (no database
extensions required).
* Supports Ecto 2.x and Ecto 3.x.
* Supports Elixir 1.4 and newer.
* Confirmed working on PostgreSQL and MySQL.
* Optimized for high throughput.
- Supports Ecto ~> 3.2.
- Supports Elixir 1.7 and newer.
- Confirmed working on PostgreSQL and MySQL.
- Optimized for high throughput.

## Performance

Expand Down Expand Up @@ -78,7 +78,7 @@ Alternatively, if you plan to use ULID as the primary key type for all of your t
config :my_app, MyApp.Repo, migration_primary_key: [name: :id, type: :binary_id]
```

and then you *do not* need to specify the `id` column in your migrations:
and then you _do not_ need to specify the `id` column in your migrations:

```elixir
create table(:events) do
Expand Down
26 changes: 0 additions & 26 deletions ci/mix-ecto-2.2.exs

This file was deleted.

27 changes: 0 additions & 27 deletions ci/mix-ecto-3.0.exs

This file was deleted.

6 changes: 3 additions & 3 deletions ci/mix-ecto-2.0.exs → ci/mix-ecto-3.2.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Ecto.ULID.Mixfile do
app: :ecto_ulid,
version: "0.1.1",
elixir: "~> 1.4",
start_permanent: Mix.env == :prod,
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
Expand All @@ -19,8 +19,8 @@ defmodule Ecto.ULID.Mixfile do

defp deps do
[
{:ecto, "~> 2.0.0"},
{:benchfella, "~> 0.3.5", only: [:dev, :test]},
{:ecto, "3.2.5"},
{:benchfella, "~> 0.3.5", only: [:dev, :test]}
]
end
end
6 changes: 3 additions & 3 deletions ci/mix-ecto-2.1.exs → ci/mix-ecto-3.5.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Ecto.ULID.Mixfile do
app: :ecto_ulid,
version: "0.1.1",
elixir: "~> 1.4",
start_permanent: Mix.env == :prod,
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
Expand All @@ -19,8 +19,8 @@ defmodule Ecto.ULID.Mixfile do

defp deps do
[
{:ecto, "~> 2.1.0"},
{:benchfella, "~> 0.3.5", only: [:dev, :test]},
{:ecto, "~> 3.5.0"},
{:benchfella, "~> 0.3.5", only: [:dev, :test]}
]
end
end
53 changes: 38 additions & 15 deletions lib/ecto/ulid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ defmodule Ecto.ULID do
An Ecto type for ULID strings.
"""

@behaviour Ecto.Type
use Ecto.Type

@typedoc """
A hex-encoded ULID string.
"""
@type t :: <<_::208>>

@doc """
The underlying schema type.
Expand All @@ -20,6 +25,7 @@ defmodule Ecto.ULID do
:error
end
end

def cast(_), do: :error

@doc """
Expand Down Expand Up @@ -76,15 +82,20 @@ defmodule Ecto.ULID do
<<timestamp::unsigned-size(48), :crypto.strong_rand_bytes(10)::binary>>
end

defp encode(<< b1::3, b2::5, b3::5, b4::5, b5::5, b6::5, b7::5, b8::5, b9::5, b10::5, b11::5, b12::5, b13::5,
b14::5, b15::5, b16::5, b17::5, b18::5, b19::5, b20::5, b21::5, b22::5, b23::5, b24::5, b25::5, b26::5>>) do
<<e(b1), e(b2), e(b3), e(b4), e(b5), e(b6), e(b7), e(b8), e(b9), e(b10), e(b11), e(b12), e(b13),
e(b14), e(b15), e(b16), e(b17), e(b18), e(b19), e(b20), e(b21), e(b22), e(b23), e(b24), e(b25), e(b26)>>
defp encode(
<<b1::3, b2::5, b3::5, b4::5, b5::5, b6::5, b7::5, b8::5, b9::5, b10::5, b11::5, b12::5,
b13::5, b14::5, b15::5, b16::5, b17::5, b18::5, b19::5, b20::5, b21::5, b22::5, b23::5,
b24::5, b25::5, b26::5>>
) do
<<e(b1), e(b2), e(b3), e(b4), e(b5), e(b6), e(b7), e(b8), e(b9), e(b10), e(b11), e(b12),
e(b13), e(b14), e(b15), e(b16), e(b17), e(b18), e(b19), e(b20), e(b21), e(b22), e(b23),
e(b24), e(b25), e(b26)>>
catch
:error -> :error
else
encoded -> {:ok, encoded}
end

defp encode(_), do: :error

@compile {:inline, e: 1}
Expand Down Expand Up @@ -122,15 +133,21 @@ defmodule Ecto.ULID do
defp e(30), do: ?Y
defp e(31), do: ?Z

defp decode(<< c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, c9::8, c10::8, c11::8, c12::8, c13::8,
c14::8, c15::8, c16::8, c17::8, c18::8, c19::8, c20::8, c21::8, c22::8, c23::8, c24::8, c25::8, c26::8>>) do
<< d(c1)::3, d(c2)::5, d(c3)::5, d(c4)::5, d(c5)::5, d(c6)::5, d(c7)::5, d(c8)::5, d(c9)::5, d(c10)::5, d(c11)::5, d(c12)::5, d(c13)::5,
d(c14)::5, d(c15)::5, d(c16)::5, d(c17)::5, d(c18)::5, d(c19)::5, d(c20)::5, d(c21)::5, d(c22)::5, d(c23)::5, d(c24)::5, d(c25)::5, d(c26)::5>>
defp decode(
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, c9::8, c10::8, c11::8, c12::8,
c13::8, c14::8, c15::8, c16::8, c17::8, c18::8, c19::8, c20::8, c21::8, c22::8, c23::8,
c24::8, c25::8, c26::8>>
) do
<<d(c1)::3, d(c2)::5, d(c3)::5, d(c4)::5, d(c5)::5, d(c6)::5, d(c7)::5, d(c8)::5, d(c9)::5,
d(c10)::5, d(c11)::5, d(c12)::5, d(c13)::5, d(c14)::5, d(c15)::5, d(c16)::5, d(c17)::5,
d(c18)::5, d(c19)::5, d(c20)::5, d(c21)::5, d(c22)::5, d(c23)::5, d(c24)::5, d(c25)::5,
d(c26)::5>>
catch
:error -> :error
else
decoded -> {:ok, decoded}
end

defp decode(_), do: :error

@compile {:inline, d: 1}
Expand Down Expand Up @@ -167,13 +184,19 @@ defmodule Ecto.ULID do
defp d(?X), do: 29
defp d(?Y), do: 30
defp d(?Z), do: 31
defp d(_), do: throw :error

defp valid?(<< c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, c9::8, c10::8, c11::8, c12::8, c13::8,
c14::8, c15::8, c16::8, c17::8, c18::8, c19::8, c20::8, c21::8, c22::8, c23::8, c24::8, c25::8, c26::8>>) do
v(c1) && v(c2) && v(c3) && v(c4) && v(c5) && v(c6) && v(c7) && v(c8) && v(c9) && v(c10) && v(c11) && v(c12) && v(c13) &&
v(c14) && v(c15) && v(c16) && v(c17) && v(c18) && v(c19) && v(c20) && v(c21) && v(c22) && v(c23) && v(c24) && v(c25) && v(c26)
defp d(_), do: throw(:error)

defp valid?(
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, c9::8, c10::8, c11::8, c12::8,
c13::8, c14::8, c15::8, c16::8, c17::8, c18::8, c19::8, c20::8, c21::8, c22::8, c23::8,
c24::8, c25::8, c26::8>>
) do
v(c1) && v(c2) && v(c3) && v(c4) && v(c5) && v(c6) && v(c7) && v(c8) && v(c9) && v(c10) &&
v(c11) && v(c12) && v(c13) &&
v(c14) && v(c15) && v(c16) && v(c17) && v(c18) && v(c19) && v(c20) && v(c21) && v(c22) &&
v(c23) && v(c24) && v(c25) && v(c26)
end

defp valid?(_), do: false

@compile {:inline, v: 1}
Expand Down
12 changes: 6 additions & 6 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ defmodule Ecto.ULID.Mixfile do
[
app: :ecto_ulid,
version: "0.2.0",
elixir: "~> 1.4",
start_permanent: Mix.env == :prod,
elixir: "~> 1.7",
start_permanent: Mix.env() == :prod,
deps: deps(),
name: "Ecto.ULID",
description: "An Ecto.Type implementation of ULID.",
package: package(),
source_url: "https://github.com/TheRealReal/ecto-ulid",
homepage_url: "https://github.com/TheRealReal/ecto-ulid",
docs: [main: "Ecto.ULID"],
docs: [main: "Ecto.ULID"]
]
end

Expand All @@ -27,15 +27,15 @@ defmodule Ecto.ULID.Mixfile do
[
maintainers: ["David Cuddeback"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/TheRealReal/ecto-ulid"},
links: %{"GitHub" => "https://github.com/TheRealReal/ecto-ulid"}
]
end

defp deps do
[
{:ecto, "~> 2.0 or ~> 3.0"},
{:ecto, "~> 3.2"},
{:benchfella, "~> 0.3.5", only: [:dev, :test]},
{:ex_doc, "~> 0.16", only: :dev, runtime: false},
{:ex_doc, "~> 0.16", only: :dev, runtime: false}
]
end
end
2 changes: 1 addition & 1 deletion test/ecto/ulid_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule Ecto.ULIDTest do

test "generate/0 encodes milliseconds in first 10 characters" do
# test case from ULID README: https://github.com/ulid/javascript#seed-time
<<encoded::bytes-size(10), _rest::bytes-size(16)>> = Ecto.ULID.generate(1469918176385)
<<encoded::bytes-size(10), _rest::bytes-size(16)>> = Ecto.ULID.generate(1_469_918_176_385)

assert encoded == "01ARYZ6S41"
end
Expand Down