2025-02-06 20:27:28 +08:00
package sql
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAllowQuery ( t * testing . T ) {
testCases := [ ] struct {
name string
q string
err error
} {
{
name : "a big catch all for now" ,
q : example_metrics_query ,
err : nil ,
} ,
2025-02-07 17:44:17 +08:00
{
name : "an example from todd" ,
q : example_argo_commit_example ,
err : nil ,
} ,
2025-03-12 23:57:50 +08:00
{
name : "case statement" ,
q : example_case_statement ,
err : nil ,
} ,
{
2025-04-08 02:23:39 +08:00
name : "many allowed functions" ,
q : example_many_allowed_functions ,
err : nil ,
} ,
{
name : "many more allowed functions" ,
q : example_many_more_allowed_functions ,
2025-03-12 23:57:50 +08:00
err : nil ,
} ,
2025-03-26 00:19:52 +08:00
{
name : "paren select allowed" ,
q : ` (SELECT * FROM a_table) UNION ALL (SELECT * FROM a_table2) ` ,
err : nil ,
} ,
2025-03-26 17:05:42 +08:00
{
name : "allows keywords 'is', 'not', 'null'" ,
q : ` SELECT * FROM a_table WHERE a_column IS NOT NULL ` ,
err : nil ,
} ,
2025-03-27 19:32:48 +08:00
{
name : "null literal" ,
q : ` SELECT 1 as id, NULL as null_col ` ,
err : nil ,
} ,
{
name : "val tuple in read query" ,
q : ` SELECT 1 WHERE 1 IN (1, 2, 3) ` ,
err : nil ,
} ,
{
name : "group concat in read query" ,
q : ` SELECT 1 as id, GROUP_CONCAT('will_', 'concatenate') as concat_val ` ,
err : nil ,
} ,
{
name : "collate in read query" ,
q : ` SELECT 'some text' COLLATE utf8mb4_bin ` ,
err : nil ,
} ,
2025-03-28 04:49:21 +08:00
{
name : "allow substring_index" ,
q : ` SELECT __value__, SUBSTRING_INDEX(name, '.', -1) AS code FROM A ` ,
err : nil ,
} ,
2025-04-01 19:45:01 +08:00
{
name : "json functions" ,
q : example_json_functions ,
err : nil ,
} ,
2025-04-02 20:13:17 +08:00
{
name : "range condition (between)" ,
q : ` SELECT '2024-04-01 15:30:00' BETWEEN '2024-04-01 15:29:00' AND '2024-04-01 15:31:00' ` ,
err : nil ,
} ,
2025-04-08 02:23:39 +08:00
{
name : "window functions" ,
q : example_window_functions ,
err : nil ,
} ,
{
name : "json table" ,
q : "SELECT * FROM mockGitHubIssuesDSResponse, JSON_TABLE(labels, '$[*]' COLUMNS(val VARCHAR(255) PATH '$')) AS jt WHERE CAST(jt.val AS CHAR) LIKE 'type%'" ,
err : nil ,
} ,
2025-02-06 20:27:28 +08:00
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
_ , err := AllowQuery ( tc . q )
if tc . err != nil {
require . Error ( t , err )
} else {
require . NoError ( t , err )
}
} )
}
}
var example_metrics_query = ` WITH
metrics_this_month AS (
SELECT
Month ,
namespace ,
sum ( BillableSeries ) AS billable_series
FROM metrics
WHERE
Month = "2024-11"
GROUP BY
Month ,
namespace
ORDER BY billable_series DESC
) ,
total_metrics AS (
SELECT SUM ( billable_series ) AS metrics_billable_series_total
FROM metrics_this_month
) ,
total_traces AS (
-- "usage" is a reserved keyword in MySQL . Quote it with backticks .
SELECT SUM ( value ) AS traces_usage_total
FROM traces
) ,
usage_by_team AS (
SELECT
COALESCE ( teams . team , ' unaccounted ' ) AS team ,
1 + 0 AS team_count ,
-- Metrics
SUM ( COALESCE ( metrics_this_month . billable_series , 0 ) ) AS metrics_billable_series ,
-- Traces
SUM ( COALESCE ( traces . value , 0 ) ) AS traces_usage
-- FROM teams
-- FULL OUTER JOIN metrics_this_month
FROM metrics_this_month
FULL OUTER JOIN teams
ON teams . namespace = metrics_this_month . namespace
FULL OUTER JOIN traces
ON teams . namespace = traces . namespace
GROUP BY
-- COALESCE ( teams . team , ' unaccounted ' )
teams . team
ORDER BY metrics_billable_series DESC
)
SELECT *
FROM usage_by_team
CROSS JOIN total_metrics
CROSS JOIN total_traces `
2025-02-07 17:44:17 +08:00
var example_argo_commit_example = ` WITH
gh AS
( SELECT Count ( * ) AS commits
FROM
( SELECT *
FROM oss_repo
UNION ALL SELECT *
FROM ent_repo ) AS ent_repos ) ,
argo_success AS
( SELECT IF ( argo . status = ' Succeeded ' , argo . value , 0 ) AS value FROM argo ) ,
argo_failure AS
( SELECT IF ( argo . status = ' Failed ' , argo . value , 0 ) AS value FROM argo )
SELECT IF ( env . value > 1 , TRUE , workflows . runs < 1 OR gh . commits < 1 ) AS status ,
gh . commits AS ' merged commits to main ( OSS + enterprise ) ' ,
drone . value AS ' enterprise downstream publish ' ,
workflows . runs AS ' github trigger instant workflow runs today ' ,
argo_success . value AS ' argo success ' ,
argo_failure . value AS ' argo failure ' ,
( env . value - 1 ) AS ' new dev instant deployments '
FROM drone ,
env ,
gh ,
argo_success ,
argo_failure ,
workflows ; `
2025-03-12 23:57:50 +08:00
var example_case_statement = ` SELECT
value ,
CASE
WHEN value > 100 THEN ' High '
WHEN value > 50 THEN ' Medium '
ELSE ' Low '
END AS category
FROM metrics `
2025-04-08 02:23:39 +08:00
var example_many_allowed_functions = ` WITH sample_data AS (
2025-03-19 06:41:42 +08:00
SELECT
100 AS value ,
' example ' AS name ,
2025-04-08 02:23:39 +08:00
' 2025 - 01 - 01 00 : 00 : 00 ' AS created_at
2025-03-19 06:41:42 +08:00
UNION ALL SELECT
50 AS value ,
' test ' AS name ,
2025-04-08 02:23:39 +08:00
DATE_SUB ( ' 2025 - 01 - 01 00 : 00 : 00 ' , INTERVAL 1 DAY ) AS created_at
2025-03-19 06:41:42 +08:00
)
SELECT
2025-03-12 23:57:50 +08:00
-- Conditional functions
IF ( value > 100 , ' High ' , ' Low ' ) AS conditional_if ,
COALESCE ( value , 0 ) AS conditional_coalesce ,
IFNULL ( value , 0 ) AS conditional_ifnull ,
NULLIF ( value , 0 ) AS conditional_nullif ,
-- Aggregation functions
SUM ( value ) AS agg_sum ,
AVG ( value ) AS agg_avg ,
COUNT ( * ) AS agg_count ,
MIN ( value ) AS agg_min ,
MAX ( value ) AS agg_max ,
STDDEV ( value ) AS agg_stddev ,
STD ( value ) AS agg_std ,
STDDEV_POP ( value ) AS agg_stddev_pop ,
VARIANCE ( value ) AS agg_variance ,
VAR_POP ( value ) AS agg_var_pop ,
-- Mathematical functions
ABS ( value ) AS math_abs ,
ROUND ( value , 2 ) AS math_round ,
FLOOR ( value ) AS math_floor ,
CEILING ( value ) AS math_ceiling ,
CEIL ( value ) AS math_ceil ,
SQRT ( ABS ( value ) ) AS math_sqrt ,
POW ( value , 2 ) AS math_pow ,
POWER ( value , 2 ) AS math_power ,
MOD ( value , 10 ) AS math_mod ,
LOG ( value ) AS math_log ,
LOG10 ( value ) AS math_log10 ,
EXP ( value ) AS math_exp ,
SIGN ( value ) AS math_sign ,
-- String functions
CONCAT ( ' value : ' , CAST ( value AS CHAR ) ) AS str_concat ,
LENGTH ( name ) AS str_length ,
CHAR_LENGTH ( name ) AS str_char_length ,
LOWER ( name ) AS str_lower ,
UPPER ( name ) AS str_upper ,
SUBSTRING ( name , 1 , 5 ) AS str_substring ,
TRIM ( name ) AS str_trim ,
-- Date functions
STR_TO_DATE ( ' 2023 - 01 - 01 ' , ' % Y - % m - % d ' ) AS date_str_to_date ,
2025-04-08 02:23:39 +08:00
DATE_FORMAT ( ' 2025 - 01 - 01 00 : 00 : 00 ' , ' % Y - % m - % d ' ) AS date_format ,
' 2025 - 01 - 01 00 : 00 : 00 ' AS date_now ,
2025-03-12 23:57:50 +08:00
DATE_ADD ( created_at , INTERVAL 1 DAY ) AS date_add ,
DATE_SUB ( created_at , INTERVAL 1 DAY ) AS date_sub ,
YEAR ( created_at ) AS date_year ,
MONTH ( created_at ) AS date_month ,
DAY ( created_at ) AS date_day ,
WEEKDAY ( created_at ) AS date_weekday ,
2025-04-08 02:23:39 +08:00
DATEDIFF ( ' 2025 - 01 - 01 00 : 00 : 00 ' , created_at ) AS date_datediff ,
2025-03-12 23:57:50 +08:00
UNIX_TIMESTAMP ( created_at ) AS date_unix_timestamp ,
FROM_UNIXTIME ( 1634567890 ) AS date_from_unixtime ,
-- Type conversion
CAST ( value AS CHAR ) AS type_cast ,
CONVERT ( value , CHAR ) AS type_convert
2025-03-19 06:41:42 +08:00
FROM sample_data
2025-03-12 23:57:50 +08:00
GROUP BY name , value , created_at
LIMIT 10 `
2025-04-01 19:45:01 +08:00
var example_json_functions = ` SELECT
JSON_OBJECT ( ' key1 ' , ' value1 ' , ' key2 ' , 10 ) AS json_obj ,
JSON_ARRAY ( 1 , ' abc ' , NULL , TRUE ) AS json_arr ,
JSON_EXTRACT ( ' { "id" : 123 , "name" : "test" } ' , ' $ . id ' ) AS json_ext ,
JSON_UNQUOTE ( JSON_EXTRACT ( ' { "name" : "test" } ' , ' $ . name ' ) ) AS json_unq ,
JSON_CONTAINS ( ' { "a" : 1 , "b" : 2 } ' , ' { "a" : 1 } ' ) AS json_contains ,
JSON_SET ( ' { "a" : 1 } ' , ' $ . b ' , 2 ) AS json_set ,
JSON_REMOVE ( ' { "a" : 1 , "b" : 2 } ' , ' $ . b ' ) AS json_remove ,
JSON_LENGTH ( ' { "a" : 1 , "b" : { "c" : 3 } } ' ) AS json_len ,
JSON_SEARCH ( ' { "a" : "xyz" , "b" : "abc" } ' , ' one ' , ' abc ' ) AS json_search ,
JSON_TYPE ( ' { "a" : 1 } ' ) AS json_type `
2025-04-08 02:23:39 +08:00
var example_many_more_allowed_functions = `
SELECT
-- Math functions
LN ( 10 ) as ln_val ,
TRUNCATE ( 12.345 , 2 ) as truncate_val ,
SIN ( 0.5 ) as sin_val ,
COS ( 0.5 ) as cos_val ,
TAN ( 0.5 ) as tan_val ,
ASIN ( 0.5 ) as asin_val ,
ACOS ( 0.5 ) as acos_val ,
ATAN ( 0.5 ) as atan_val ,
ATAN2 ( 1 , 2 ) as atan2_val ,
RAND ( ) as rand_val ,
PI ( ) as pi_val ,
-- String functions
LEFT ( ' hello ' , 2 ) as left_val ,
RIGHT ( ' hello ' , 2 ) as right_val ,
LTRIM ( ' hello ' ) as ltrim_val ,
RTRIM ( ' hello ' ) as rtrim_val ,
REPLACE ( ' hello ' , 'l' , 'x' ) as replace_val ,
REVERSE ( ' hello ' ) as reverse_val ,
LCASE ( ' HELLO ' ) as lcase_val ,
UCASE ( ' hello ' ) as ucase_val ,
MID ( ' hello ' , 2 , 2 ) as mid_val ,
REPEAT ( 'a' , 3 ) as repeat_val ,
POSITION ( 'l' IN ' hello ' ) as position_val ,
INSTR ( ' hello ' , 'l' ) as instr_val ,
LOCATE ( 'l' , ' hello ' ) as locate_val ,
ASCII ( 'A' ) as ascii_val ,
ORD ( 'A' ) as ord_val ,
CHAR ( 65 ) as char_val ,
REGEXP_SUBSTR ( ' hello world ' , ' world ' ) as regexp_substr_val ,
-- Date functions
EXTRACT ( YEAR FROM ' 2023 - 01 - 01 ' ) as extract_val ,
HOUR ( ' 12 : 34 : 56 ' ) as hour_val ,
MINUTE ( ' 12 : 34 : 56 ' ) as minute_val ,
SECOND ( ' 12 : 34 : 56 ' ) as second_val ,
DAYNAME ( ' 2023 - 01 - 01 ' ) as dayname_val ,
MONTHNAME ( ' 2023 - 01 - 01 ' ) as monthname_val ,
DAYOFWEEK ( ' 2023 - 01 - 01 ' ) as dayofweek_val ,
DAYOFMONTH ( ' 2023 - 01 - 01 ' ) as dayofmonth_val ,
DAYOFYEAR ( ' 2023 - 01 - 01 ' ) as dayofyear_val ,
WEEK ( ' 2023 - 01 - 01 ' ) as week_val ,
QUARTER ( ' 2023 - 01 - 01 ' ) as quarter_val ,
TIME_TO_SEC ( ' 12 : 34 : 56 ' ) as time_to_sec_val ,
SEC_TO_TIME ( 45296 ) as sec_to_time_val ,
TIMESTAMPDIFF ( HOUR , ' 2023 - 01 - 01 ' , ' 2023 - 01 - 02 ' ) as timestampdiff_val ,
TIMESTAMPADD ( HOUR , 1 , ' 2023 - 01 - 01 ' ) as timestampadd_val ,
-- Type conversion
CONVERT ( 12.34 , CHAR ) as convert_val ,
-- JSON functions
JSON_MERGE_PATCH ( ' { "a" : 1 } ' , ' { "b" : 2 } ' ) as json_merge_patch_val ,
JSON_VALID ( ' { "a" : 1 } ' ) as json_valid_val ,
JSON_KEYS ( ' { "a" : 1 , "b" : 2 } ' ) as json_keys_val ,
JSON_QUOTE ( ' hello ' ) as json_quote_val ,
JSON_INSERT ( ' { "a" : 1 } ' , ' $ . b ' , 2 ) as json_insert_val ,
JSON_REPLACE ( ' { "a" : 1 , "b" : 2 } ' , ' $ . b ' , 3 ) as json_replace_val
FROM dual ; `
var example_window_functions = `
WITH dummy_data AS (
SELECT 1 as val , ' apple ' as txt
UNION ALL SELECT 2 , ' banana '
UNION ALL SELECT 3 , ' cherry '
)
SELECT
val ,
txt ,
ROW_NUMBER ( ) OVER ( ORDER BY val ) as row_num ,
RANK ( ) OVER ( ORDER BY val ) as rank_val ,
DENSE_RANK ( ) OVER ( ORDER BY val ) as dense_rank_val ,
LEAD ( val ) OVER ( ORDER BY val ) as lead_val ,
LAG ( val ) OVER ( ORDER BY val ) as lag_val ,
FIRST_VALUE ( val ) OVER ( ORDER BY val ) as first_val ,
LAST_VALUE ( val ) OVER ( ORDER BY val ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) as last_val
FROM dummy_data ; `