-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathmytap-routines.sql
502 lines (400 loc) · 17.1 KB
/
mytap-routines.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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
-- ROUTINES
-- ========
USE tap;
DELIMITER //
-- work around for STRICT MODE in 5.7
SELECT tap.mysql_version() INTO @version //
SET @mode = (SELECT @@SESSION.sql_mode) //
SET @@SESSION.sql_mode = '' //
/****************************************************************************/
-- internal function to check
DROP FUNCTION IF EXISTS _has_routine //
CREATE FUNCTION _has_routine(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9))
RETURNS BOOLEAN
DETERMINISTIC
COMMENT 'Internal boolean test for the existence of a named routine within the given schema.'
BEGIN
DECLARE ret BOOLEAN;
SELECT 1 INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname
AND `routine_type` = rtype;
RETURN COALESCE(ret,0);
END //
-- has_routine(schema, routine, type, description)
DROP FUNCTION IF EXISTS has_routine //
CREATE FUNCTION has_routine(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Test for the existence of a named routine within a given schema.'
BEGIN
IF description = '' THEN
SET description = CONCAT(rtype ,' ',
quote_ident(sname), '.', quote_ident(rname), ' should exist');
END IF;
RETURN ok(_has_routine(sname, rname, rtype), description);
END //
-- hasnt_routine(schema, name, type, description)
DROP FUNCTION IF EXISTS hasnt_routine //
CREATE FUNCTION hasnt_routine(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a named routine does not exist within the given schema.'
BEGIN
IF description = '' THEN
SET description = CONCAT(rtype ,' ',
quote_ident(sname), '.', quote_ident(rname), ' should not exist');
END IF;
RETURN ok(NOT _has_routine(sname, rname, rtype), description);
END //
DROP FUNCTION IF EXISTS has_function //
CREATE FUNCTION has_function(sname VARCHAR(64), rname VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a named function exists within the given schema.'
BEGIN
RETURN has_routine(sname, rname, 'Function', description);
END //
DROP FUNCTION IF EXISTS has_procedure //
CREATE FUNCTION has_procedure(sname VARCHAR(64), rname VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a named procedure exists within the given schema.'
BEGIN
RETURN has_routine(sname, rname, 'Procedure', description);
END //
DROP FUNCTION IF EXISTS hasnt_function //
CREATE FUNCTION hasnt_function(sname VARCHAR(64), rname VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a named function does not exist within the given schema.'
BEGIN
RETURN hasnt_routine(sname, rname, 'Function', description);
END //
DROP FUNCTION IF EXISTS hasnt_procedure //
CREATE FUNCTION hasnt_procedure(sname VARCHAR(64), rname VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a named procedure does not exist within the given schema.'
BEGIN
RETURN hasnt_routine(sname, rname, 'Procedure', description);
END //
/****************************************************************************/
-- FUNCTION DATA_TYPE i.e. return type
-- NB Procedures have no data_type so only deal with Functions
-- _function_data_type(schema, function, returns, description)
DROP FUNCTION IF EXISTS _function_data_type //
CREATE FUNCTION _function_data_type(sname VARCHAR(64), rname VARCHAR(64))
RETURNS VARCHAR(64)
DETERMINISTIC
COMMENT 'Internal function to return the data type returned by a function.'
BEGIN
DECLARE ret VARCHAR(64);
SELECT `data_type` INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname
AND `routine_type` = 'FUNCTION';
RETURN COALESCE(ret, NULL);
END //
-- function_data_type_is(schema, function, returns, description)
DROP FUNCTION IF EXISTS function_data_type_is//
CREATE FUNCTION function_data_type_is(sname VARCHAR(64), rname VARCHAR(64), dtype VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a named function returns the given data type.'
BEGIN
IF description = '' THEN
SET description = concat('Function ', quote_ident(sname), '.', quote_ident(rname),
' should return ', quote_ident(_datatype(dtype)));
END IF;
IF NOT _has_routine(sname, rname, 'FUNCTION') THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT('Function ', quote_ident(sname),'.', quote_ident(rname),
' does not exist')));
END IF;
RETURN eq(_function_data_type(sname, rname), _datatype(dtype), description);
END //
/****************************************************************************/
-- IS_DETERMINISTIC YES/NO
-- _routine_is_deterministic(schema, name, type, description)
DROP FUNCTION IF EXISTS _routine_is_deterministic //
CREATE FUNCTION _routine_is_deterministic(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9))
RETURNS VARCHAR(3)
DETERMINISTIC
COMMENT 'Internal function to return whether a routine is deterministic.'
BEGIN
DECLARE ret VARCHAR(3);
SELECT `is_deterministic` INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname
AND `routine_type` = rtype;
RETURN COALESCE(ret, NULL);
END //
-- routine_is_deterministic(schema, name, type, val, description)
DROP FUNCTION IF EXISTS routine_is_deterministic //
CREATE FUNCTION routine_is_deterministic(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), val VARCHAR(3), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a routine is deterministic.'
BEGIN
IF description = '' THEN
SET description = CONCAT(rtype, ' ', quote_ident(sname), '.', quote_ident(rname),
' should have IS_DETERMINISTIC ', quote_ident(val));
END IF;
IF val NOT IN('YES','NO') THEN
RETURN CONCAT(ok(FALSE, description),'\n',
diag('Is Deterministic must be { YES | NO }'));
END IF;
IF NOT _has_routine(sname, rname, rtype) THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT(ucf(rtype), ' ', quote_ident(sname), '.', quote_ident(rname),
' does not exist')));
END IF;
RETURN eq(_routine_is_deterministic(sname, rname, rtype), val, description);
END //
DROP FUNCTION IF EXISTS function_is_deterministic //
CREATE FUNCTION function_is_deterministic(sname VARCHAR(64), rname VARCHAR(64), val VARCHAR(3), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a function is deterministic.'
BEGIN
RETURN routine_is_deterministic(sname, rname, 'Function', val, description);
END //
DROP FUNCTION IF EXISTS procedure_is_deterministic //
CREATE FUNCTION procedure_is_deterministic(sname VARCHAR(64), rname VARCHAR(64), val VARCHAR(3), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a procedure is deterministic.'
BEGIN
RETURN routine_is_deterministic(sname, rname, 'Procedure', val, description);
END //
/****************************************************************************/
-- SECURITY TYPE
-- { INVOKER | DEFINER }
-- _routine_security_type(schema, routine, security_type, description)
DROP FUNCTION IF EXISTS _routine_security_type //
CREATE FUNCTION _routine_security_type(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9))
RETURNS VARCHAR(7)
DETERMINISTIC
COMMENT 'Internal function to return the security type of a routine.'
BEGIN
DECLARE ret VARCHAR(7);
SELECT `security_type` INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname
AND `routine_type` = rtype ;
RETURN COALESCE(ret, NULL);
END //
-- routine_security_type_is(schema, name, type, security type , description)
DROP FUNCTION IF EXISTS routine_security_type_is //
CREATE FUNCTION routine_security_type_is(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), stype VARCHAR(7), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Confirm the security type of a routine matches the value provided.'
BEGIN
IF description = '' THEN
SET description = CONCAT(rtype, ' ', quote_ident(sname), '.', quote_ident(rname),
' should have Security Type ' , quote_ident(stype));
END IF;
IF stype NOT IN('INVOKER','DEFINER') THEN
RETURN CONCAT(ok(FALSE, description),'\n',
diag('Security Type must be { INVOKER | DEFINER }'));
END IF;
IF NOT _has_routine(sname, rname, rtype) THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT(ucf(rtype), ' ', quote_ident(sname), '.', quote_ident(rname), ' does not exist')));
END IF;
RETURN eq(_routine_security_type(sname, rname, rtype), stype, description);
END //
DROP FUNCTION IF EXISTS function_security_type_is //
CREATE FUNCTION function_security_type_is(sname VARCHAR(64), rname VARCHAR(64), stype VARCHAR(7), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Confirm the security type of a function matches the value provided.'
BEGIN
RETURN routine_security_type_is(sname, rname, 'Function', stype, description);
END //
DROP FUNCTION IF EXISTS procedure_security_type_is //
CREATE FUNCTION procedure_security_type_is(sname VARCHAR(64), rname VARCHAR(64), stype VARCHAR(7), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Confirm the security type of a procedure matches the value provided.'
BEGIN
RETURN routine_security_type_is(sname, rname, 'Procedure', stype, description);
END //
/****************************************************************************/
-- SQL_DATA_ACCESS
-- { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
-- _routine_sql_data_access(schema, routine, type, description)
DROP FUNCTION IF EXISTS _routine_sql_data_access //
CREATE FUNCTION _routine_sql_data_access(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9))
RETURNS VARCHAR(64)
DETERMINISTIC
COMMENT 'Internal function to return the SQL data access value for a named routine within the given schema.'
BEGIN
DECLARE ret VARCHAR(64);
SELECT `sql_data_access` INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname
AND `routine_type` = rtype ;
RETURN COALESCE(ret, NULL);
END //
-- function_sql_data_access_is(schema, function, sql data access , description)
DROP FUNCTION IF EXISTS routine_sql_data_access_is //
CREATE FUNCTION routine_sql_data_access_is(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), sda VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Confirm the SQL data access value of a routine matches that provided.'
BEGIN
IF description = '' THEN
SET description = CONCAT(rtype ,' ', quote_ident(sname), '.', quote_ident(rname),
' should have SQL Data Access ', quote_ident(sda));
END IF;
IF NOT rtype IN('FUNCTION','PROCEDURE') THEN
RETURN CONCAT(ok(FALSE,description), '\n',
diag('Routine Type must be { FUNCTION | PROCEDURE }'));
END IF;
IF NOT sda IN('CONTAINS SQL','NO SQL','READS SQL DATA','MODIFIES SQL DATA') THEN
RETURN CONCAT(ok(FALSE,description), '\n',
diag('SQL Data Access must be { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }'));
END IF;
IF NOT _has_routine(sname, rname, rtype) THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT(ucf(rtype), ' ', quote_ident(sname), '.', quote_ident(rname), ' does not exist')));
END IF;
RETURN eq(_routine_sql_data_access(sname, rname, rtype), sda, description);
END //
DROP FUNCTION IF EXISTS function_sql_data_access_is //
CREATE FUNCTION function_sql_data_access_is(sname VARCHAR(64), rname VARCHAR(64), sda VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Confirm the SQL data access value of a function matches that provided.'
BEGIN
RETURN routine_sql_data_access_is(sname, rname, 'Function', sda, description);
END //
DROP FUNCTION IF EXISTS procedure_sql_data_access_is //
CREATE FUNCTION procedure_sql_data_access_is(sname VARCHAR(64), rname VARCHAR(64), sda VARCHAR(64), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Confirm the SQL data access value of a procedure matches that provided.'
BEGIN
RETURN routine_sql_data_access_is(sname, rname, 'Procedure', sda, description);
END //
/*******************************************************************/
-- Check that the proper routines are defined
DROP FUNCTION IF EXISTS routines_are //
CREATE FUNCTION routines_are(sname VARCHAR(64), rtype VARCHAR(9), want TEXT, description TEXT)
RETURNS TEXT
DETERMINISTIC
BEGIN
SET @want = want;
SET @have = (SELECT GROUP_CONCAT('`',routine_name,'`')
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_type` = rtype);
IF description = '' THEN
SET description = CONCAT('Schema ', quote_ident(sname),
' should have the correct ', LOWER(rtype), 's');
END IF;
IF NOT _has_schema(sname) THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT('Schema ', quote_ident(sname), ' does not exist')));
END IF;
CALL _populate_want(@want);
CALL _populate_have(@have);
SET @missing = (SELECT _missing(@have));
SET @extras = (SELECT _extra(@want));
RETURN _are(CONCAT(rtype, 's'), @extras, @missing, description);
END //
/****************************************************************************/
-- SQL_MODE
-- Checks to ensure appropriate sql mode is available for a function or procedure
-- _routine_has_sql_mode(schema, routine, mode)
DROP FUNCTION IF EXISTS _routine_has_sql_mode //
CREATE FUNCTION _routine_has_sql_mode(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), smode VARCHAR(8192))
RETURNS BOOLEAN
DETERMINISTIC
COMMENT 'Internal function to return the SQL mode which will apply to a routine.'
BEGIN
DECLARE ret BOOLEAN;
SELECT LOCATE(smode, `sql_mode`) INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname
AND `routine_type` = rtype;
RETURN COALESCE(ret, 0);
END //
-- routine_has_sql_mode(schema, name, type, sql_mode , description)
DROP FUNCTION IF EXISTS routine_has_sql_mode //
CREATE FUNCTION routine_has_sql_mode(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(64), smode VARCHAR(8192), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Check that a particular SQL mode will apply to a named routine within the given schema.'
BEGIN
-- this 5.7 list of sql_modes
-- should be fine as a test provide it is superset of previous modes
-- we're only interesed in the name rather than what it does (which does change)
DECLARE valid ENUM('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES','IGNORE_SPACE',
'NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION','NO_DIR_IN_CREATE',
'POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB','NO_KEY_OPTIONS','NO_TABLE_OPTIONS',
'NO_FIELD_OPTIONS','MYSQL323','MYSQL40','ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES',
'STRICT_TRANS_TABLES','STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES',
'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER','HIGH_NOT_PRECEDENCE',
'NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH');
DECLARE EXIT HANDLER FOR 1265 -- invalid assignment to enum
RETURN CONCAT(ok(FALSE,description), '\n',
diag(CONCAT('SQL Mode ', quote_ident(smode), ' is invalid')));
IF description = '' THEN
SET description = CONCAT(UPPER(rtype), ' ', quote_ident(sname), '.', quote_ident(rname),
' requires SQL Mode ', quote_ident(smode));
END IF;
SET valid = smode;
IF NOT _has_routine(sname, rname, 'FUNCTION') THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT(UPPER(rtype),' ', quote_ident(sname), '.', quote_ident(rname), ' does not exist')));
END IF;
RETURN ok(_routine_has_sql_mode(sname, rname, rtype, smode), description);
END //
/****************************************************************************/
-- ROUTINE BODY
-- Get the SHA-1 of the routine body to compare for changes
-- allows match against partial value to save typing
-- You can run _routine_sha1 to get the SHA-1, how much of it is used is down to
-- the individual, we can probably ignore the likelihood of collisions.
DROP FUNCTION IF EXISTS _routine_sha1 //
CREATE FUNCTION _routine_sha1(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9))
RETURNS CHAR(40)
DETERMINISTIC
COMMENT 'Internal function to return a SHA1 of the routine body to check for changes over time. This should be easier to maintain than dumping the routine body into the test script.'
BEGIN
DECLARE ret CHAR(40);
SELECT SHA1(`routine_definition`) INTO ret
FROM `information_schema`.`routines`
WHERE `routine_schema` = sname
AND `routine_name` = rname;
RETURN COALESCE(ret, NULL);
END //
DROP FUNCTION IF EXISTS routine_sha1_is //
CREATE FUNCTION routine_sha1_is(sname VARCHAR(64), rname VARCHAR(64), rtype VARCHAR(9), sha1 VARCHAR(40), description TEXT)
RETURNS TEXT
DETERMINISTIC
COMMENT 'Get the SHA1 value of a routine body to compare against a previous value.'
BEGIN
IF description = '' THEN
SET description = CONCAT(ucf(rtype), ' ', quote_ident(sname), '.', quote_ident(rname),
' definition should match expected value');
END IF;
IF NOT _has_routine(sname, rname, rtype) THEN
RETURN CONCAT(ok(FALSE, description), '\n',
diag(CONCAT(ucf(rtype), ' ', quote_ident(sname), '.', quote_ident(rname), ' does not exist')));
END IF;
-- NB length of supplied value not of a SHA-1
RETURN eq(LEFT(_routine_sha1(sname, rname, rtype), LENGTH(sha1)), sha1, description);
END //
DELIMITER ;
SET @@SESSION.sql_mode = @mode;