{"id":18029095,"url":"https://github.com/svan-jansson/ex_sql_client","last_synced_at":"2026-05-18T03:08:56.697Z","repository":{"id":62429426,"uuid":"227447164","full_name":"svan-jansson/ex_sql_client","owner":"svan-jansson","description":"Microsoft SQL Server Driver for Elixir","archived":false,"fork":false,"pushed_at":"2024-01-16T20:02:29.000Z","size":50,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-10T11:37:56.887Z","etag":null,"topics":["elixir","mssql","mssql-driver"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/svan-jansson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-12-11T19:47:20.000Z","updated_at":"2020-07-16T13:49:22.000Z","dependencies_parsed_at":"2024-12-18T00:41:38.291Z","dependency_job_id":null,"html_url":"https://github.com/svan-jansson/ex_sql_client","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/svan-jansson%2Fex_sql_client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svan-jansson%2Fex_sql_client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svan-jansson%2Fex_sql_client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svan-jansson%2Fex_sql_client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/svan-jansson","download_url":"https://codeload.github.com/svan-jansson/ex_sql_client/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247249537,"owners_count":20908212,"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":["elixir","mssql","mssql-driver"],"created_at":"2024-10-30T09:08:03.632Z","updated_at":"2026-05-18T03:08:56.691Z","avatar_url":"https://github.com/svan-jansson.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"logo/ex_sql_client.svg\" alt=\"netler logo\" height=\"150px\"\u003e\n\u003c/p\u003e\n\n[![Build Status](https://github.com/svan-jansson/ex_sql_client/actions/workflows/build-test-publish.yml/badge.svg)](https://github.com/svan-jansson/ex_sql_client/actions/workflows/build-test-publish.yml)\n[![Hex pm](https://img.shields.io/hexpm/v/ex_sql_client.svg?style=flat)](https://hex.pm/packages/ex_sql_client)\n[![Hex pm](https://img.shields.io/hexpm/dt/ex_sql_client.svg?style=flat)](https://hex.pm/packages/ex_sql_client)\n\n# ExSqlClient\n\nMicrosoft SQL Server driver for Elixir based on [Netler](https://github.com/svan-jansson/netler) and .NET's `System.Data.SqlClient`.\n\n## Goals\n\n- Provide a user friendly interface for interacting with MSSQL\n- Provide comprehensible type mappings between MSSQL and Elixir\n- Real-life implementation of a `Netler` use case to help discover issues and use as proof-of-concept\n\n## Checklist\n\n- ☑ Support encrypted connections\n- ☑ Support multiple result sets\n- ☑ Implement the `DbConnection` behaviour\n  - ☑ Connect\n  - ☑ Disconnect\n  - ☑ Execute\n  - ☑ Transactions\n  - ☑ Prepared Statements\n- ☑ Release first version on hex.pm\n- ☑ Provide an `Ecto.Adapter` that is compatible with Ecto 3\n\n## Installation\n\nAdd `ex_sql_client` to your dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:ex_sql_client, \"~\u003e 0.4\"}\n  ]\nend\n```\n\nTo use the Ecto adapter, also add `ecto` and `ecto_sql`:\n\n```elixir\ndef deps do\n  [\n    {:ex_sql_client, \"~\u003e 0.4\"},\n    {:ecto, \"~\u003e 3.10\"},\n    {:ecto_sql, \"~\u003e 3.10\"}\n  ]\nend\n```\n\n---\n\n## Using ExSqlClient Directly\n\nUse this approach when you want low-level access to SQL Server without Ecto, or when you need to run raw DDL, stored procedures, or arbitrary queries.\n\n### Connecting\n\nStart a connection using a standard ADO.NET connection string:\n\n```elixir\n{:ok, conn} =\n  ExSqlClient.start_link(\n    connection_string:\n      \"Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;\"\n  )\n```\n\n### Executing Queries\n\nPass parameters as a map with string keys. Use `@paramName` placeholders in your SQL:\n\n```elixir\n{:ok, rows} =\n  ExSqlClient.query(conn, \"SELECT * FROM [records] WHERE [status] = @status\", %{status: 1})\n\n# rows is a list of maps, one map per row, with string column names as keys\n# e.g. [%{\"id\" =\u003e 1, \"status\" =\u003e 1, \"name\" =\u003e \"foo\"}, ...]\n```\n\nQueries with no parameters:\n\n```elixir\n{:ok, rows} = ExSqlClient.query(conn, \"SELECT @@VERSION\", %{})\n```\n\n### Transactions\n\n```elixir\nDBConnection.transaction(conn, fn conn -\u003e\n  {:ok, _} = ExSqlClient.query(conn, \"INSERT INTO [orders] ([ref]) VALUES (@ref)\", %{ref: \"ORD-1\"})\n  {:ok, _} = ExSqlClient.query(conn, \"UPDATE [stock] SET [qty] = [qty] - 1 WHERE [id] = @id\", %{id: 42})\nend)\n```\n\n### Prepared Statements\n\n```elixir\nquery = %ExSqlClient.Query{statement: \"SELECT * FROM [users] WHERE [email] = @email\"}\n\n{:ok, query} = DBConnection.prepare(conn, query)\n{:ok, rows}  = DBConnection.execute(conn, query, %{email: \"user@example.com\"})\n:ok          = DBConnection.close(conn, query)\n```\n\n---\n\n## Using the Ecto Adapter\n\n`ExSqlClient.Ecto` is a full `Ecto.Adapters.SQL` adapter for Microsoft SQL Server. It generates MSSQL-dialect SQL (bracket identifiers, `TOP(n)`, `OFFSET…FETCH`, `OUTPUT INSERTED/DELETED` for returning) and maps Ecto types to SQL Server column types.\n\n### Setting Up a Repo\n\n```elixir\ndefmodule MyApp.Repo do\n  use Ecto.Repo,\n    otp_app: :my_app,\n    adapter: ExSqlClient.Ecto\nend\n```\n\n### Configuration\n\n```elixir\n# config/config.exs \nconfig :my_app, MyApp.Repo,\n  connection_string:\n    \"Server=tcp:db.example.com,1433;Database=mydb;User Id=myapp_user;Password=secret;Encrypt=True\"\n```\n\nAdd the repo to your application's supervision tree:\n\n```elixir\ndef start(_type, _args) do\n  children = [\n    MyApp.Repo\n  ]\n  Supervisor.start_link(children, strategy: :one_for_one)\nend\n```\n\n### Schema Example\n\n```elixir\ndefmodule MyApp.User do\n  use Ecto.Schema\n\n  schema \"users\" do\n    field :name,  :string\n    field :email, :string\n    field :active, :boolean, default: true\n    timestamps()\n  end\nend\n```\n\n### Query Examples\n\n```elixir\n# Fetch all active users\nMyApp.Repo.all(from u in MyApp.User, where: u.active == true)\n\n# Insert a record and return it\n{:ok, user} = MyApp.Repo.insert(%MyApp.User{name: \"Alice\", email: \"alice@example.com\"})\n\n# Update\nMyApp.Repo.update_all(from(u in MyApp.User, where: u.active == false), set: [name: \"Deactivated\"])\n\n# Delete\nMyApp.Repo.delete_all(from u in MyApp.User, where: u.email == ^\"old@example.com\")\n\n# Raw SQL via the Ecto adapter\n{:ok, result} = MyApp.Repo.query(\"SELECT @@VERSION\")\n```\n\n### Known Limitations\n\n| Feature | Status |\n|---|---|\n| Migrations / DDL | Not supported — use `ExSqlClient.query/3` directly for DDL |\n| `Repo.stream/2` | Raises at runtime — cursors are not supported by the protocol |\n| `query_many/4` | Raises at runtime — multiple result sets are not supported |\n| `on_conflict` | Only `:raise` is supported |\n| Window functions | Not supported |\n| Materialized CTEs | Not supported |\n| `DISTINCT` on multiple columns | Not supported; use `distinct: true` for a distinct result set |\n| Aggregate filters (`filter/2`) | Not supported |\n| `json_extract_path` | Not supported; use `fragment/1` with `JSON_VALUE`/`JSON_QUERY` instead |\n| `OFFSET` without `ORDER BY` | Raises at compile time — SQL Server requires `ORDER BY` when using `OFFSET` |\n| `OFFSET` without `LIMIT` | Raises at compile time |\n\n---\n\n## Performance\n\nExSqlClient uses [Netler](https://github.com/svan-jansson/netler) to communicate with a .NET worker process over a local TCP socket using MessagePack serialisation. Every query involves at least one Elixir → .NET → SQL Server → .NET → Elixir round-trip.\n\n### Benchmark highlights\n\nMeasured with `mix run bench/benchmarks.exs` against SQL Server 2022 in a local container. Single-process, `pool_size: 5` for query scenarios, `pool_size: 1` for prepared statements. Machine: Intel Core Ultra 9 285H, Elixir 1.19.5, Erlang/OTP 28.\n\n| Scenario | Median latency | Throughput |\n|---|---|---|\n| Netler IPC only (no SQL) | 0.022 ms | ~26 000 req/s |\n| SELECT constant (`SELECT 1`) | 0.95 ms | ~900 req/s |\n| SELECT 1 row | 0.94 ms | ~900 req/s |\n| SELECT 1 row, parameterised | 1.13 ms | ~760 req/s |\n| SELECT 10 rows | 1.17 ms | ~770 req/s |\n| SELECT 100 rows | 1.37 ms | ~670 req/s |\n| Prepared statement (SELECT 1 row) | 0.40 ms | ~1 400 req/s |\n| INSERT | 5.34 ms | ~180 req/s |\n| Transaction (INSERT + commit) | 6.38 ms | ~150 req/s |\n\n### What the numbers mean\n\n**Netler IPC overhead is negligible.** The raw IPC round-trip (no SQL) costs ~0.02 ms. The ~1 ms you see on a simple SELECT is almost entirely SQL Server query execution and ADO.NET overhead — not the Elixir↔.NET transport.\n\n**Prepared statements halve read latency.** Reusing a prepared statement drops median latency from ~0.94 ms to ~0.40 ms by skipping the SQL Server parse/compile step on repeated identical queries.\n\n**Row count has modest impact on reads.** Fetching 100 rows takes ~1.37 ms vs ~0.94 ms for 1 row — the extra 0.4 ms is serialisation and transfer of the additional data.\n\n**Write operations are slower due to SQL Server I/O.** An INSERT takes ~5.3 ms; wrapping it in an explicit transaction adds ~1 ms for the `BEGIN`/`COMMIT` round-trips.\n\n**Throughput scales with pool size.** The figures above are for a single Elixir process. With a larger `pool_size` and concurrent callers, total throughput grows proportionally up to the SQL Server's own limits.\n\n### Running the benchmarks yourself\n\n```bash\n# Uses Testcontainers to spin up SQL Server automatically\nmix run bench/benchmarks.exs\n\n# Or point at an existing SQL Server instance\nMSSQL_CONNECTION_STRING=\"Server=...;...\" mix run bench/benchmarks.exs\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvan-jansson%2Fex_sql_client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsvan-jansson%2Fex_sql_client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvan-jansson%2Fex_sql_client/lists"}