|
| 1 | +SET SEARCH_PATH to pgstac, public; |
| 2 | +set check_function_bodies = off; |
| 3 | + |
| 4 | +CREATE OR REPLACE FUNCTION pgstac.cql2_query(j jsonb, wrapper text DEFAULT NULL::text) |
| 5 | + RETURNS text |
| 6 | + LANGUAGE plpgsql |
| 7 | + STABLE |
| 8 | +AS $function$ |
| 9 | +#variable_conflict use_variable |
| 10 | +DECLARE |
| 11 | + args jsonb := j->'args'; |
| 12 | + arg jsonb; |
| 13 | + op text := lower(j->>'op'); |
| 14 | + cql2op RECORD; |
| 15 | + literal text; |
| 16 | + _wrapper text; |
| 17 | +BEGIN |
| 18 | + IF j IS NULL OR (op IS NOT NULL AND args IS NULL) THEN |
| 19 | + RETURN NULL; |
| 20 | + END IF; |
| 21 | + RAISE NOTICE 'CQL2_QUERY: %', j; |
| 22 | + IF j ? 'filter' THEN |
| 23 | + RETURN cql2_query(j->'filter'); |
| 24 | + END IF; |
| 25 | + |
| 26 | + IF j ? 'upper' THEN |
| 27 | + RETURN cql2_query(jsonb_build_object('op', 'upper', 'args', j->'upper')); |
| 28 | + END IF; |
| 29 | + |
| 30 | + IF j ? 'lower' THEN |
| 31 | + RETURN cql2_query(jsonb_build_object('op', 'lower', 'args', j->'lower')); |
| 32 | + END IF; |
| 33 | + |
| 34 | + -- Temporal Query |
| 35 | + IF op ilike 't_%' or op = 'anyinteracts' THEN |
| 36 | + RETURN temporal_op_query(op, args); |
| 37 | + END IF; |
| 38 | + |
| 39 | + -- If property is a timestamp convert it to text to use with |
| 40 | + -- general operators |
| 41 | + IF j ? 'timestamp' THEN |
| 42 | + RETURN format('%L::timestamptz', to_tstz(j->'timestamp')); |
| 43 | + END IF; |
| 44 | + IF j ? 'interval' THEN |
| 45 | + RAISE EXCEPTION 'Please use temporal operators when using intervals.'; |
| 46 | + RETURN NONE; |
| 47 | + END IF; |
| 48 | + |
| 49 | + -- Spatial Query |
| 50 | + IF op ilike 's_%' or op = 'intersects' THEN |
| 51 | + RETURN spatial_op_query(op, args); |
| 52 | + END IF; |
| 53 | + |
| 54 | + |
| 55 | + IF op = 'in' THEN |
| 56 | + RAISE NOTICE 'IN : % % %', args, jsonb_build_array(args->0), args->1; |
| 57 | + args := jsonb_build_array(args->0) || (args->1); |
| 58 | + RAISE NOTICE 'IN2 : %', args; |
| 59 | + -- RETURN format( |
| 60 | + -- '%s = ANY (%L)', |
| 61 | + -- cql2_query(args->0), |
| 62 | + -- to_text_array(args->1) |
| 63 | + -- ); |
| 64 | + END IF; |
| 65 | + |
| 66 | + |
| 67 | + |
| 68 | + IF op = 'between' THEN |
| 69 | + args = jsonb_build_array( |
| 70 | + args->0, |
| 71 | + args->1->0, |
| 72 | + args->1->1 |
| 73 | + ); |
| 74 | + END IF; |
| 75 | + |
| 76 | + -- Make sure that args is an array and run cql2_query on |
| 77 | + -- each element of the array |
| 78 | + RAISE NOTICE 'ARGS PRE: %', args; |
| 79 | + IF j ? 'args' THEN |
| 80 | + IF jsonb_typeof(args) != 'array' THEN |
| 81 | + args := jsonb_build_array(args); |
| 82 | + END IF; |
| 83 | + |
| 84 | + IF jsonb_path_exists(args, '$[*] ? (@.property == "id" || @.property == "datetime" || @.property == "end_datetime" || @.property == "collection")') THEN |
| 85 | + wrapper := NULL; |
| 86 | + ELSE |
| 87 | + -- if any of the arguments are a property, try to get the property_wrapper |
| 88 | + FOR arg IN SELECT jsonb_path_query(args, '$[*] ? (@.property != null)') LOOP |
| 89 | + RAISE NOTICE 'Arg: %', arg; |
| 90 | + SELECT property_wrapper INTO wrapper |
| 91 | + FROM queryables |
| 92 | + WHERE name=(arg->>'property') |
| 93 | + LIMIT 1; |
| 94 | + RAISE NOTICE 'Property: %, Wrapper: %', arg, wrapper; |
| 95 | + IF wrapper IS NOT NULL THEN |
| 96 | + EXIT; |
| 97 | + END IF; |
| 98 | + END LOOP; |
| 99 | + |
| 100 | + -- if the property was not in queryables, see if any args were numbers |
| 101 | + IF |
| 102 | + wrapper IS NULL |
| 103 | + AND jsonb_path_exists(args, '$[*] ? (@.type()=="number")') |
| 104 | + THEN |
| 105 | + wrapper := 'to_float'; |
| 106 | + END IF; |
| 107 | + wrapper := coalesce(wrapper, 'to_text'); |
| 108 | + END IF; |
| 109 | + |
| 110 | + SELECT jsonb_agg(cql2_query(a, wrapper)) |
| 111 | + INTO args |
| 112 | + FROM jsonb_array_elements(args) a; |
| 113 | + END IF; |
| 114 | + RAISE NOTICE 'ARGS: %', args; |
| 115 | + |
| 116 | + IF op IN ('and', 'or') THEN |
| 117 | + RETURN |
| 118 | + format( |
| 119 | + '(%s)', |
| 120 | + array_to_string(to_text_array(args), format(' %s ', upper(op))) |
| 121 | + ); |
| 122 | + END IF; |
| 123 | + |
| 124 | + IF op = 'in' THEN |
| 125 | + RAISE NOTICE 'IN -- % %', args->0, to_text(args->0); |
| 126 | + RETURN format( |
| 127 | + '%s IN (%s)', |
| 128 | + to_text(args->0), |
| 129 | + array_to_string((to_text_array(args))[2:], ',') |
| 130 | + ); |
| 131 | + END IF; |
| 132 | + |
| 133 | + -- Look up template from cql2_ops |
| 134 | + IF j ? 'op' THEN |
| 135 | + SELECT * INTO cql2op FROM cql2_ops WHERE cql2_ops.op ilike op; |
| 136 | + IF FOUND THEN |
| 137 | + -- If specific index set in queryables for a property cast other arguments to that type |
| 138 | + |
| 139 | + RETURN format( |
| 140 | + cql2op.template, |
| 141 | + VARIADIC (to_text_array(args)) |
| 142 | + ); |
| 143 | + ELSE |
| 144 | + RAISE EXCEPTION 'Operator % Not Supported.', op; |
| 145 | + END IF; |
| 146 | + END IF; |
| 147 | + |
| 148 | + |
| 149 | + IF wrapper IS NOT NULL THEN |
| 150 | + RAISE NOTICE 'Wrapping % with %', j, wrapper; |
| 151 | + IF j ? 'property' THEN |
| 152 | + RETURN format('%I(%s)', wrapper, (queryable(j->>'property')).path); |
| 153 | + ELSE |
| 154 | + RETURN format('%I(%L)', wrapper, j); |
| 155 | + END IF; |
| 156 | + ELSIF j ? 'property' THEN |
| 157 | + RETURN quote_ident(j->>'property'); |
| 158 | + END IF; |
| 159 | + |
| 160 | + RETURN quote_literal(to_text(j)); |
| 161 | +END; |
| 162 | +$function$ |
| 163 | +; |
| 164 | + |
| 165 | + |
| 166 | + |
| 167 | +SELECT set_version('0.6.5'); |
0 commit comments