Skip to content

Commit 287ba0d

Browse files
authored
Use query binding instead of interpolation (#72)
1 parent a63b782 commit 287ba0d

35 files changed

Lines changed: 531 additions & 540 deletions

lib/ecto_psql_extras.ex

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defmodule EctoPSQLExtras do
1212
optional(:args_for_select) => list
1313
}
1414

15-
@callback query :: binary
15+
@callback query(args :: keyword) :: {binary, list}
1616

1717
@type repo :: module() | {module(), node()}
1818

@@ -138,10 +138,13 @@ defmodule EctoPSQLExtras do
138138
query_module = Map.fetch!(queries(repo), name)
139139
opts = prepare_opts(opts, query_module.info()[:default_args])
140140

141+
{statement, params} = query_module.query(Keyword.fetch!(opts, :args))
142+
141143
result =
142144
query!(
143145
repo,
144-
query_module.query(Keyword.fetch!(opts, :args)),
146+
statement,
147+
params,
145148
Keyword.get(opts, :query_opts, @default_query_opts)
146149
)
147150

@@ -152,10 +155,10 @@ defmodule EctoPSQLExtras do
152155
)
153156
end
154157

155-
defp query!(repo, query, query_opts \\ @default_query_opts)
158+
defp query!(repo, query, params \\ [], query_opts \\ @default_query_opts)
156159

157-
defp query!({repo, node}, query, query_opts) do
158-
case :rpc.call(node, repo, :query!, [query, [], query_opts]) do
160+
defp query!({repo, node}, query, params, query_opts) do
161+
case :rpc.call(node, repo, :query!, [query, params, query_opts]) do
159162
{:badrpc, {:EXIT, {:undef, _}}} ->
160163
raise "repository is not defined on remote node"
161164

@@ -167,8 +170,8 @@ defmodule EctoPSQLExtras do
167170
end
168171
end
169172

170-
defp query!(repo, query, query_opts) do
171-
repo.query!(query, [], query_opts)
173+
defp query!(repo, query, params, query_opts) do
174+
repo.query!(query, params, query_opts)
172175
end
173176

