Sqlcommenter (sqlcommenter v0.2.0-beta.2)
Sqlcommenter
Disclaimer: Currently using comments disables prepared statements and may increase cpu load on your database. Elixir implementation of sqlcommenter escaping. Attach SQL comments to correlate user code in ORMs and SQL drivers with SQL statements.
Usage
Add these callbacks to your Repo module, adjust to your needs. The stacktrace option is only required when you plan to include use the extractrepocaller function. This function also accepts the __MODULE as the second argument to exclude it from the stacktrace.
The sqlcommenter option is used for adding static data to the comment like the running app, team, owner etc. Any dynamic data can be added in the prepare_query function. Besides the caller it can also add the trace and span.
The generated comment needs to be added as comment option - this will be passed to the adapter.
def default_options(_operation) do
[stacktrace: true, prepare: :unnamed, sqlcommenter: [team: "sqlcomm", app: "sqlcomm"]]
end
def prepare_query(_operation, query, opts) do
sqlcommennter_defaults = Keyword.get(opts, :sqlcommenter)
caller = Sqlcommenter.extract_repo_caller(opts, __MODULE__)
comment = Sqlcommenter.to_str([caller: caller] ++ sqlcommenter_defaults)
{query, [comment: comment] ++ opts}
end
Now your postgres logs should look will return a log line like this:
2024-10-27 21:43:04.331 GMT,"postgres","sqlcomm_test",416336,"127.0.0.1:53348",671eb3e8.65a50,6, "SELECT",2024-10-27 21:43:04 GMT,15/54,61620,LOG,00000,"execute <unnamed>: SELECT u0.""id"",
u0.""active"", u0.""name"", u0.""inserted_at"", u0.""updated_at"" FROM ""users"" AS
u0/*app='sqlcomm',caller='Elixir.SqlcommTest.test%20insert%20user%2F1',team='sqlcomm'*/"
,,,,,,,,,"","client backend",,0
Alternatively, when you're concerned about performance and your options are mostly static you can also omit the Sqlcommenter logic and write your own custom function. Just remember that according the sqlcommenter specs the keys must be sorted, and the values properly escaped check specification and sqlcommenter source code.
defmodule SqlEEx do
require EEx
EEx.function_from_string(
:def,
:to_comment,
"app:'sqlcomm',caller:'<%= caller %>'team:'sqlcomm'",
[:caller]
)
end
And then use it in your repo:
def prepare_query(_operation, query, opts) do
caller = Sqlcommenter.extract_repo_caller(opts, __MODULE__)
SqlEEx.to_comment(caller: caller)
{query, [comment: comment] ++ opts}
end
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/sqlcommenter.
Summary
Functions
Appends serialized data to query
Appends serialized data to query
extracts serialized data from query
The same as to_iodata but it assumes the keys are sorted already.
Encodes enumerable to iodata iex> Sqlcommenter.to_iodata(controller: :person, function: :index) [
Encodes enumerable to string iex> Sqlcommenter.to_str(controller: :person, function: :index) "controller='person',function='index'"
Functions
append_to_io_query(query, params)
Appends serialized data to query
iex> query = ["SELECT", [~s{p0."id"}, ", ", ~s{p0."first_name"}], " FROM ", ~s{"person"."person"}, " AS ", "p0"] iex> Sqlcommenter.append_to_io_query(query, %{controller: :person, function: :index}) [ ["SELECT", [~s{p0."id"}, ", ", ~s{p0."first_name"}], " FROM ", ~s{"person"."person"}, " AS ", "p0"], " /*", [
["controller", "='", "person", "'"],
",",
["function", "='", "index", "'"]
], "*/"]
append_to_query(query, params)
Appends serialized data to query
iex> query = ~s{SELECT p0."id", p0."first_name" FROM "person"."person" AS p0} iex> Sqlcommenter.append_to_query(query, %{controller: :person, function: :index}) ~s{SELECT p0."id", p0."first_name" FROM "person"."person" AS p0 } <> "/controller='person',function='index'/"
deserialize(query)
extracts serialized data from query
Example
iex> query = ~s{SELECT p0."id", p0."first_name" FROM "person"."person" AS p0 /request_id='fa2af7b2-d8e1-4e8f-8820-3fd648b73187'/} iex> Sqlcommenter.deserialize(query) %{"request_id" => "fa2af7b2-d8e1-4e8f-8820-3fd648b73187"}
extract_repo_caller(opts, repo_module)
sorted_to_iodata(params)
The same as to_iodata but it assumes the keys are sorted already.
to_iodata(params)
@spec to_iodata(Keyword.t()) :: maybe_improper_list()
Encodes enumerable to iodata iex> Sqlcommenter.to_iodata(controller: :person, function: :index) [
["controller", "='", "person", "'"],
",",
["function", "='", "index", "'"]
]
to_str(params)
Encodes enumerable to string iex> Sqlcommenter.to_str(controller: :person, function: :index) "controller='person',function='index'"