connection.ex (54133B)
1 if Code.ensure_loaded?(Tds) do 2 defmodule Ecto.Adapters.Tds.Connection do 3 @moduledoc false 4 require Logger 5 alias Tds.Query 6 alias Ecto.Query.Tagged 7 alias Ecto.Adapters.SQL 8 require Ecto.Schema 9 10 @behaviour Ecto.Adapters.SQL.Connection 11 12 @impl true 13 def child_spec(opts) do 14 opts 15 |> Keyword.put_new(:use_elixir_calendar_types, true) 16 |> Tds.child_spec() 17 end 18 19 @impl true 20 def prepare_execute(pid, _name, statement, params, opts \\ []) do 21 query = %Query{statement: statement} 22 params = prepare_params(params) 23 24 opts = Keyword.put(opts, :parameters, params) 25 DBConnection.prepare_execute(pid, query, params, opts) 26 end 27 28 @impl true 29 def execute(pid, statement, params, opts) when is_binary(statement) or is_list(statement) do 30 query = %Query{statement: statement} 31 params = prepare_params(params) 32 opts = Keyword.put(opts, :parameters, params) 33 34 case DBConnection.prepare_execute(pid, query, params, opts) do 35 {:ok, _, %Tds.Result{columns: nil, num_rows: num_rows, rows: []}} 36 when num_rows >= 0 -> 37 {:ok, %Tds.Result{columns: nil, num_rows: num_rows, rows: nil}} 38 39 {:ok, _, query} -> 40 {:ok, query} 41 42 {:error, _} = err -> 43 err 44 end 45 end 46 47 def execute(pid, %{} = query, params, opts) do 48 opts = Keyword.put_new(opts, :parameters, params) 49 params = prepare_params(params) 50 opts = Keyword.put(opts, :parameters, params) 51 52 case DBConnection.prepare_execute(pid, query, params, opts) do 53 {:ok, _, query} -> {:ok, query} 54 {:error, _} = err -> err 55 end 56 end 57 58 @impl true 59 def stream(_conn, _sql, _params, _opts) do 60 error!(nil, "Repo.stream is not supported in the Tds adapter") 61 end 62 63 @impl true 64 def query(conn, sql, params, opts) do 65 params = prepare_params(params) 66 Tds.query(conn, sql, params, opts) 67 end 68 69 @impl true 70 def query_many(_conn, _sql, _params, _opts) do 71 error!(nil, "query_many is not supported in the Tds adapter") 72 end 73 74 @impl true 75 def to_constraints(%Tds.Error{mssql: %{number: code, msg_text: message}}, _opts) do 76 Tds.Error.get_constraint_violations(code, message) 77 end 78 79 def to_constraints(_, _opts), do: [] 80 81 defp prepare_params(params) do 82 {params, _} = 83 Enum.map_reduce(params, 1, fn param, acc -> 84 {value, type} = prepare_param(param) 85 {%Tds.Parameter{name: "@#{acc}", value: value, type: type}, acc + 1} 86 end) 87 88 params 89 end 90 91 # Decimal 92 defp prepare_param(%Decimal{} = value) do 93 {value, :decimal} 94 end 95 96 defp prepare_param(%NaiveDateTime{} = value) do 97 {value, :datetime2} 98 end 99 100 defp prepare_param(%DateTime{} = value) do 101 {value, :datetimeoffset} 102 end 103 104 defp prepare_param(%Date{} = value) do 105 {value, :date} 106 end 107 108 defp prepare_param(%Time{} = value) do 109 {value, :time} 110 end 111 112 defp prepare_param(%{__struct__: module} = _value) do 113 # just in case dumpers/loaders are not defined for the this struct 114 error!( 115 nil, 116 "Tds adapter is unable to convert struct `#{inspect(module)}` into supported MSSQL types" 117 ) 118 end 119 120 defp prepare_param(%{} = value), do: {json_library().encode!(value), :string} 121 defp prepare_param(value), do: prepare_raw_param(value) 122 123 defp prepare_raw_param(value) when is_binary(value) do 124 type = if String.printable?(value), do: :string, else: :binary 125 {value, type} 126 end 127 128 defp prepare_raw_param(value) when value == true, do: {1, :boolean} 129 defp prepare_raw_param(value) when value == false, do: {0, :boolean} 130 defp prepare_raw_param({_, :varchar} = value), do: value 131 defp prepare_raw_param(value), do: {value, nil} 132 133 defp json_library(), do: Application.get_env(:tds, :json_library, Jason) 134 135 ## Query 136 137 @parent_as __MODULE__ 138 alias Ecto.Query 139 alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr} 140 141 @impl true 142 def all(query, as_prefix \\ []) do 143 sources = create_names(query, as_prefix) 144 145 cte = cte(query, sources) 146 from = from(query, sources) 147 select = select(query, sources) 148 join = join(query, sources) 149 where = where(query, sources) 150 group_by = group_by(query, sources) 151 having = having(query, sources) 152 _window = window(query, sources) 153 combinations = combinations(query) 154 order_by = order_by(query, sources) 155 # limit = is handled in select (TOP X) 156 offset = offset(query, sources) 157 lock = lock(query, sources) 158 159 if query.offset != nil and query.order_bys == [], 160 do: error!(query, "ORDER BY is mandatory when OFFSET is set") 161 162 [cte, select, from, join, where, group_by, having, combinations, order_by, lock | offset] 163 end 164 165 @impl true 166 def update_all(query) do 167 sources = create_names(query, []) 168 cte = cte(query, sources) 169 {table, name, _model} = elem(sources, 0) 170 171 fields = update_fields(query, sources) 172 from = " FROM #{table} AS #{name}" 173 join = join(query, sources) 174 where = where(query, sources) 175 lock = lock(query, sources) 176 177 [ 178 cte, 179 "UPDATE ", 180 name, 181 " SET ", 182 fields, 183 returning(query, 0, "INSERTED"), 184 from, 185 join, 186 where | lock 187 ] 188 end 189 190 @impl true 191 def delete_all(query) do 192 sources = create_names(query, []) 193 cte = cte(query, sources) 194 {table, name, _model} = elem(sources, 0) 195 196 delete = "DELETE #{name}" 197 from = " FROM #{table} AS #{name}" 198 join = join(query, sources) 199 where = where(query, sources) 200 lock = lock(query, sources) 201 202 [cte, delete, returning(query, 0, "DELETED"), from, join, where | lock] 203 end 204 205 @impl true 206 def insert(prefix, table, header, rows, on_conflict, returning, placeholders) do 207 counter_offset = length(placeholders) + 1 208 [] = on_conflict(on_conflict, header) 209 returning = returning(returning, "INSERTED") 210 211 values = 212 if header == [] do 213 [returning, " DEFAULT VALUES"] 214 else 215 [ 216 ?\s, 217 ?(, 218 quote_names(header), 219 ?), 220 returning | 221 insert_all(rows, counter_offset) 222 ] 223 end 224 225 ["INSERT INTO ", quote_table(prefix, table), values] 226 end 227 228 defp on_conflict({:raise, _, []}, _header) do 229 [] 230 end 231 232 defp on_conflict({_, _, _}, _header) do 233 error!(nil, "Tds adapter supports only on_conflict: :raise") 234 end 235 236 defp insert_all(%Ecto.Query{} = query, _counter) do 237 [?\s, all(query)] 238 end 239 defp insert_all(rows, counter) do 240 sql = 241 intersperse_reduce(rows, ",", counter, fn row, counter -> 242 {row, counter} = insert_each(row, counter) 243 {[?(, row, ?)], counter} 244 end) 245 |> elem(0) 246 247 [" VALUES " | sql] 248 end 249 250 defp insert_each(values, counter) do 251 intersperse_reduce(values, ", ", counter, fn 252 nil, counter -> 253 {"DEFAULT", counter} 254 255 {%Query{} = query, params_counter}, counter -> 256 {[?(, all(query), ?)], counter + params_counter} 257 258 {:placeholder, placeholder_index}, counter -> 259 {[?@ | placeholder_index], counter} 260 261 _, counter -> 262 {[?@ | Integer.to_string(counter)], counter + 1} 263 end) 264 end 265 266 @impl true 267 def update(prefix, table, fields, filters, returning) do 268 {fields, count} = 269 intersperse_reduce(fields, ", ", 1, fn field, acc -> 270 {[quote_name(field), " = @", Integer.to_string(acc)], acc + 1} 271 end) 272 273 {filters, _count} = 274 intersperse_reduce(filters, " AND ", count, fn 275 {field, nil}, acc -> 276 {[quote_name(field), " IS NULL"], acc + 1} 277 278 {field, _value}, acc -> 279 {[quote_name(field), " = @", Integer.to_string(acc)], acc + 1} 280 281 field, acc -> 282 {[quote_name(field), " = @", Integer.to_string(acc)], acc + 1} 283 end) 284 285 [ 286 "UPDATE ", 287 quote_table(prefix, table), 288 " SET ", 289 fields, 290 returning(returning, "INSERTED"), 291 " WHERE " | filters 292 ] 293 end 294 295 @impl true 296 def delete(prefix, table, filters, returning) do 297 {filters, _} = 298 intersperse_reduce(filters, " AND ", 1, fn 299 {field, nil}, acc -> 300 {[quote_name(field), " IS NULL"], acc + 1} 301 302 {field, _value}, acc -> 303 {[quote_name(field), " = @", Integer.to_string(acc)], acc + 1} 304 305 field, acc -> 306 {[quote_name(field), " = @", Integer.to_string(acc)], acc + 1} 307 end) 308 309 [ 310 "DELETE FROM ", 311 quote_table(prefix, table), 312 returning(returning, "DELETED"), 313 " WHERE " | filters 314 ] 315 end 316 317 @impl true 318 def explain_query(conn, query, params, opts) do 319 params = prepare_params(params) 320 321 case Tds.query_multi(conn, build_explain_query(query), params, opts) do 322 {:ok, [_, %Tds.Result{} = result, _]} -> 323 {:ok, SQL.format_table(result)} 324 325 error -> 326 error 327 end 328 end 329 330 def build_explain_query(query) do 331 [ 332 "SET STATISTICS XML ON; ", 333 "SET STATISTICS PROFILE ON; ", 334 query, 335 "; ", 336 "SET STATISTICS XML OFF; ", 337 "SET STATISTICS PROFILE OFF;" 338 ] 339 |> IO.iodata_to_binary() 340 end 341 342 ## Query generation 343 344 binary_ops = [ 345 ==: " = ", 346 !=: " <> ", 347 <=: " <= ", 348 >=: " >= ", 349 <: " < ", 350 >: " > ", 351 +: " + ", 352 -: " - ", 353 *: " * ", 354 /: " / ", 355 and: " AND ", 356 or: " OR ", 357 ilike: " LIKE ", 358 like: " LIKE " 359 ] 360 361 @binary_ops Keyword.keys(binary_ops) 362 363 Enum.map(binary_ops, fn {op, str} -> 364 defp handle_call(unquote(op), 2), do: {:binary_op, unquote(str)} 365 end) 366 367 defp handle_call(fun, _arity), do: {:fun, Atom.to_string(fun)} 368 369 defp select(%{select: %{fields: fields}, distinct: distinct} = query, sources) do 370 [ 371 "SELECT ", 372 distinct(distinct, sources, query), 373 limit(query, sources), 374 select(fields, sources, query) 375 ] 376 end 377 378 defp distinct(nil, _sources, _query), do: [] 379 defp distinct(%QueryExpr{expr: true}, _sources, _query), do: "DISTINCT " 380 defp distinct(%QueryExpr{expr: false}, _sources, _query), do: [] 381 382 defp distinct(%QueryExpr{expr: exprs}, _sources, query) when is_list(exprs) do 383 error!( 384 query, 385 "DISTINCT with multiple columns is not supported by MsSQL. " <> 386 "Please use distinct(true) if you need distinct resultset" 387 ) 388 end 389 390 defp select([], _sources, _query) do 391 "CAST(1 as bit)" 392 end 393 394 defp select(fields, sources, query) do 395 intersperse_map(fields, ", ", fn 396 {:&, _, [idx]} -> 397 case elem(sources, idx) do 398 {nil, source, nil} -> 399 error!(query, "Tds adapter does not support selecting all fields from fragment #{source}. " <> 400 "Please specify exactly which fields you want to select") 401 402 {source, _, nil} -> 403 error!(query, "Tds adapter does not support selecting all fields from #{source} without a schema. " <> 404 "Please specify a schema or specify exactly which fields you want in projection") 405 406 {_, source, _} -> 407 source 408 end 409 410 {key, value} -> 411 [select_expr(value, sources, query), " AS ", quote_name(key)] 412 413 value -> 414 select_expr(value, sources, query) 415 end) 416 end 417 418 defp select_expr({:not, _, [expr]}, sources, query) do 419 [?~, ?(, select_expr(expr, sources, query), ?)] 420 end 421 422 defp select_expr(value, sources, query), do: expr(value, sources, query) 423 424 defp from(%{from: %{source: source, hints: hints}} = query, sources) do 425 {from, name} = get_source(query, sources, 0, source) 426 427 [" FROM ", from, " AS ", name, hints(hints)] 428 end 429 430 defp cte(%{with_ctes: %WithExpr{queries: [_ | _] = queries}} = query, sources) do 431 ctes = intersperse_map(queries, ", ", &cte_expr(&1, sources, query)) 432 ["WITH ", ctes, " "] 433 end 434 435 defp cte(%{with_ctes: _}, _), do: [] 436 437 defp cte_expr({name, cte}, sources, query) do 438 [quote_name(name), cte_header(cte, query), " AS ", cte_query(cte, sources, query)] 439 end 440 441 defp cte_header(%QueryExpr{}, query) do 442 error!( 443 query, 444 "Tds adapter does not support fragment in CTE" 445 ) 446 end 447 448 defp cte_header(%Ecto.Query{select: %{fields: fields}} = query, _) do 449 [ 450 " (", 451 intersperse_map(fields, ",", fn 452 {key, _} -> 453 quote_name(key) 454 455 other -> 456 error!( 457 query, 458 "Tds adapter expected field name or alias in CTE header," <> 459 " instead got #{inspect(other)}" 460 ) 461 end), 462 ?) 463 ] 464 end 465 466 defp cte_query(%Ecto.Query{} = query, sources, parent_query) do 467 query = put_in(query.aliases[@parent_as], {parent_query, sources}) 468 [?(, all(query, subquery_as_prefix(sources)), ?)] 469 end 470 471 defp update_fields(%Query{updates: updates} = query, sources) do 472 for( 473 %{expr: expr} <- updates, 474 {op, kw} <- expr, 475 {key, value} <- kw, 476 do: update_op(op, key, value, sources, query) 477 ) 478 |> Enum.intersperse(", ") 479 end 480 481 defp update_op(:set, key, value, sources, query) do 482 {_table, name, _model} = elem(sources, 0) 483 [name, ?., quote_name(key), " = " | expr(value, sources, query)] 484 end 485 486 defp update_op(:inc, key, value, sources, query) do 487 {_table, name, _model} = elem(sources, 0) 488 quoted = quote_name(key) 489 490 [name, ?., quoted, " = ", name, ?., quoted, " + " | expr(value, sources, query)] 491 end 492 493 defp update_op(command, _key, _value, _sources, query) do 494 error!(query, "Unknown update operation #{inspect(command)} for TDS") 495 end 496 497 defp join(%{joins: []}, _sources), do: [] 498 499 defp join(%{joins: joins} = query, sources) do 500 [ 501 ?\s, 502 intersperse_map(joins, ?\s, fn 503 %JoinExpr{on: %QueryExpr{expr: expr}, qual: qual, ix: ix, source: source, hints: hints} -> 504 {join, name} = get_source(query, sources, ix, source) 505 qual_text = join_qual(qual) 506 join = join || ["(", expr(source, sources, query) | ")"] 507 [qual_text, join, " AS ", name, hints(hints) | join_on(qual, expr, sources, query)] 508 end) 509 ] 510 end 511 512 defp join_on(:cross, true, _sources, _query), do: [] 513 defp join_on(:inner_lateral, true, _sources, _query), do: [] 514 defp join_on(:left_lateral, true, _sources, _query), do: [] 515 defp join_on(_qual, true, _sources, _query), do: [" ON 1 = 1"] 516 defp join_on(_qual, expr, sources, query), do: [" ON " | expr(expr, sources, query)] 517 518 defp join_qual(:inner), do: "INNER JOIN " 519 defp join_qual(:inner_loop), do: "INNER LOOP JOIN " 520 defp join_qual(:inner_hash), do: "INNER HASH JOIN " 521 defp join_qual(:inner_merge), do: "INNER MERGE JOIN " 522 defp join_qual(:inner_remote), do: "INNER REMOTE JOIN " 523 defp join_qual(:left), do: "LEFT OUTER JOIN " 524 defp join_qual(:right), do: "RIGHT OUTER JOIN " 525 defp join_qual(:full), do: "FULL OUTER JOIN " 526 defp join_qual(:cross), do: "CROSS JOIN " 527 defp join_qual(:inner_lateral), do: "CROSS APPLY " 528 defp join_qual(:left_lateral), do: "OUTER APPLY " 529 530 defp where(%Query{wheres: wheres} = query, sources) do 531 boolean(" WHERE ", wheres, sources, query) 532 end 533 534 defp having(%Query{havings: havings} = query, sources) do 535 boolean(" HAVING ", havings, sources, query) 536 end 537 538 defp window(%{windows: []}, _sources), do: [] 539 540 defp window(_query, _sources), 541 do: raise(RuntimeError, "Tds adapter does not support window functions") 542 543 defp group_by(%{group_bys: []}, _sources), do: [] 544 545 defp group_by(%{group_bys: group_bys} = query, sources) do 546 [ 547 " GROUP BY " 548 | intersperse_map(group_bys, ", ", fn %QueryExpr{expr: expr} -> 549 intersperse_map(expr, ", ", &expr(&1, sources, query)) 550 end) 551 ] 552 end 553 554 defp order_by(%{order_bys: []}, _sources), do: [] 555 556 defp order_by(%{order_bys: order_bys} = query, sources) do 557 [ 558 " ORDER BY " 559 | intersperse_map(order_bys, ", ", fn %QueryExpr{expr: expr} -> 560 intersperse_map(expr, ", ", &order_by_expr(&1, sources, query)) 561 end) 562 ] 563 end 564 565 defp order_by_expr({dir, expr}, sources, query) do 566 str = expr(expr, sources, query) 567 568 case dir do 569 :asc -> str 570 :desc -> [str | " DESC"] 571 _ -> error!(query, "#{dir} is not supported in ORDER BY in MSSQL") 572 end 573 end 574 575 defp limit(%Query{limit: nil}, _sources), do: [] 576 577 defp limit( 578 %Query{ 579 limit: %QueryExpr{ 580 expr: expr 581 } 582 } = query, 583 sources 584 ) do 585 case Map.get(query, :offset) do 586 nil -> 587 ["TOP(", expr(expr, sources, query), ") "] 588 589 _ -> 590 [] 591 end 592 end 593 594 defp offset(%{offset: nil}, _sources), do: [] 595 596 defp offset(%Query{offset: _, limit: nil} = query, _sources) do 597 error!(query, "You must provide a limit while using an offset") 598 end 599 600 defp offset(%{offset: offset, limit: limit} = query, sources) do 601 [ 602 " OFFSET ", 603 expr(offset.expr, sources, query), 604 " ROW", 605 " FETCH NEXT ", 606 expr(limit.expr, sources, query), 607 " ROWS ONLY" 608 ] 609 end 610 611 defp hints([_ | _] = hints), do: [" WITH (", Enum.intersperse(hints, ", "), ?)] 612 defp hints([]), do: [] 613 614 defp lock(%{lock: nil}, _sources), do: [] 615 defp lock(%{lock: binary}, _sources) when is_binary(binary), do: [" OPTION (", binary, ?)] 616 defp lock(%{lock: expr} = query, sources), do: [" OPTION (", expr(expr, sources, query), ?)] 617 618 defp combinations(%{combinations: combinations}) do 619 Enum.map(combinations, fn 620 {:union, query} -> [" UNION (", all(query), ")"] 621 {:union_all, query} -> [" UNION ALL (", all(query), ")"] 622 {:except, query} -> [" EXCEPT (", all(query), ")"] 623 {:except_all, query} -> [" EXCEPT ALL (", all(query), ")"] 624 {:intersect, query} -> [" INTERSECT (", all(query), ")"] 625 {:intersect_all, query} -> [" INTERSECT ALL (", all(query), ")"] 626 end) 627 end 628 629 defp boolean(_name, [], _sources, _query), do: [] 630 631 defp boolean(name, [%{expr: expr, op: op} | query_exprs], sources, query) do 632 [ 633 name 634 | Enum.reduce(query_exprs, {op, paren_expr(expr, sources, query)}, fn 635 %BooleanExpr{expr: expr, op: op}, {op, acc} -> 636 {op, [acc, operator_to_boolean(op), paren_expr(expr, sources, query)]} 637 638 %BooleanExpr{expr: expr, op: op}, {_, acc} -> 639 {op, [?(, acc, ?), operator_to_boolean(op), paren_expr(expr, sources, query)]} 640 end) 641 |> elem(1) 642 ] 643 end 644 645 defp operator_to_boolean(:and), do: " AND " 646 defp operator_to_boolean(:or), do: " OR " 647 648 defp parens_for_select([first_expr | _] = expr) do 649 if is_binary(first_expr) and String.match?(first_expr, ~r/^\s*select/i) do 650 [?(, expr, ?)] 651 else 652 expr 653 end 654 end 655 656 defp paren_expr(true, _sources, _query) do 657 ["(1 = 1)"] 658 end 659 660 defp paren_expr(false, _sources, _query) do 661 ["(1 = 0)"] 662 end 663 664 defp paren_expr(expr, sources, query) do 665 [?(, expr(expr, sources, query), ?)] 666 end 667 668 # :^ - represents parameter ix is index number 669 defp expr({:^, [], [idx]}, _sources, _query) do 670 "@#{idx + 1}" 671 end 672 673 defp expr({{:., _, [{:parent_as, _, [as]}, field]}, _, []}, _sources, query) 674 when is_atom(field) do 675 {ix, sources} = get_parent_sources_ix(query, as) 676 {_, name, _} = elem(sources, ix) 677 [name, ?. | quote_name(field)] 678 end 679 680 defp expr({{:., _, [{:&, _, [idx]}, field]}, _, []}, sources, _query) 681 when is_atom(field) or is_binary(field) do 682 {_, name, _} = elem(sources, idx) 683 [name, ?. | quote_name(field)] 684 end 685 686 defp expr({:&, _, [idx]}, sources, _query) do 687 {_table, source, _schema} = elem(sources, idx) 688 source 689 end 690 691 defp expr({:&, _, [idx, fields, _counter]}, sources, query) do 692 {_table, name, schema} = elem(sources, idx) 693 694 if is_nil(schema) and is_nil(fields) do 695 error!( 696 query, 697 "Tds adapter requires a schema module when using selector #{inspect(name)} but " <> 698 "none was given. Please specify schema " <> 699 "or specify exactly which fields from #{inspect(name)} you what in projection" 700 ) 701 end 702 703 Enum.map_join(fields, ", ", &"#{name}.#{quote_name(&1)}") 704 end 705 706 # example from {:in, [], [1, {:^, [], [0, 0]}]} 707 defp expr({:in, _, [_left, []]}, _sources, _query) do 708 "0=1" 709 end 710 711 # example from(p in Post, where: p.id in [1,2, ^some_id]) 712 defp expr({:in, _, [left, right]}, sources, query) when is_list(right) do 713 args = Enum.map_join(right, ",", &expr(&1, sources, query)) 714 [expr(left, sources, query), " IN (", args | ")"] 715 end 716 717 # example from(p in Post, where: p.id in []) 718 defp expr({:in, _, [_, {:^, _, [_, 0]}]}, _sources, _query), do: "0=1" 719 720 # example from(p in Post, where: p.id in ^some_list) 721 # or from(p in Post, where: p.id in ^[]) 722 defp expr({:in, _, [left, {:^, _, [idx, length]}]}, sources, query) do 723 args = list_param_to_args(idx, length) 724 [expr(left, sources, query), " IN (", args | ")"] 725 end 726 727 defp expr({:in, _, [left, %Ecto.SubQuery{} = subquery]}, sources, query) do 728 [expr(left, sources, query), " IN ", expr(subquery, sources, query)] 729 end 730 731 defp expr({:in, _, [left, right]}, sources, query) do 732 [expr(left, sources, query), " = ANY(", expr(right, sources, query) | ")"] 733 end 734 735 defp expr({:is_nil, _, [arg]}, sources, query) do 736 "#{expr(arg, sources, query)} IS NULL" 737 end 738 739 defp expr({:not, _, [expr]}, sources, query) do 740 ["NOT (", expr(expr, sources, query) | ")"] 741 end 742 743 defp expr({:filter, _, _}, _sources, query) do 744 error!(query, "Tds adapter does not support aggregate filters") 745 end 746 747 defp expr(%Ecto.SubQuery{query: query}, sources, parent_query) do 748 query = put_in(query.aliases[@parent_as], {parent_query, sources}) 749 [?(, all(query, subquery_as_prefix(sources)), ?)] 750 end 751 752 defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do 753 error!(query, "Tds adapter does not support keyword or interpolated fragments") 754 end 755 756 defp expr({:fragment, _, parts}, sources, query) do 757 Enum.map(parts, fn 758 {:raw, part} -> part 759 {:expr, expr} -> expr(expr, sources, query) 760 end) 761 |> parens_for_select 762 end 763 764 defp expr({:literal, _, [literal]}, _sources, _query) do 765 quote_name(literal) 766 end 767 768 defp expr({:selected_as, _, [name]}, _sources, _query) do 769 [quote_name(name)] 770 end 771 772 defp expr({:datetime_add, _, [datetime, count, interval]}, sources, query) do 773 [ 774 "DATEADD(", 775 interval, 776 ", ", 777 interval_count(count, sources, query), 778 ", CAST(", 779 expr(datetime, sources, query), 780 " AS datetime2(6)))" 781 ] 782 end 783 784 defp expr({:date_add, _, [date, count, interval]}, sources, query) do 785 [ 786 "CAST(DATEADD(", 787 interval, 788 ", ", 789 interval_count(count, sources, query), 790 ", CAST(", 791 expr(date, sources, query), 792 " AS datetime2(6))" | ") AS date)" 793 ] 794 end 795 796 defp expr({:count, _, []}, _sources, _query), do: "count(*)" 797 798 defp expr({:json_extract_path, _, _}, _sources, query) do 799 error!( 800 query, 801 "Tds adapter does not support json_extract_path expression" <> 802 ", use fragment with JSON_VALUE/JSON_QUERY" 803 ) 804 end 805 806 defp expr({fun, _, args}, sources, query) when is_atom(fun) and is_list(args) do 807 {modifier, args} = 808 case args do 809 [rest, :distinct] -> {"DISTINCT ", [rest]} 810 _ -> {"", args} 811 end 812 813 case handle_call(fun, length(args)) do 814 {:binary_op, op} -> 815 [left, right] = args 816 [op_to_binary(left, sources, query), op | op_to_binary(right, sources, query)] 817 818 {:fun, fun} -> 819 [fun, ?(, modifier, intersperse_map(args, ", ", &expr(&1, sources, query)), ?)] 820 end 821 end 822 823 defp expr(list, sources, query) when is_list(list) do 824 Enum.map_join(list, ", ", &expr(&1, sources, query)) 825 end 826 827 defp expr({string, :varchar}, _sources, _query) 828 when is_binary(string) do 829 "'#{escape_string(string)}'" 830 end 831 832 defp expr(string, _sources, _query) when is_binary(string) do 833 "N'#{escape_string(string)}'" 834 end 835 836 defp expr(%Decimal{exp: exp} = decimal, _sources, _query) do 837 # this should help gaining precision for decimals values embedded in query 838 # but this is still not good enough, for instance: 839 # 840 # from(p in Post, select: type(2.0 + ^"2", p.cost()))) 841 # 842 # Post.cost is :decimal, but we don't know precision and scale since 843 # such info is only available in migration files. So query compilation 844 # will yield 845 # 846 # SELECT CAST(CAST(2.0 as decimal(38, 1)) + @1 AS decimal) 847 # FROM [posts] AS p0 848 # 849 # as long as we have CAST(... as DECIMAL) without precision and scale 850 # value could be truncated 851 [ 852 "CAST(", 853 Decimal.to_string(decimal, :normal), 854 " as decimal(38, #{abs(exp)})", 855 ?) 856 ] 857 end 858 859 defp expr(%Tagged{value: binary, type: :binary}, _sources, _query) when is_binary(binary) do 860 hex = Base.encode16(binary, case: :lower) 861 "0x#{hex}" 862 end 863 864 defp expr(%Tagged{value: binary, type: :uuid}, _sources, _query) when is_binary(binary) do 865 case binary do 866 <<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>> -> 867 {:ok, value} = Tds.Ecto.UUID.dump(binary) 868 value 869 870 any -> 871 any 872 end 873 end 874 875 defp expr(%Tagged{value: other, type: type}, sources, query) 876 when type in [:varchar, :nvarchar] do 877 "CAST(#{expr(other, sources, query)} AS #{column_type(type, [])}(max))" 878 end 879 880 defp expr(%Tagged{value: other, type: :integer}, sources, query) do 881 "CAST(#{expr(other, sources, query)} AS bigint)" 882 end 883 884 defp expr(%Tagged{value: other, type: type}, sources, query) do 885 "CAST(#{expr(other, sources, query)} AS #{column_type(type, [])})" 886 end 887 888 defp expr(nil, _sources, _query), do: "NULL" 889 defp expr(true, _sources, _query), do: "1" 890 defp expr(false, _sources, _query), do: "0" 891 892 defp expr(literal, _sources, _query) when is_binary(literal) do 893 "'#{escape_string(literal)}'" 894 end 895 896 defp expr(literal, _sources, _query) when is_integer(literal) do 897 Integer.to_string(literal) 898 end 899 900 defp expr(literal, _sources, _query) when is_float(literal) do 901 Float.to_string(literal) 902 end 903 904 defp expr(field, _sources, query) do 905 error!(query, "unsupported MSSQL expressions: `#{inspect(field)}`") 906 end 907 908 defp op_to_binary({op, _, [_, _]} = expr, sources, query) when op in @binary_ops do 909 paren_expr(expr, sources, query) 910 end 911 912 defp op_to_binary({:is_nil, _, [_]} = expr, sources, query) do 913 paren_expr(expr, sources, query) 914 end 915 916 defp op_to_binary(expr, sources, query) do 917 expr(expr, sources, query) 918 end 919 920 defp interval_count(count, _sources, _query) when is_integer(count) do 921 Integer.to_string(count) 922 end 923 924 defp interval_count(count, _sources, _query) when is_float(count) do 925 :erlang.float_to_binary(count, [:compact, decimals: 16]) 926 end 927 928 defp interval_count(count, sources, query) do 929 expr(count, sources, query) 930 end 931 932 defp returning([], _verb), do: [] 933 934 defp returning(returning, verb) when is_list(returning) do 935 [" OUTPUT ", intersperse_map(returning, ", ", &[verb, ?., quote_name(&1)])] 936 end 937 938 defp returning(%{select: nil}, _, _), 939 do: [] 940 941 defp returning(%{select: %{fields: fields}} = query, idx, verb), 942 do: [ 943 " OUTPUT " 944 | intersperse_map(fields, ", ", fn 945 {{:., _, [{:&, _, [^idx]}, key]}, _, _} -> [verb, ?., quote_name(key)] 946 _ -> error!(query, "MSSQL can only return table #{verb} columns") 947 end) 948 ] 949 950 defp create_names(%{sources: sources}, as_prefix) do 951 create_names(sources, 0, tuple_size(sources), as_prefix) |> List.to_tuple() 952 end 953 954 defp create_names(sources, pos, limit, as_prefix) when pos < limit do 955 [create_name(sources, pos, as_prefix) | create_names(sources, pos + 1, limit, as_prefix)] 956 end 957 958 defp create_names(_sources, pos, pos, as_prefix) do 959 [as_prefix] 960 end 961 962 defp subquery_as_prefix(sources) do 963 [?s | :erlang.element(tuple_size(sources), sources)] 964 end 965 966 defp create_name(sources, pos, as_prefix) do 967 case elem(sources, pos) do 968 {:fragment, _, _} -> 969 {nil, as_prefix ++ [?f | Integer.to_string(pos)], nil} 970 971 {table, model, prefix} -> 972 name = as_prefix ++ [create_alias(table) | Integer.to_string(pos)] 973 {quote_table(prefix, table), name, model} 974 975 %Ecto.SubQuery{} -> 976 {nil, as_prefix ++ [?s | Integer.to_string(pos)], nil} 977 end 978 end 979 980 defp create_alias(<<first, _rest::binary>>) when first in ?a..?z when first in ?A..?Z do 981 first 982 end 983 984 defp create_alias(_) do 985 ?t 986 end 987 988 # DDL 989 alias Ecto.Migration.{Table, Index, Reference, Constraint} 990 991 @creates [:create, :create_if_not_exists] 992 @drops [:drop, :drop_if_exists] 993 994 @impl true 995 def execute_ddl({command, %Table{} = table, columns}) when command in @creates do 996 prefix = table.prefix 997 998 pk_name = 999 if table.prefix, 1000 do: "#{table.prefix}_#{table.name}", 1001 else: table.name 1002 1003 table_structure = 1004 table 1005 |> column_definitions(columns) 1006 |> Kernel.++(pk_definitions(columns, ", CONSTRAINT [#{pk_name}_pkey] ")) 1007 |> case do 1008 [] -> [] 1009 list -> [" (", list, ?)] 1010 end 1011 1012 create_if_not_exists = 1013 if_table_not_exists(command == :create_if_not_exists, table.name, prefix) 1014 1015 [ 1016 [ 1017 create_if_not_exists, 1018 "CREATE TABLE ", 1019 quote_table(prefix, table.name), 1020 table_structure, 1021 engine_expr(table.engine), 1022 options_expr(table.options), 1023 "; " 1024 ] 1025 ] 1026 end 1027 1028 def execute_ddl({command, %Table{}, :cascade}) when command in @drops, 1029 do: error!(nil, "MSSQL does not support `CASCADE` in DROP TABLE commands") 1030 1031 def execute_ddl({command, %Table{} = table, :restrict}) when command in @drops do 1032 prefix = table.prefix 1033 1034 [ 1035 [ 1036 if_table_exists(command == :drop_if_exists, table.name, prefix), 1037 "DROP TABLE ", 1038 quote_table(prefix, table.name), 1039 "; " 1040 ] 1041 ] 1042 end 1043 1044 def execute_ddl({:alter, %Table{} = table, changes}) do 1045 statement_prefix = ["ALTER TABLE ", quote_table(table.prefix, table.name), " "] 1046 1047 pk_name = 1048 if table.prefix, 1049 do: "#{table.prefix}_#{table.name}", 1050 else: table.name 1051 1052 pkeys = 1053 case pk_definitions(changes, " CONSTRAINT [#{pk_name}_pkey] ") do 1054 [] -> [] 1055 sql -> [statement_prefix, "ADD", sql] 1056 end 1057 1058 [ 1059 [ 1060 column_changes(statement_prefix, table, changes), 1061 pkeys 1062 ] 1063 ] 1064 end 1065 1066 def execute_ddl({command, %Index{} = index}) when command in @creates do 1067 prefix = index.prefix 1068 1069 if index.using do 1070 error!(nil, "MSSQL does not support `using` in indexes") 1071 end 1072 1073 if index.nulls_distinct == true do 1074 error!(nil, "MSSQL does not support nulls_distinct set to true in indexes") 1075 end 1076 1077 with_options = 1078 if index.concurrently or index.options != nil do 1079 [ 1080 " WITH", 1081 ?(, 1082 if_do(index.concurrently, "ONLINE=ON"), 1083 if_do(index.concurrently and index.options != nil, ","), 1084 if_do(index.options != nil, index.options), 1085 ?) 1086 ] 1087 else 1088 [] 1089 end 1090 1091 include = 1092 index.include 1093 |> List.wrap() 1094 |> intersperse_map(", ", &index_expr/1) 1095 1096 [ 1097 [ 1098 if_index_not_exists( 1099 command == :create_if_not_exists, 1100 index.name, 1101 unquoted_name(prefix, index.table) 1102 ), 1103 "CREATE", 1104 if_do(index.unique, " UNIQUE"), 1105 " INDEX ", 1106 quote_name(index.name), 1107 " ON ", 1108 quote_table(prefix, index.table), 1109 " (", 1110 intersperse_map(index.columns, ", ", &index_expr/1), 1111 ?), 1112 if_do(include != [], [" INCLUDE ", ?(, include, ?)]), 1113 if_do(index.where, [" WHERE (", index.where, ?)]), 1114 with_options, 1115 ?; 1116 ] 1117 ] 1118 end 1119 1120 def execute_ddl({:create, %Constraint{exclude: exclude}}) when exclude != nil do 1121 msg = 1122 "`:exclude` is not supported Tds adapter check constraint parameter, instead " <> 1123 "set `:check` attribute with negated expression." 1124 1125 error!(nil, msg) 1126 end 1127 1128 def execute_ddl({:create, %Constraint{validate: false}}) do 1129 error!(nil, "`:validate` is not supported by the Tds adapter") 1130 end 1131 1132 def execute_ddl({:create, %Constraint{} = constraint}) do 1133 table_name = quote_table(constraint.prefix, constraint.table) 1134 1135 [ 1136 [ 1137 "ALTER TABLE ", 1138 table_name, 1139 " ADD CONSTRAINT ", 1140 quote_name(constraint.name), 1141 " ", 1142 "CHECK (", 1143 constraint.check, 1144 "); " 1145 ] 1146 ] 1147 end 1148 1149 def execute_ddl({command, %Index{}, :cascade}) when command in @drops, 1150 do: error!(nil, "MSSQL does not support `CASCADE` in DROP INDEX commands") 1151 1152 def execute_ddl({command, %Index{} = index, :restrict}) when command in @drops do 1153 prefix = index.prefix 1154 1155 [ 1156 [ 1157 if_index_exists( 1158 command == :drop_if_exists, 1159 index.name, 1160 unquoted_name(prefix, index.table) 1161 ), 1162 "DROP INDEX ", 1163 quote_name(index.name), 1164 " ON ", 1165 quote_table(prefix, index.table), 1166 if_do(index.concurrently, " LOCK=NONE"), 1167 "; " 1168 ] 1169 ] 1170 end 1171 1172 def execute_ddl({command, %Constraint{}, :cascade}) when command in @drops, 1173 do: error!(nil, "MSSQL does not support `CASCADE` in DROP CONSTRAINT commands") 1174 1175 def execute_ddl({command, %Constraint{} = constraint, _}) when command in @drops do 1176 table_name = quote_table(constraint.prefix, constraint.table) 1177 1178 [ 1179 [ 1180 if_check_constraint_exists( 1181 command == :drop_if_exists, 1182 constraint.name, 1183 constraint.prefix 1184 ), 1185 "ALTER TABLE ", 1186 table_name, 1187 " DROP CONSTRAINT ", 1188 quote_name(constraint.name), 1189 "; " 1190 ] 1191 ] 1192 end 1193 1194 def execute_ddl({:rename, %Table{} = current_table, %Table{} = new_table}) do 1195 [ 1196 [ 1197 "EXEC sp_rename '", 1198 unquoted_name(current_table.prefix, current_table.name), 1199 "', '", 1200 unquoted_name(new_table.prefix, new_table.name), 1201 "'" 1202 ] 1203 ] 1204 end 1205 1206 def execute_ddl({:rename, table, current_column, new_column}) do 1207 [ 1208 [ 1209 "EXEC sp_rename '", 1210 unquoted_name(table.prefix, table.name, current_column), 1211 "', '", 1212 unquoted_name(new_column), 1213 "', 'COLUMN'" 1214 ] 1215 ] 1216 end 1217 1218 def execute_ddl(string) when is_binary(string), do: [string] 1219 1220 def execute_ddl(keyword) when is_list(keyword), 1221 do: error!(nil, "Tds adapter does not support keyword lists in execute") 1222 1223 @impl true 1224 def ddl_logs(_), do: [] 1225 1226 @impl true 1227 def table_exists_query(table) do 1228 {"SELECT 1 FROM sys.tables WHERE [name] = @1", [table]} 1229 end 1230 1231 defp pk_definitions(columns, prefix) do 1232 pks = 1233 for {_, name, _, opts} <- columns, 1234 opts[:primary_key], 1235 do: name 1236 1237 case pks do 1238 [] -> 1239 [] 1240 1241 _ -> 1242 [prefix, "PRIMARY KEY CLUSTERED (", quote_names(pks), ?)] 1243 end 1244 end 1245 1246 defp column_definitions(table, columns) do 1247 intersperse_map(columns, ", ", &column_definition(table, &1)) 1248 end 1249 1250 defp column_definition(table, {:add, name, %Reference{} = ref, opts}) do 1251 [ 1252 quote_name(name), 1253 " ", 1254 reference_column_type(ref.type, opts), 1255 column_options(table, name, opts), 1256 reference_expr(ref, table, name) 1257 ] 1258 end 1259 1260 defp column_definition(table, {:add, name, type, opts}) do 1261 [quote_name(name), " ", column_type(type, opts), column_options(table, name, opts)] 1262 end 1263 1264 defp column_changes(statement, table, columns) do 1265 for column <- columns do 1266 column_change(statement, table, column) 1267 end 1268 end 1269 1270 defp column_change(_statement_prefix, _table, {_command, _name, %Reference{validate: false}, _opts}) do 1271 error!(nil, "validate: false on references is not supported in Tds") 1272 end 1273 1274 defp column_change(statement_prefix, table, {:add, name, %Reference{} = ref, opts}) do 1275 [ 1276 [ 1277 statement_prefix, 1278 "ADD ", 1279 quote_name(name), 1280 " ", 1281 reference_column_type(ref.type, opts), 1282 column_options(table, name, opts), 1283 "; " 1284 ], 1285 [statement_prefix, "ADD", constraint_expr(ref, table, name), "; "] 1286 ] 1287 end 1288 1289 defp column_change(statement_prefix, table, {:add, name, type, opts}) do 1290 [ 1291 [ 1292 statement_prefix, 1293 "ADD ", 1294 quote_name(name), 1295 " ", 1296 column_type(type, opts), 1297 column_options(table, name, opts), 1298 "; " 1299 ] 1300 ] 1301 end 1302 1303 defp column_change( 1304 statement_prefix, 1305 %{name: table_name, prefix: prefix} = table, 1306 {:add_if_not_exists, column_name, type, opts} 1307 ) do 1308 [ 1309 [ 1310 if_column_not_exists(prefix, table_name, column_name), 1311 statement_prefix, 1312 "ADD ", 1313 quote_name(column_name), 1314 " ", 1315 column_type(type, opts), 1316 column_options(table, column_name, opts), 1317 "; " 1318 ] 1319 ] 1320 end 1321 1322 defp column_change(statement_prefix, table, {:modify, name, %Reference{} = ref, opts}) do 1323 [ 1324 drop_constraint_from_expr(opts[:from], table, name, statement_prefix), 1325 maybe_drop_default_expr(statement_prefix, table, name, opts), 1326 [ 1327 statement_prefix, 1328 "ALTER COLUMN ", 1329 quote_name(name), 1330 " ", 1331 reference_column_type(ref.type, opts), 1332 column_options(table, name, opts), 1333 "; " 1334 ], 1335 [statement_prefix, "ADD", constraint_expr(ref, table, name), "; "], 1336 [column_default_value(statement_prefix, table, name, opts)] 1337 ] 1338 end 1339 1340 defp column_change(statement_prefix, table, {:modify, name, type, opts}) do 1341 [ 1342 drop_constraint_from_expr(opts[:from], table, name, statement_prefix), 1343 maybe_drop_default_expr(statement_prefix, table, name, opts), 1344 [ 1345 statement_prefix, 1346 "ALTER COLUMN ", 1347 quote_name(name), 1348 " ", 1349 column_type(type, opts), 1350 null_expr(Keyword.get(opts, :null)), 1351 "; " 1352 ], 1353 [column_default_value(statement_prefix, table, name, opts)] 1354 ] 1355 end 1356 1357 defp column_change(statement_prefix, _table, {:remove, name}) do 1358 [statement_prefix, "DROP COLUMN ", quote_name(name), "; "] 1359 end 1360 1361 defp column_change( 1362 statement_prefix, 1363 %{name: table, prefix: prefix}, 1364 {:remove_if_exists, column_name, _} 1365 ) do 1366 [ 1367 [ 1368 if_column_exists(prefix, table, column_name), 1369 statement_prefix, 1370 "DROP COLUMN ", 1371 quote_name(column_name), 1372 "; " 1373 ] 1374 ] 1375 end 1376 1377 defp column_options(table, name, opts) do 1378 default = Keyword.fetch(opts, :default) 1379 null = Keyword.get(opts, :null) 1380 [null_expr(null), default_expr(table, name, default)] 1381 end 1382 1383 defp column_default_value(statement_prefix, table, name, opts) do 1384 default_expression = default_expr(table, name, Keyword.fetch(opts, :default)) 1385 1386 case default_expression do 1387 [] -> [] 1388 _ -> [statement_prefix, "ADD", default_expression, " FOR ", quote_name(name), "; "] 1389 end 1390 end 1391 1392 defp null_expr(false), do: [" NOT NULL"] 1393 defp null_expr(true), do: [" NULL"] 1394 defp null_expr(_), do: [] 1395 1396 defp default_expr(_table, _name, {:ok, nil}), 1397 do: [] 1398 1399 defp default_expr(table, name, {:ok, literal}) when is_binary(literal), 1400 do: [ 1401 " CONSTRAINT ", 1402 constraint_name("DF", table, name), 1403 " DEFAULT (N'", 1404 escape_string(literal), 1405 "')" 1406 ] 1407 1408 defp default_expr(table, name, {:ok, true}), 1409 do: [" CONSTRAINT ", constraint_name("DF", table, name), " DEFAULT (1)"] 1410 1411 defp default_expr(table, name, {:ok, false}), 1412 do: [" CONSTRAINT ", constraint_name("DF", table, name), " DEFAULT (0)"] 1413 1414 defp default_expr(table, name, {:ok, literal}) when is_number(literal), 1415 do: [ 1416 " CONSTRAINT ", 1417 constraint_name("DF", table, name), 1418 " DEFAULT (", 1419 to_string(literal), 1420 ")" 1421 ] 1422 1423 defp default_expr(table, name, {:ok, {:fragment, expr}}), 1424 do: [" CONSTRAINT ", constraint_name("DF", table, name), " DEFAULT (", expr, ")"] 1425 1426 defp default_expr(_table, _name, :error), do: [] 1427 1428 defp drop_constraint_from_expr(%Reference{} = ref, table, name, stm_prefix) do 1429 [stm_prefix, "DROP CONSTRAINT ", reference_name(ref, table, name), "; "] 1430 end 1431 1432 defp drop_constraint_from_expr(_, _, _, _), 1433 do: [] 1434 1435 defp maybe_drop_default_expr(statement_prefix, table, name, opts) do 1436 if Keyword.has_key?(opts, :default) do 1437 constraint_name = constraint_name("DF", table, name) 1438 if_exists_drop_constraint(constraint_name, statement_prefix) 1439 else 1440 [] 1441 end 1442 end 1443 1444 defp constraint_name(type, table, name), 1445 do: quote_name("#{type}_#{table.prefix}_#{table.name}_#{name}") 1446 1447 defp index_expr(literal) when is_binary(literal), do: literal 1448 defp index_expr(literal), do: quote_name(literal) 1449 1450 defp engine_expr(_storage_engine), do: [""] 1451 1452 defp options_expr(nil), do: [] 1453 1454 defp options_expr(keyword) when is_list(keyword), 1455 do: error!(nil, "Tds adapter does not support keyword lists in :options") 1456 1457 defp options_expr(options), do: [" ", to_string(options)] 1458 1459 defp column_type(type, opts) do 1460 size = Keyword.get(opts, :size) 1461 precision = Keyword.get(opts, :precision) 1462 scale = Keyword.get(opts, :scale) 1463 ecto_to_db(type, size, precision, scale) 1464 end 1465 1466 defp constraint_expr(%Reference{} = ref, table, name) do 1467 {current_columns, reference_columns} = Enum.unzip([{name, ref.column} | ref.with]) 1468 1469 if ref.match do 1470 error!(nil, ":match is not supported in references for tds") 1471 end 1472 1473 [ 1474 " CONSTRAINT ", 1475 reference_name(ref, table, name), 1476 " FOREIGN KEY (#{quote_names(current_columns)})", 1477 " REFERENCES ", 1478 quote_table(ref.prefix || table.prefix, ref.table), 1479 "(#{quote_names(reference_columns)})", 1480 reference_on_delete(ref.on_delete), 1481 reference_on_update(ref.on_update) 1482 ] 1483 end 1484 1485 defp reference_expr(%Reference{} = ref, table, name) do 1486 [",", constraint_expr(ref, table, name)] 1487 end 1488 1489 defp reference_name(%Reference{name: nil}, table, column), 1490 do: quote_name("#{table.name}_#{column}_fkey") 1491 1492 defp reference_name(%Reference{name: name}, _table, _column), do: quote_name(name) 1493 1494 defp reference_column_type(:id, _opts), do: "BIGINT" 1495 defp reference_column_type(:serial, _opts), do: "INT" 1496 defp reference_column_type(:bigserial, _opts), do: "BIGINT" 1497 defp reference_column_type(type, opts), do: column_type(type, opts) 1498 1499 defp reference_on_delete(:nilify_all), do: " ON DELETE SET NULL" 1500 defp reference_on_delete(:delete_all), do: " ON DELETE CASCADE" 1501 defp reference_on_delete(:nothing), do: " ON DELETE NO ACTION" 1502 defp reference_on_delete(_), do: [] 1503 1504 defp reference_on_update(:nilify_all), do: " ON UPDATE SET NULL" 1505 defp reference_on_update(:update_all), do: " ON UPDATE CASCADE" 1506 defp reference_on_update(:nothing), do: " ON UPDATE NO ACTION" 1507 defp reference_on_update(_), do: [] 1508 1509 ## Helpers 1510 1511 defp get_source(query, sources, ix, source) do 1512 {expr, name, _schema} = elem(sources, ix) 1513 {expr || expr(source, sources, query), name} 1514 end 1515 1516 defp get_parent_sources_ix(query, as) do 1517 case query.aliases[@parent_as] do 1518 {%{aliases: %{^as => ix}}, sources} -> {ix, sources} 1519 {%{} = parent, _sources} -> get_parent_sources_ix(parent, as) 1520 end 1521 end 1522 1523 defp quote_name(name) when is_atom(name) do 1524 quote_name(Atom.to_string(name)) 1525 end 1526 1527 defp quote_name(name) when is_binary(name) do 1528 if String.contains?(name, ["[", "]"]) do 1529 error!(nil, "bad literal/field/table name #{inspect(name)} ('[' and ']' are not permitted)") 1530 end 1531 1532 "[#{name}]" 1533 end 1534 1535 defp quote_names(names), do: intersperse_map(names, ?,, "e_name/1) 1536 1537 defp quote_table(nil, name), do: quote_table(name) 1538 1539 defp quote_table({server, db, schema}, name), 1540 do: [quote_table(server), ".", quote_table(db), ".", quote_table(schema), ".", quote_table(name)] 1541 1542 defp quote_table({db, schema}, name), 1543 do: [quote_table(db), ".", quote_table(schema), ".", quote_table(name)] 1544 1545 defp quote_table(prefix, name), 1546 do: [quote_table(prefix), ".", quote_table(name)] 1547 1548 defp quote_table(name) when is_atom(name), do: quote_table(Atom.to_string(name)) 1549 1550 defp quote_table(name) do 1551 if String.contains?(name, "[") or String.contains?(name, "]") do 1552 error!(nil, "bad table name #{inspect(name)} '[' and ']' are not permitted") 1553 end 1554 1555 "[#{name}]" 1556 end 1557 1558 defp unquoted_name(prefix, name, column_name), 1559 do: unquoted_name(unquoted_name(prefix, name), column_name) 1560 1561 defp unquoted_name(nil, name), do: unquoted_name(name) 1562 1563 defp unquoted_name(prefix, name) do 1564 prefix = if is_atom(prefix), do: Atom.to_string(prefix), else: prefix 1565 name = if is_atom(name), do: Atom.to_string(name), else: name 1566 1567 [prefix, ".", name] 1568 end 1569 1570 defp unquoted_name(name) when is_atom(name), do: unquoted_name(Atom.to_string(name)) 1571 1572 defp unquoted_name(name) do 1573 if String.contains?(name, ["[", "]"]) do 1574 error!(nil, "bad table name #{inspect(name)} '[' and ']' are not permitted") 1575 end 1576 1577 name 1578 end 1579 1580 defp intersperse_map([], _separator, _mapper), do: [] 1581 defp intersperse_map([elem], _separator, mapper), do: mapper.(elem) 1582 1583 defp intersperse_map([elem | rest], separator, mapper) do 1584 [mapper.(elem), separator | intersperse_map(rest, separator, mapper)] 1585 end 1586 1587 defp intersperse_reduce(list, separator, user_acc, reducer, acc \\ []) 1588 1589 defp intersperse_reduce([], _separator, user_acc, _reducer, acc), 1590 do: {acc, user_acc} 1591 1592 defp intersperse_reduce([elem], _separator, user_acc, reducer, acc) do 1593 {elem, user_acc} = reducer.(elem, user_acc) 1594 {[acc | elem], user_acc} 1595 end 1596 1597 defp intersperse_reduce([elem | rest], separator, user_acc, reducer, acc) do 1598 {elem, user_acc} = reducer.(elem, user_acc) 1599 intersperse_reduce(rest, separator, user_acc, reducer, [acc, elem, separator]) 1600 end 1601 1602 defp if_do(condition, value) do 1603 if condition, do: value, else: [] 1604 end 1605 1606 defp escape_string(value) when is_binary(value) do 1607 value |> :binary.replace("'", "''", [:global]) 1608 end 1609 1610 defp ecto_to_db(type, size, precision, scale, query \\ nil) 1611 1612 defp ecto_to_db({:array, _}, _, _, _, query), 1613 do: error!(query, "Array type is not supported by TDS") 1614 1615 defp ecto_to_db(:id, _, _, _, _), do: "bigint" 1616 defp ecto_to_db(:serial, _, _, _, _), do: "int IDENTITY(1,1)" 1617 defp ecto_to_db(:bigserial, _, _, _, _), do: "bigint IDENTITY(1,1)" 1618 defp ecto_to_db(:binary_id, _, _, _, _), do: "uniqueidentifier" 1619 defp ecto_to_db(:boolean, _, _, _, _), do: "bit" 1620 defp ecto_to_db(:string, nil, _, _, _), do: "nvarchar(255)" 1621 defp ecto_to_db(:string, :max, _, _, _), do: "nvarchar(max)" 1622 defp ecto_to_db(:string, s, _, _, _) when s in 1..4_000, do: "nvarchar(#{s})" 1623 defp ecto_to_db(:float, nil, _, _, _), do: "float" 1624 defp ecto_to_db(:float, s, _, _, _) when s in 1..53, do: "float(#{s})" 1625 defp ecto_to_db(:binary, nil, _, _, _), do: "varbinary(max)" 1626 defp ecto_to_db(:binary, s, _, _, _) when s in 1..8_000, do: "varbinary(#{s})" 1627 defp ecto_to_db(:uuid, _, _, _, _), do: "uniqueidentifier" 1628 defp ecto_to_db(:map, nil, _, _, _), do: "nvarchar(max)" 1629 defp ecto_to_db(:map, s, _, _, _) when s in 0..4_000, do: "nvarchar(#{s})" 1630 defp ecto_to_db({:map, _}, nil, _, _, _), do: "nvarchar(max)" 1631 defp ecto_to_db({:map, _}, s, _, _, _) when s in 1..4_000, do: "nvarchar(#{s})" 1632 defp ecto_to_db(:time, _, _, _, _), do: "time(0)" 1633 defp ecto_to_db(:time_usec, _, p, _, _) when p in 0..7, do: "time(#{p})" 1634 defp ecto_to_db(:time_usec, _, _, _, _), do: "time(6)" 1635 defp ecto_to_db(:utc_datetime, _, _, _, _), do: "datetime" 1636 defp ecto_to_db(:utc_datetime_usec, _, p, _, _) when p in 0..7, do: "datetime2(#{p})" 1637 defp ecto_to_db(:utc_datetime_usec, _, _, _, _), do: "datetime2(6)" 1638 defp ecto_to_db(:naive_datetime, _, _, _, _), do: "datetime" 1639 defp ecto_to_db(:naive_datetime_usec, _, p, _, _) when p in 0..7, do: "datetime2(#{p})" 1640 defp ecto_to_db(:naive_datetime_usec, _, _, _, _), do: "datetime2(6)" 1641 1642 defp ecto_to_db(other, size, _, _, _) when is_integer(size) do 1643 "#{Atom.to_string(other)}(#{size})" 1644 end 1645 1646 defp ecto_to_db(other, _, precision, scale, _) when is_integer(precision) do 1647 "#{Atom.to_string(other)}(#{precision},#{scale || 0})" 1648 end 1649 1650 defp ecto_to_db(atom, nil, nil, nil, _) when is_atom(atom) do 1651 Atom.to_string(atom) 1652 end 1653 1654 defp ecto_to_db(type, _, _, _, _) do 1655 raise ArgumentError, 1656 "unsupported type `#{inspect(type)}`. The type can either be an atom, a string " <> 1657 "or a tuple of the form `{:map, t}` where `t` itself follows the same conditions." 1658 end 1659 1660 defp error!(nil, message) do 1661 raise ArgumentError, message 1662 end 1663 1664 defp error!(query, message) do 1665 raise Ecto.QueryError, query: query, message: message 1666 end 1667 1668 defp if_table_not_exists(condition, name, prefix) do 1669 if_do(condition, [ 1670 "IF NOT EXISTS (SELECT * FROM [INFORMATION_SCHEMA].[TABLES] ", 1671 "WHERE ", 1672 "[TABLE_NAME] = ", 1673 ?', 1674 "#{name}", 1675 ?', 1676 if_do(prefix != nil, [ 1677 " AND [TABLE_SCHEMA] = ", 1678 ?', 1679 "#{prefix}", 1680 ?' 1681 ]), 1682 ") " 1683 ]) 1684 end 1685 1686 defp if_table_exists(condition, name, prefix) do 1687 if_do(condition, [ 1688 "IF EXISTS (SELECT * FROM [INFORMATION_SCHEMA].[TABLES] ", 1689 "WHERE ", 1690 "[TABLE_NAME] = ", 1691 ?', 1692 "#{name}", 1693 ?', 1694 if_do(prefix != nil, [ 1695 " AND [TABLE_SCHEMA] = ", 1696 ?', 1697 "#{prefix}", 1698 ?' 1699 ]), 1700 ") " 1701 ]) 1702 end 1703 1704 defp if_column_exists(prefix, table, column_name) do 1705 [ 1706 "IF EXISTS (SELECT 1 FROM [sys].[columns] ", 1707 "WHERE [name] = N'#{column_name}' AND ", 1708 "[object_id] = OBJECT_ID(N'", 1709 if_do(prefix != nil, ["#{prefix}", ?.]), 1710 "#{table}", 1711 "')) " 1712 ] 1713 end 1714 1715 defp if_column_not_exists(prefix, table, column_name) do 1716 [ 1717 "IF NOT EXISTS (SELECT 1 FROM [sys].[columns] ", 1718 "WHERE [name] = N'#{column_name}' AND ", 1719 "[object_id] = OBJECT_ID(N'", 1720 if_do(prefix != nil, ["#{prefix}", ?.]), 1721 "#{table}", 1722 "')) " 1723 ] 1724 end 1725 1726 defp list_param_to_args(idx, length) do 1727 Enum.map_join(1..length, ",", &"@#{idx + &1}") 1728 end 1729 1730 defp as_string(atom) when is_atom(atom), do: Atom.to_string(atom) 1731 defp as_string(str), do: str 1732 1733 defp if_index_exists(condition, index_name, table_name) do 1734 if_do(condition, [ 1735 "IF EXISTS (SELECT name FROM sys.indexes WHERE name = N'", 1736 as_string(index_name), 1737 "' AND object_id = OBJECT_ID(N'", 1738 as_string(table_name), 1739 "')) " 1740 ]) 1741 end 1742 1743 defp if_index_not_exists(condition, index_name, table_name) do 1744 if_do(condition, [ 1745 "IF NOT EXISTS (SELECT name FROM sys.indexes WHERE name = N'", 1746 as_string(index_name), 1747 "' AND object_id = OBJECT_ID(N'", 1748 as_string(table_name), 1749 "')) " 1750 ]) 1751 end 1752 1753 defp if_check_constraint_exists(condition, name, prefix) do 1754 if_do(condition, [ 1755 "IF NOT EXISTS (SELECT * ", 1756 "FROM [INFORMATION_SCHEMA].[CHECK_CONSTRAINTS] ", 1757 "WHERE [CONSTRAINT_NAME] = N'#{name}'", 1758 if_do(prefix != nil, [ 1759 " AND [CONSTRAINT_SCHEMA] = N'#{prefix}'" 1760 ]), 1761 ") " 1762 ]) 1763 end 1764 1765 # types 1766 # "U" - table, 1767 # "C", "PK", "UQ", "F ", "D " - constraints 1768 defp if_object_exists(name, type, statement) do 1769 [ 1770 "IF (OBJECT_ID(N'", 1771 name, 1772 "', '", 1773 type, 1774 "') IS NOT NULL) ", 1775 statement 1776 ] 1777 end 1778 1779 defp if_exists_drop_constraint(name, statement_prefix) do 1780 [ 1781 if_object_exists( 1782 name, 1783 "D", 1784 "#{statement_prefix}DROP CONSTRAINT #{name}; " 1785 ) 1786 ] 1787 end 1788 end 1789 end