@@ -7,6 +7,29 @@ import { normalizeValue } from "../../utils/comparison.js"
77import type { BasicExpression , Func , PropRef } from "../ir.js"
88import type { NamespacedRow } from "../../types.js"
99
10+ /**
11+ * Helper function to check if a value is null or undefined (represents UNKNOWN in 3-valued logic)
12+ */
13+ function isUnknown ( value : any ) : boolean {
14+ return value === null || value === undefined
15+ }
16+
17+ /**
18+ * Converts a 3-valued logic result to a boolean for use in WHERE/HAVING filters.
19+ * In SQL, UNKNOWN (null) values in WHERE clauses exclude rows, matching false behavior.
20+ *
21+ * @param result - The 3-valued logic result: true, false, or null (UNKNOWN)
22+ * @returns true only if result is explicitly true, false otherwise
23+ *
24+ * Truth table:
25+ * - true → true (include row)
26+ * - false → false (exclude row)
27+ * - null (UNKNOWN) → false (exclude row, matching SQL behavior)
28+ */
29+ export function toBooleanPredicate ( result : boolean | null ) : boolean {
30+ return result === true
31+ }
32+
1033/**
1134 * Compiled expression evaluator function type
1235 */
@@ -145,6 +168,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
145168 return ( data ) => {
146169 const a = normalizeValue ( argA ( data ) )
147170 const b = normalizeValue ( argB ( data ) )
171+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
172+ if ( isUnknown ( a ) || isUnknown ( b ) ) {
173+ return null
174+ }
148175 return a === b
149176 }
150177 }
@@ -154,6 +181,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
154181 return ( data ) => {
155182 const a = argA ( data )
156183 const b = argB ( data )
184+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
185+ if ( isUnknown ( a ) || isUnknown ( b ) ) {
186+ return null
187+ }
157188 return a > b
158189 }
159190 }
@@ -163,6 +194,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
163194 return ( data ) => {
164195 const a = argA ( data )
165196 const b = argB ( data )
197+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
198+ if ( isUnknown ( a ) || isUnknown ( b ) ) {
199+ return null
200+ }
166201 return a >= b
167202 }
168203 }
@@ -172,6 +207,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
172207 return ( data ) => {
173208 const a = argA ( data )
174209 const b = argB ( data )
210+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
211+ if ( isUnknown ( a ) || isUnknown ( b ) ) {
212+ return null
213+ }
175214 return a < b
176215 }
177216 }
@@ -181,32 +220,78 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
181220 return ( data ) => {
182221 const a = argA ( data )
183222 const b = argB ( data )
223+ // In 3-valued logic, any comparison with null/undefined returns UNKNOWN
224+ if ( isUnknown ( a ) || isUnknown ( b ) ) {
225+ return null
226+ }
184227 return a <= b
185228 }
186229 }
187230
188231 // Boolean operators
189232 case `and` :
190233 return ( data ) => {
234+ // 3-valued logic for AND:
235+ // - false AND anything = false (short-circuit)
236+ // - null AND false = false
237+ // - null AND anything (except false) = null
238+ // - anything (except false) AND null = null
239+ // - true AND true = true
240+ let hasUnknown = false
191241 for ( const compiledArg of compiledArgs ) {
192- if ( ! compiledArg ( data ) ) {
242+ const result = compiledArg ( data )
243+ if ( result === false ) {
193244 return false
194245 }
246+ if ( isUnknown ( result ) ) {
247+ hasUnknown = true
248+ }
249+ }
250+ // If we got here, no operand was false
251+ // If any operand was null, return null (UNKNOWN)
252+ if ( hasUnknown ) {
253+ return null
195254 }
255+
196256 return true
197257 }
198258 case `or` :
199259 return ( data ) => {
260+ // 3-valued logic for OR:
261+ // - true OR anything = true (short-circuit)
262+ // - null OR anything (except true) = null
263+ // - false OR false = false
264+ let hasUnknown = false
200265 for ( const compiledArg of compiledArgs ) {
201- if ( compiledArg ( data ) ) {
266+ const result = compiledArg ( data )
267+ if ( result === true ) {
202268 return true
203269 }
270+ if ( isUnknown ( result ) ) {
271+ hasUnknown = true
272+ }
273+ }
274+ // If we got here, no operand was true
275+ // If any operand was null, return null (UNKNOWN)
276+ if ( hasUnknown ) {
277+ return null
204278 }
279+
205280 return false
206281 }
207282 case `not` : {
208283 const arg = compiledArgs [ 0 ] !
209- return ( data ) => ! arg ( data )
284+ return ( data ) => {
285+ // 3-valued logic for NOT:
286+ // - NOT null = null
287+ // - NOT true = false
288+ // - NOT false = true
289+ const result = arg ( data )
290+ if ( isUnknown ( result ) ) {
291+ return null
292+ }
293+ return ! result
294+ }
210295 }
211296
212297 // Array operators
@@ -216,6 +301,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
216301 return ( data ) => {
217302 const value = valueEvaluator ( data )
218303 const array = arrayEvaluator ( data )
304+ // In 3-valued logic, if the value is null/undefined, return UNKNOWN
305+ if ( isUnknown ( value ) ) {
306+ return null
307+ }
219308 if ( ! Array . isArray ( array ) ) {
220309 return false
221310 }
@@ -230,6 +319,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
230319 return ( data ) => {
231320 const value = valueEvaluator ( data )
232321 const pattern = patternEvaluator ( data )
322+ // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN
323+ if ( isUnknown ( value ) || isUnknown ( pattern ) ) {
324+ return null
325+ }
233326 return evaluateLike ( value , pattern , false )
234327 }
235328 }
@@ -239,6 +332,10 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
239332 return ( data ) => {
240333 const value = valueEvaluator ( data )
241334 const pattern = patternEvaluator ( data )
335+ // In 3-valued logic, if value or pattern is null/undefined, return UNKNOWN
336+ if ( isUnknown ( value ) || isUnknown ( pattern ) ) {
337+ return null
338+ }
242339 return evaluateLike ( value , pattern , true )
243340 }
244341 }
0 commit comments