( exp: IR.Func<unknown>, params: Array<unknown> = [], encodeColumnName?: ColumnEncoder, )
| 166 | } |
| 167 | |
| 168 | function compileFunction( |
| 169 | exp: IR.Func<unknown>, |
| 170 | params: Array<unknown> = [], |
| 171 | encodeColumnName?: ColumnEncoder, |
| 172 | ): string { |
| 173 | const { name, args } = exp |
| 174 | |
| 175 | const opName = getOpName(name) |
| 176 | |
| 177 | // Handle comparison operators with null/undefined values |
| 178 | // These would create invalid queries with missing params (e.g., "col = $1" with empty params) |
| 179 | // In SQL, all comparisons with NULL return UNKNOWN, so these are almost always mistakes |
| 180 | if (isComparisonOp(name)) { |
| 181 | const nullArgIndex = args.findIndex((arg: IR.BasicExpression) => |
| 182 | isNullValue(arg), |
| 183 | ) |
| 184 | |
| 185 | if (nullArgIndex !== -1) { |
| 186 | // All comparison operators (including eq) throw an error for null values |
| 187 | // Users should use isNull() or isUndefined() to check for null values |
| 188 | throw new Error( |
| 189 | `Cannot use null/undefined value with '${name}' operator. ` + |
| 190 | `Comparisons with null always evaluate to UNKNOWN in SQL. ` + |
| 191 | `Use isNull() or isUndefined() to check for null values, ` + |
| 192 | `or filter out null values before building the query.`, |
| 193 | ) |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | const compiledArgs = args.map((arg: IR.BasicExpression) => |
| 198 | compileBasicExpression(arg, params, encodeColumnName), |
| 199 | ) |
| 200 | |
| 201 | // Special case for IS NULL / IS NOT NULL - these are postfix operators |
| 202 | if (name === `isNull` || name === `isUndefined`) { |
| 203 | if (compiledArgs.length !== 1) { |
| 204 | throw new Error(`${name} expects 1 argument`) |
| 205 | } |
| 206 | return `${compiledArgs[0]} ${opName}` |
| 207 | } |
| 208 | |
| 209 | // Special case for NOT - unary prefix operator |
| 210 | if (name === `not`) { |
| 211 | if (compiledArgs.length !== 1) { |
| 212 | throw new Error(`NOT expects 1 argument`) |
| 213 | } |
| 214 | // Check if the argument is IS NULL to generate IS NOT NULL |
| 215 | const arg = args[0] |
| 216 | if (arg && arg.type === `func`) { |
| 217 | const funcArg = arg |
| 218 | if (funcArg.name === `isNull` || funcArg.name === `isUndefined`) { |
| 219 | const innerArg = compileBasicExpression( |
| 220 | funcArg.args[0]!, |
| 221 | params, |
| 222 | encodeColumnName, |
| 223 | ) |
| 224 | return `${innerArg} IS NOT NULL` |
| 225 | } |
no test coverage detected