174177
@doc """

lib/queries/all_locks.ex

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,23 @@ defmodule EctoPSQLExtras.AllLocks do
1818
end
1919

2020
def query(_args \\ []) do
21-
"""
22-
/* ECTO_PSQL_EXTRAS: Queries with active locks */
21+
{"""
22+
/* ECTO_PSQL_EXTRAS: Queries with active locks */
2323
24-
SELECT
25-
pg_stat_activity.pid,
26-
pg_class.relname,
27-
pg_locks.transactionid,
28-
pg_locks.granted,
29-
pg_locks.mode,
30-
pg_stat_activity.query AS query_snippet,
31-
age(now(),pg_stat_activity.query_start) AS "age"
32-
FROM pg_stat_activity,pg_locks left
33-
OUTER JOIN pg_class
34-
ON (pg_locks.relation = pg_class.oid)
35-
WHERE pg_stat_activity.query <> '<insufficient privilege>'
36-
AND pg_locks.pid = pg_stat_activity.pid
37-
AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
38-
"""
24+
SELECT
25+
pg_stat_activity.pid,
26+
pg_class.relname,
27+
pg_locks.transactionid,
28+
pg_locks.granted,
29+
pg_locks.mode,
30+
pg_stat_activity.query AS query_snippet,
31+
age(now(),pg_stat_activity.query_start) AS "age"
32+
FROM pg_stat_activity,pg_locks left
33+
OUTER JOIN pg_class
34+
ON (pg_locks.relation = pg_class.oid)
35+
WHERE pg_stat_activity.query <> '<insufficient privilege>'
36+
AND pg_locks.pid = pg_stat_activity.pid
37+
AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
38+
""", []}
3939
end
4040
end

lib/queries/bloat.ex

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,69 +17,69 @@ defmodule EctoPSQLExtras.Bloat do
1717
end
1818

1919
def query(_args \\ []) do
20-
"""
21-
/* ECTO_PSQL_EXTRAS: Table and index bloat in your database ordered by most wasteful */
20+
{"""
21+
/* ECTO_PSQL_EXTRAS: Table and index bloat in your database ordered by most wasteful */
2222
23-
WITH constants AS (
24-
SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 4 AS ma
25-
), bloat_info AS (
26-
SELECT
27-
ma,bs,schemaname,tablename,
28-
(datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
29-
(maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
30-
FROM (
31-
SELECT
32-
schemaname, tablename, hdr, ma, bs,
33-
SUM((1-null_frac)*avg_width) AS datawidth,
34-
MAX(null_frac) AS maxfracsum,
35-
hdr+(
36-
SELECT 1+count(*)/8
37-
FROM pg_stats s2
38-
WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
39-
) AS nullhdr
40-
FROM pg_stats s, constants
41-
GROUP BY 1,2,3,4,5
42-
) AS foo
43-
), table_bloat AS (
44-
SELECT
45-
schemaname, tablename, cc.relpages, bs,
46-
CEIL((cc.reltuples*((datahdr+ma-
47-
(CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)) AS otta
48-
FROM bloat_info
49-
JOIN pg_class cc ON cc.relname = bloat_info.tablename
50-
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
51-
), index_bloat AS (
52-
SELECT
53-
schemaname, tablename, bs,
54-
COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
55-
COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
56-
FROM bloat_info
57-
JOIN pg_class cc ON cc.relname = bloat_info.tablename
58-
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
59-
JOIN pg_index i ON indrelid = cc.oid
60-
JOIN pg_class c2 ON c2.oid = i.indexrelid
61-
)
62-
SELECT
63-
type, schemaname, object_name, bloat, waste
64-
FROM
65-
(SELECT
66-
'table' as type,
67-
schemaname,
68-
tablename as object_name,
69-
ROUND(CASE WHEN otta=0 THEN 0.0 ELSE table_bloat.relpages/otta::numeric END,1) AS bloat,
70-
CASE WHEN relpages < otta THEN 0 ELSE (bs*(table_bloat.relpages-otta)::bigint)::bigint END AS waste
71-
FROM
72-
table_bloat
73-
UNION
74-
SELECT
75-
'index' as type,
76-
schemaname,
77-
tablename || '::' || iname as object_name,
78-
ROUND(CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages/iotta::numeric END,1) AS bloat,
79-
CASE WHEN ipages < iotta THEN 0 ELSE (bs*(ipages-iotta))::bigint END AS waste
80-
FROM
81-
index_bloat) bloat_summary
82-
ORDER BY waste DESC, bloat DESC;
83-
"""
23+
WITH constants AS (
24+
SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 4 AS ma
25+
), bloat_info AS (
26+
SELECT
27+
ma,bs,schemaname,tablename,
28+
(datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
29+
(maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
30+
FROM (
31+
SELECT
32+
schemaname, tablename, hdr, ma, bs,
33+
SUM((1-null_frac)*avg_width) AS datawidth,
34+
MAX(null_frac) AS maxfracsum,
35+
hdr+(
36+
SELECT 1+count(*)/8
37+
FROM pg_stats s2
38+
WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
39+
) AS nullhdr
40+
FROM pg_stats s, constants
41+
GROUP BY 1,2,3,4,5
42+
) AS foo
43+
), table_bloat AS (
44+
SELECT
45+
schemaname, tablename, cc.relpages, bs,
46+
CEIL((cc.reltuples*((datahdr+ma-
47+
(CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)) AS otta
48+
FROM bloat_info
49+
JOIN pg_class cc ON cc.relname = bloat_info.tablename
50+
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
51+
), index_bloat AS (
52+
SELECT
53+
schemaname, tablename, bs,
54+
COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
55+
COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
56+
FROM bloat_info
57+
JOIN pg_class cc ON cc.relname = bloat_info.tablename
58+
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
59+
JOIN pg_index i ON indrelid = cc.oid
60+
JOIN pg_class c2 ON c2.oid = i.indexrelid
61+
)
62+
SELECT
63+
type, schemaname, object_name, bloat, waste
64+
FROM
65+
(SELECT
66+
'table' as type,
67+
schemaname,
68+
tablename as object_name,
69+
ROUND(CASE WHEN otta=0 THEN 0.0 ELSE table_bloat.relpages/otta::numeric END,1) AS bloat,
70+
CASE WHEN relpages < otta THEN 0 ELSE (bs*(table_bloat.relpages-otta)::bigint)::bigint END AS waste
71+
FROM
72+
table_bloat
73+
UNION
74+
SELECT
75+
'index' as type,
76+
schemaname,
77+
tablename || '::' || iname as object_name,
78+
ROUND(CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages/iotta::numeric END,1) AS bloat,
79+
CASE WHEN ipages < iotta THEN 0 ELSE (bs*(ipages-iotta))::bigint END AS waste
80+
FROM
81+
index_bloat) bloat_summary
82+
ORDER BY waste DESC, bloat DESC;
83+
""", []}
8484
end
8585
end

lib/queries/blocking.ex

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@ defmodule EctoPSQLExtras.Blocking do
1717
end
1818

1919
def query(_args \\ []) do
20-
"""
21-
/* ECTO_PSQL_EXTRAS: Queries holding locks other queries are waiting to be released */
20+
{"""
21+
/* ECTO_PSQL_EXTRAS: Queries holding locks other queries are waiting to be released */
2222
23-
SELECT bl.pid AS blocked_pid,
24-
ka.query AS blocking_statement,
25-
now() - ka.query_start AS blocking_duration,
26-
kl.pid AS blocking_pid,
27-
a.query AS blocked_statement,
28-
now() - a.query_start AS blocked_duration
29-
FROM pg_catalog.pg_locks bl
30-
JOIN pg_catalog.pg_stat_activity a
31-
ON bl.pid = a.pid
32-
JOIN pg_catalog.pg_locks kl
33-
JOIN pg_catalog.pg_stat_activity ka
34-
ON kl.pid = ka.pid
35-
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
36-
WHERE NOT bl.granted;
37-
"""
23+
SELECT bl.pid AS blocked_pid,
24+
ka.query AS blocking_statement,
25+
now() - ka.query_start AS blocking_duration,
26+
kl.pid AS blocking_pid,
27+
a.query AS blocked_statement,
28+
now() - a.query_start AS blocked_duration
29+
FROM pg_catalog.pg_locks bl
30+
JOIN pg_catalog.pg_stat_activity a
31+
ON bl.pid = a.pid
32+
JOIN pg_catalog.pg_locks kl
33+
JOIN pg_catalog.pg_stat_activity ka
34+
ON kl.pid = ka.pid
35+
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
36+
WHERE NOT bl.granted;
37+
""", []}
3838
end
3939
end

lib/queries/cache_hit.ex

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ defmodule EctoPSQLExtras.CacheHit do
1313
end
1414

1515
def query(_args \\ []) do
16-
"""
17-
/* ECTO_PSQL_EXTRAS: Index and table hit rate */
16+
{"""
17+
/* ECTO_PSQL_EXTRAS: Index and table hit rate */
1818
19-
SELECT
20-
'index hit rate' AS name,
21-
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS ratio
22-
FROM pg_statio_user_indexes
23-
UNION ALL
24-
SELECT
25-
'table hit rate' AS name,
26-
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS ratio
27-
FROM pg_statio_user_tables;
28-
"""
19+
SELECT
20+
'index hit rate' AS name,
21+
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS ratio
22+
FROM pg_statio_user_indexes
23+
UNION ALL
24+
SELECT
25+
'table hit rate' AS name,
26+
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS ratio
27+
FROM pg_statio_user_tables;
28+
""", []}
2929
end
3030
end

lib/queries/calls.ex

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,18 @@ defmodule EctoPSQLExtras.Calls do
1818
end
1919

2020
def query(args \\ []) do
21-
"""
22-
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
21+
{"""
22+
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
2323
24-
SELECT query AS query,
25-
interval '1 millisecond' * total_exec_time AS exec_time,
26-
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
27-
calls,
28-
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
29-
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
30-
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
31-
ORDER BY calls DESC
32-
LIMIT <%= limit %>;
33-
"""
34-
|> EEx.eval_string(args)
24+
SELECT query AS query,
25+
interval '1 millisecond' * total_exec_time AS exec_time,
26+
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
27+
calls,
28+
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
29+
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
30+
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
31+
ORDER BY calls DESC
32+
LIMIT $1;
33+
""", [args[:limit]]}
3534
end
3635
end

lib/queries/calls_17.ex

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,18 @@ defmodule EctoPSQLExtras.Calls17 do
1818
end
1919

2020
def query(args \\ []) do
21-
"""
22-
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
21+
{"""
22+
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
2323
24-
SELECT query AS query,
25-
interval '1 millisecond' * total_exec_time AS exec_time,
26-
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
27-
calls,
28-
interval '1 millisecond' * (shared_blk_read_time + shared_blk_write_time) AS sync_io_time
29-
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
30-
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
31-
ORDER BY calls DESC
32-
LIMIT <%= limit %>;
33-
"""
34-
|> EEx.eval_string(args)
24+
SELECT query AS query,
25+
interval '1 millisecond' * total_exec_time AS exec_time,
26+
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
27+
calls,
28+
interval '1 millisecond' * (shared_blk_read_time + shared_blk_write_time) AS sync_io_time
29+
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
30+
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
31+
ORDER BY calls DESC
32+
LIMIT $1;
33+
""", [args[:limit]]}
3534
end
3635
end

lib/queries/calls_legacy.ex

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,18 @@ defmodule EctoPSQLExtras.CallsLegacy do
1717
end
1818

1919
def query(args \\ []) do
20-
"""
21-
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
20+
{"""
21+
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
2222
23-
SELECT query AS query,
24-
interval '1 millisecond' * total_time AS exec_time,
25-
(total_time/sum(total_time) OVER()) AS exec_time_ratio,
26-
calls,
27-
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
28-
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
29-
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
30-
ORDER BY calls DESC
31-
LIMIT <%= limit %>;
32-
"""
33-
|> EEx.eval_string(args)
23+
SELECT query AS query,
24+
interval '1 millisecond' * total_time AS exec_time,
25+
(total_time/sum(total_time) OVER()) AS exec_time_ratio,
26+
calls,
27+
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
28+
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
29+
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
30+
ORDER BY calls DESC
31+
LIMIT $1;
32+
""", [args[:limit]]}
3433
end
3534
end

0 commit comments

Comments
 (0)