types.ex (8177B)
1 if Code.ensure_loaded?(Tds) do 2 defmodule Tds.Ecto.UUID do 3 @moduledoc """ 4 An TDS adapter type for UUIDs strings. 5 6 If you are using Tds adapter and UUIDs in your project, instead of `Ecto.UUID` 7 you should use Tds.Ecto.UUID to generate correct bytes that should be stored 8 in database. 9 """ 10 11 use Ecto.Type 12 13 @typedoc """ 14 A hex-encoded UUID string. 15 """ 16 @type t :: <<_::288>> 17 18 @typedoc """ 19 A raw binary representation of a UUID. 20 """ 21 @type raw :: <<_::128>> 22 23 @doc false 24 @impl true 25 def type(), do: :uuid 26 27 @doc """ 28 Casts to UUID. 29 """ 30 @impl true 31 @spec cast(t | raw | any) :: {:ok, t} | :error 32 def cast(<< a1, a2, a3, a4, a5, a6, a7, a8, ?-, 33 b1, b2, b3, b4, ?-, 34 c1, c2, c3, c4, ?-, 35 d1, d2, d3, d4, ?-, 36 e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12 >>) do 37 << c(a1), c(a2), c(a3), c(a4), c(a5), c(a6), c(a7), c(a8), ?-, 38 c(b1), c(b2), c(b3), c(b4), ?-, 39 c(c1), c(c2), c(c3), c(c4), ?-, 40 c(d1), c(d2), c(d3), c(d4), ?-, 41 c(e1), c(e2), c(e3), c(e4), c(e5), c(e6), c(e7), c(e8), c(e9), c(e10), c(e11), c(e12) >> 42 catch 43 :error -> :error 44 else 45 casted -> {:ok, casted} 46 end 47 48 def cast(<<bin::binary-size(16)>>), do: encode(bin) 49 def cast(_), do: :error 50 51 @doc """ 52 Same as `cast/1` but raises `Ecto.CastError` on invalid arguments. 53 """ 54 def cast!(value) do 55 case cast(value) do 56 {:ok, uuid} -> uuid 57 :error -> raise Ecto.CastError, type: __MODULE__, value: value 58 end 59 end 60 61 @compile {:inline, c: 1} 62 63 defp c(?0), do: ?0 64 defp c(?1), do: ?1 65 defp c(?2), do: ?2 66 defp c(?3), do: ?3 67 defp c(?4), do: ?4 68 defp c(?5), do: ?5 69 defp c(?6), do: ?6 70 defp c(?7), do: ?7 71 defp c(?8), do: ?8 72 defp c(?9), do: ?9 73 defp c(?A), do: ?a 74 defp c(?B), do: ?b 75 defp c(?C), do: ?c 76 defp c(?D), do: ?d 77 defp c(?E), do: ?e 78 defp c(?F), do: ?f 79 defp c(?a), do: ?a 80 defp c(?b), do: ?b 81 defp c(?c), do: ?c 82 defp c(?d), do: ?d 83 defp c(?e), do: ?e 84 defp c(?f), do: ?f 85 defp c(_), do: throw(:error) 86 87 @doc """ 88 Converts a string representing a UUID into a binary. 89 """ 90 @impl true 91 @spec dump(t | any) :: {:ok, raw} | :error 92 def dump(<<a1, a2, a3, a4, a5, a6, a7, a8, ?-, 93 b1, b2, b3, b4, ?-, 94 c1, c2, c3, c4, ?-, 95 d1, d2, d3, d4, ?-, 96 e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>) do 97 try do 98 << d(a7)::4, d(a8)::4, d(a5)::4, d(a6)::4, 99 d(a3)::4, d(a4)::4, d(a1)::4, d(a2)::4, 100 d(b3)::4, d(b4)::4, d(b1)::4, d(b2)::4, 101 d(c3)::4, d(c4)::4, d(c1)::4, d(c2)::4, 102 d(d1)::4, d(d2)::4, d(d3)::4, d(d4)::4, 103 d(e1)::4, d(e2)::4, d(e3)::4, d(e4)::4, 104 d(e5)::4, d(e6)::4, d(e7)::4, d(e8)::4, 105 d(e9)::4, d(e10)::4, d(e11)::4, d(e12)::4 >> 106 catch 107 :error -> :error 108 else 109 binary -> 110 {:ok, binary} 111 end 112 end 113 114 def dump(_), do: :error 115 116 def dump!(value) do 117 case dump(value) do 118 {:ok, binary} -> binary 119 :error -> raise ArgumentError, "Invalid uuid value #{inspect(value)}" 120 end 121 end 122 123 @compile {:inline, d: 1} 124 125 defp d(?0), do: 0 126 defp d(?1), do: 1 127 defp d(?2), do: 2 128 defp d(?3), do: 3 129 defp d(?4), do: 4 130 defp d(?5), do: 5 131 defp d(?6), do: 6 132 defp d(?7), do: 7 133 defp d(?8), do: 8 134 defp d(?9), do: 9 135 defp d(?A), do: 10 136 defp d(?B), do: 11 137 defp d(?C), do: 12 138 defp d(?D), do: 13 139 defp d(?E), do: 14 140 defp d(?F), do: 15 141 defp d(?a), do: 10 142 defp d(?b), do: 11 143 defp d(?c), do: 12 144 defp d(?d), do: 13 145 defp d(?e), do: 14 146 defp d(?f), do: 15 147 defp d(_), do: throw(:error) 148 149 @doc """ 150 Converts a binary UUID into a string. 151 """ 152 @impl true 153 @spec load(raw | any) :: {:ok, t} | :error 154 def load(<<_::128>> = uuid) do 155 encode(uuid) 156 end 157 158 def load(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> = string) do 159 raise ArgumentError, "trying to load string UUID as Tds.Ecto.UUID: #{inspect string}. " <> 160 "Maybe you wanted to declare :uuid as your database field?" 161 end 162 163 def load(_), do: :error 164 165 @doc """ 166 Generates a version 4 (random) UUID. 167 """ 168 @spec generate() :: t 169 def generate do 170 {:ok, uuid} = encode(bingenerate()) 171 uuid 172 end 173 174 @doc """ 175 Generates a version 4 (random) UUID in the binary format. 176 """ 177 @spec bingenerate() :: raw 178 def bingenerate do 179 <<u0::56, u1::36, u2::28>> = :crypto.strong_rand_bytes(15) 180 <<u0::56, 4::4, u1::36, 2::4, u2::28>> 181 end 182 183 # Callback invoked by autogenerate fields. 184 @impl true 185 def autogenerate, do: generate() 186 187 defp encode(<<a1::4, a2::4, a3::4, a4::4, 188 a5::4, a6::4, a7::4, a8::4, 189 b1::4, b2::4, b3::4, b4::4, 190 c1::4, c2::4, c3::4, c4::4, 191 d1::4, d2::4, d3::4, d4::4, 192 e1::4, e2::4, e3::4, e4::4, 193 e5::4, e6::4, e7::4, e8::4, 194 e9::4, e10::4, e11::4, e12::4 >>) do 195 << e(a7), e(a8), e(a5), e(a6), e(a3), e(a4), e(a1), e(a2), ?-, 196 e(b3), e(b4), e(b1), e(b2), ?-, 197 e(c3), e(c4), e(c1), e(c2), ?-, 198 e(d1), e(d2), e(d3), e(d4), ?-, 199 e(e1), e(e2), e(e3), e(e4), e(e5), e(e6), e(e7), e(e8), e(e9), e(e10), e(e11), e(e12) >> 200 catch 201 :error -> :error 202 else 203 encoded -> {:ok, encoded} 204 end 205 206 @compile {:inline, e: 1} 207 208 defp e(0), do: ?0 209 defp e(1), do: ?1 210 defp e(2), do: ?2 211 defp e(3), do: ?3 212 defp e(4), do: ?4 213 defp e(5), do: ?5 214 defp e(6), do: ?6 215 defp e(7), do: ?7 216 defp e(8), do: ?8 217 defp e(9), do: ?9 218 defp e(10), do: ?a 219 defp e(11), do: ?b 220 defp e(12), do: ?c 221 defp e(13), do: ?d 222 defp e(14), do: ?e 223 defp e(15), do: ?f 224 end 225 226 defmodule Tds.Ecto.VarChar do 227 @moduledoc """ 228 An Tds adapter Ecto Type that wraps erlang string into tuple so TDS driver 229 can understand if erlang string should be encoded as NVarChar or Varchar. 230 231 Due some limitations in Ecto and Tds driver, it is not possible to 232 support collations other than the one that is set on connection during login. 233 Please be aware of this limitation if you plan to store varchar values in 234 your database using Ecto since you will probably lose some codepoints in 235 the value during encoding. Instead use `tds_encoding` library and first 236 encode value and then annotate it as `:binary` by calling `Ecto.Query.API.type/2` 237 in your query. This way all codepoints will be properly preserved during 238 insert to database. 239 """ 240 use Ecto.Type 241 242 @typedoc """ 243 A erlang string 244 """ 245 @type t :: String.t 246 247 @typedoc """ 248 A value annotated as varchar. 249 """ 250 @type varchar :: {String.t, :varchar} 251 252 @doc false 253 @impl true 254 def type(), do: :varchar 255 256 @doc """ 257 Casts to string. 258 """ 259 @spec cast(t | varchar | any) :: {:ok, t} | :error 260 @impl true 261 def cast({value, :varchar}) do 262 # In case we get already dumped value 263 {:ok, value} 264 end 265 266 def cast(value) when is_binary(value) do 267 {:ok, value} 268 end 269 270 def cast(_), do: :error 271 272 @doc """ 273 Same as `cast/1` but raises `Ecto.CastError` on invalid arguments. 274 """ 275 @spec cast!(t | varchar | any) :: t 276 def cast!(value) do 277 case cast(value) do 278 {:ok, uuid} -> uuid 279 :error -> raise Ecto.CastError, type: __MODULE__, value: value 280 end 281 end 282 283 @doc """ 284 Loads the DB type as is. 285 """ 286 @impl true 287 @spec load(t | any) :: {:ok, t} | :error 288 def load(value) do 289 {:ok, value} 290 end 291 292 @doc """ 293 Converts a string representing a VarChar into a tuple `{value, :varchar}`. 294 295 Returns `:error` if value is not binary. 296 """ 297 @impl true 298 @spec dump(t | any) :: {:ok, varchar} | :error 299 def dump(value) when is_binary(value) do 300 {:ok, {value, :varchar}} 301 end 302 303 def dump(_), do: :error 304 end 305 end