- Notifications
You must be signed in to change notification settings - Fork 849
/
Copy pathfind-sql-with-user-defined-functions.sql
305 lines (284 loc) · 10.4 KB
/
find-sql-with-user-defined-functions.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/*
When a SQL statement executes a user-defined function, your users pay
the price of a context switch, which can be expensive, especially if
the function is called in the WHERE clause. Even worse, if that function
itself contains a SQL statement, you can run into data consistency issues.
Fortunately, you can use PL/Scope in 12.2 to find all the SQL statements
in your PL/SQL code that call a user-defined function, and then analyze from there.
*/
ALTER SESSION SET plscope_settings='identifiers:all, statements:all';
CREATE TABLE my_data (n NUMBER);
CREATE OR REPLACE FUNCTION my_function1
RETURN NUMBER
AUTHID DEFINER
IS
BEGIN
RETURN 1;
END;
/
CREATE OR REPLACE FUNCTION my_function2
RETURN NUMBER
AUTHID DEFINER
IS
BEGIN
RETURN 1;
END;
/
CREATE OR REPLACE PROCEDURE my_procedure (n_in IN NUMBER)
AUTHID DEFINER
IS
l_my_data my_data%ROWTYPE;
BEGIN
SELECT my_function1 ()
INTO l_my_data
FROM my_data
WHERE n = n_in
AND my_function2 () = 0
AND n = (SELECT my_function1 () FROM DUAL);
SELECT COUNT (*)
INTO l_my_data
FROM my_data
WHERE n = n_in;
UPDATE my_data
SET n = my_function2 ()
WHERE n = n_in;
END;
/
-- Show All Identifiers and Statements - ALL_* Version
-- This query unions together rows from ALL_IDENTIFIERS and ALL_STATEMENTS to provide
-- a complete picture of your program unit.
WITH one_obj_name AS (SELECT USER owner, 'MY_PROCEDURE' object_name FROM DUAL)
SELECT plscope_type,
usage_id,
usage_context_id,
LPAD (' ', 2 * (LEVEL - 1)) || usage || ' ' || name usages,
line,
col,
signature
FROM (SELECT 'ID' plscope_type,
ai.object_name,
ai.usage usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE || ' ' || ai.name name,
ai.line,
ai.col,
signature
FROM all_identifiers ai, one_obj_name
WHERE ai.object_name = one_obj_name.object_name
AND ai.owner = one_obj_name.owner
UNION ALL
SELECT 'ST',
st.object_name,
st.TYPE,
st.usage_id,
st.usage_context_id,
'STATEMENT',
st.line,
st.col,
signature
FROM all_statements st, one_obj_name
WHERE st.object_name = one_obj_name.object_name
AND st.owner = one_obj_name.owner)
START WITH usage_context_id = 0
CONNECT BY PRIOR usage_id = usage_context_id
-- Show All Identifiers and Statements - USER_* Version
WITH one_obj_name AS (SELECT 'MY_PROCEDURE' object_name FROM DUAL)
SELECT plscope_type,
usage_id,
usage_context_id,
LPAD (' ', 2 * (LEVEL - 1)) || usage || ' ' || name usages,
line,
col,
signature
FROM (SELECT 'ID' plscope_type,
ai.object_name,
ai.usage usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE || ' ' || ai.name name,
ai.line,
ai.col,
signature
FROM user_identifiers ai, one_obj_name
WHERE ai.object_name = one_obj_name.object_name
UNION ALL
SELECT 'ST',
st.object_name,
st.TYPE,
st.usage_id,
st.usage_context_id,
'STATEMENT',
st.line,
st.col,
signature
FROM user_statements st, one_obj_name
WHERE st.object_name = one_obj_name.object_name)
START WITH usage_context_id = 0
CONNECT BY PRIOR usage_id = usage_context_id
-- Find SQL Statements Containing Function Calls - ALL_* Version
/*
Here's the secret sauce. I use subquery refactoring (WITH clause) to create
and then use some data sets: my_prog_unit - specify the program unit of interest
just once; full_set - the full set of statements and identifiers; dml_statements -
the SQL DML statements in the program unit. Then I find all the DML statements
whose full_set tree below it contain a call to a function.
*/
WITH my_prog_unit AS (SELECT USER owner, 'MY_PROCEDURE' object_name FROM DUAL),
full_set
AS (SELECT ai.usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE,
ai.name
FROM all_identifiers ai, my_prog_unit
WHERE ai.object_name = my_prog_unit.object_name
AND ai.owner = my_prog_unit.owner
UNION ALL
SELECT st.TYPE,
st.usage_id,
st.usage_context_id,
'type',
'name'
FROM all_statements st, my_prog_unit
WHERE st.object_name = my_prog_unit.object_name
AND st.owner = my_prog_unit.owner),
dml_statements
AS (SELECT st.owner, st.object_name, st.line, st.usage_id, st.type
FROM all_statements st, my_prog_unit
WHERE st.object_name = my_prog_unit.object_name
AND st.owner = my_prog_unit.owner
AND st.TYPE IN ('SELECT', 'UPDATE', 'DELETE'))
SELECT st.owner,
st.object_name,
st.line,
st.TYPE,
s.text
FROM dml_statements st, all_source s
WHERE ('CALL', 'FUNCTION') IN ( SELECT fs.usage, fs.TYPE
FROM full_set fs
CONNECT BY PRIOR fs.usage_id =
fs.usage_context_id
START WITH fs.usage_id = st.usage_id)
AND st.line = s.line
AND st.object_name = s.name
AND st.owner = s.owner
-- Find SQL Statements Containing Function Calls - USER_* Version
WITH my_prog_unit AS (SELECT 'MY_PROCEDURE' object_name FROM DUAL),
full_set
AS (SELECT ai.usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE,
ai.name
FROM user_identifiers ai, my_prog_unit
WHERE ai.object_name = my_prog_unit.object_name
/* Only with ALL_* AND ai.owner = my_prog_unit.owner */
UNION ALL
SELECT st.TYPE,
st.usage_id,
st.usage_context_id,
'type',
'name'
FROM user_statements st, my_prog_unit
WHERE st.object_name = my_prog_unit.object_name
/* Only with ALL_* AND st.owner = my_prog_unit.owner */),
dml_statements
AS (SELECT /* Only with ALL_* st.owner, */ st.object_name, st.line, st.usage_id, st.type
FROM user_statements st, my_prog_unit
WHERE st.object_name = my_prog_unit.object_name
/* Only with ALL_* AND st.owner = my_prog_unit.owner */
AND st.TYPE IN ('SELECT', 'UPDATE', 'DELETE'))
SELECT /* Only with ALL_* st.owner, */
st.object_name,
st.line,
st.TYPE,
s.text
FROM dml_statements st, all_source s
WHERE ('CALL', 'FUNCTION') IN ( SELECT fs.usage, fs.TYPE
FROM full_set fs
CONNECT BY PRIOR fs.usage_id =
fs.usage_context_id
START WITH fs.usage_id = st.usage_id)
AND st.line = s.line
/* Only with ALL_* AND st.owner = s.owner */
AND st.object_name = s.name
-- Across All Schemas, All Program Units
-- Using ALL_* views; will not run in LiveSQL. See next statement.
WITH full_set
AS (SELECT ai.owner,
ai.object_name,
ai.usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE,
ai.name
FROM all_identifiers ai
UNION ALL
SELECT st.owner,
st.object_name,
st.TYPE,
st.usage_id,
st.usage_context_id,
'type',
'name'
FROM all_statements st),
dml_statements
AS (SELECT st.owner,
st.object_name,
st.line,
st.usage_id,
st.TYPE
FROM all_statements st
WHERE st.TYPE IN ('SELECT', 'UPDATE', 'DELETE'))
SELECT st.owner,
st.object_name,
st.line,
st.TYPE,
s.text
FROM dml_statements st, all_source s
WHERE ('CALL', 'FUNCTION') IN ( SELECT fs.usage, fs.TYPE
FROM full_set fs
CONNECT BY PRIOR fs.usage_id =
fs.usage_context_id
START WITH fs.usage_id = st.usage_id)
AND st.line = s.line
AND st.object_name = s.name
AND st.owner = s.owner
-- Across All Program Units in Your Schema
-- Using USER_* views
WITH full_set
AS (SELECT ai.object_name,
ai.usage,
ai.usage_id,
ai.usage_context_id,
ai.TYPE,
ai.name
FROM user_identifiers ai
UNION ALL
SELECT st.object_name,
st.TYPE,
st.usage_id,
st.usage_context_id,
'type',
'name'
FROM user_statements st),
dml_statements
AS (SELECT st.object_name,
st.line,
st.usage_id,
st.TYPE
FROM user_statements st
WHERE st.TYPE IN ('SELECT', 'UPDATE', 'DELETE'))
SELECT st.object_name,
st.line,
st.TYPE,
s.text
FROM user_statements st, user_source s
WHERE ('CALL', 'FUNCTION') IN ( SELECT fs.usage, fs.TYPE
FROM full_set fs
CONNECT BY PRIOR fs.usage_id =
fs.usage_context_id
START WITH fs.usage_id = st.usage_id)
AND st.line = s.line
AND st.object_name = s.name