Skip to content

Commit 201bf80

Browse files
Copilotmathiasrw
andcommitted
Simplify window functions implementation - reduce from 192 to 82 lines
Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com>
1 parent 97c66c6 commit 201bf80

3 files changed

Lines changed: 68 additions & 163 deletions

File tree

src/40select.js

Lines changed: 52 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -431,131 +431,65 @@ yy.Select = class Select {
431431
}
432432
}
433433

434-
// Handle window offset functions: LEAD, LAG, FIRST_VALUE, LAST_VALUE
434+
// Window offset functions: LEAD/LAG/FIRST_VALUE/LAST_VALUE
435+
// Scans results linearly to compute values based on relative row positions
435436
if (query.windowFuncs && query.windowFuncs.length > 0) {
436-
for (var j = 0, jlen = query.windowFuncs.length; j < jlen; j++) {
437+
for (var j = 0; j < query.windowFuncs.length; j++) {
437438
var wf = query.windowFuncs[j];
438-
var partitionColumns = wf.partitionColumns || [];
439-
440-
// Group rows by partition
441-
var partitions = {};
442-
var partitionOrder = [];
443-
444-
for (var i = 0, ilen = res.length; i < ilen; i++) {
445-
// Get partition key
446-
var partitionKey =
447-
partitionColumns.length > 0
448-
? partitionColumns
449-
.map(function (col) {
450-
return res[i][col];
439+
var partCols = wf.partitionColumns || [];
440+
var exprCol = wf.args[0] && wf.args[0].columnid;
441+
442+
// Parse offset and default value arguments (handles negative literals like -1)
443+
var getArg = function (a) {
444+
return !a
445+
? undefined
446+
: a.value !== undefined
447+
? a.value
448+
: a.op === '-' && a.right && a.right.value !== undefined
449+
? -a.right.value
450+
: undefined;
451+
};
452+
var offset = getArg(wf.args[1]);
453+
if (offset === undefined) offset = 1;
454+
var defVal = getArg(wf.args[2]);
455+
if (defVal === undefined) defVal = null;
456+
457+
// Track partition boundaries as we scan
458+
var prevPart = null;
459+
var partStart = 0;
460+
461+
// Scan rows, processing each partition when boundaries change
462+
for (var i = 0; i <= res.length; i++) {
463+
var currPart =
464+
i < res.length && partCols.length > 0
465+
? partCols
466+
.map(function (c) {
467+
return res[i][c];
451468
})
452469
.join('|')
453-
: '__all__'; // Single partition for entire result set
454-
455-
if (!partitions[partitionKey]) {
456-
partitions[partitionKey] = [];
457-
partitionOrder.push(partitionKey);
458-
}
459-
partitions[partitionKey].push(i);
460-
}
461-
462-
// Process each partition
463-
partitionOrder.forEach(function (partitionKey) {
464-
var indices = partitions[partitionKey];
465-
var partitionSize = indices.length;
466-
467-
// Get the expression to evaluate (first argument)
468-
var exprArg = wf.args[0];
469-
var exprColumn = null;
470-
if (exprArg && exprArg.columnid) {
471-
exprColumn = exprArg.columnid;
472-
}
473-
474-
// Helper function to evaluate argument value
475-
var evalArgValue = function (arg) {
476-
if (!arg) return undefined;
477-
if (arg.value !== undefined) return arg.value;
478-
// Handle unary operators like -1
479-
if (arg.op === '-' && arg.right && arg.right.value !== undefined) {
480-
return -arg.right.value;
481-
}
482-
if (arg.op === '+' && arg.right && arg.right.value !== undefined) {
483-
return arg.right.value;
484-
}
485-
return undefined;
486-
};
487-
488-
// Apply window function based on type
489-
if (wf.funcid === 'LEAD') {
490-
var offset = 1;
491-
var defaultValue = null;
492-
493-
// Parse offset if provided (second argument)
494-
var offsetVal = evalArgValue(wf.args[1]);
495-
if (offsetVal !== undefined) {
496-
offset = offsetVal;
497-
}
498-
499-
// Parse default value if provided (third argument)
500-
var defaultVal = evalArgValue(wf.args[2]);
501-
if (defaultVal !== undefined) {
502-
defaultValue = defaultVal;
503-
}
504-
505-
for (var k = 0; k < partitionSize; k++) {
506-
var currentIdx = indices[k];
507-
var leadIdx = k + offset;
508-
509-
if (leadIdx < partitionSize && exprColumn) {
510-
res[currentIdx][wf.as] = res[indices[leadIdx]][exprColumn];
511-
} else {
512-
res[currentIdx][wf.as] = defaultValue;
513-
}
514-
}
515-
} else if (wf.funcid === 'LAG') {
516-
var offset = 1;
517-
var defaultValue = null;
518-
519-
// Parse offset if provided (second argument)
520-
var offsetVal = evalArgValue(wf.args[1]);
521-
if (offsetVal !== undefined) {
522-
offset = offsetVal;
523-
}
524-
525-
// Parse default value if provided (third argument)
526-
var defaultVal = evalArgValue(wf.args[2]);
527-
if (defaultVal !== undefined) {
528-
defaultValue = defaultVal;
529-
}
530-
531-
for (var k = 0; k < partitionSize; k++) {
532-
var currentIdx = indices[k];
533-
var lagIdx = k - offset;
534-
535-
if (lagIdx >= 0 && exprColumn) {
536-
res[currentIdx][wf.as] = res[indices[lagIdx]][exprColumn];
537-
} else {
538-
res[currentIdx][wf.as] = defaultValue;
470+
: '__all__';
471+
472+
// When partition ends, compute window function for all rows in partition
473+
if (i === res.length || (prevPart !== null && currPart !== prevPart)) {
474+
for (var k = partStart; k < i; k++) {
475+
var targetIdx;
476+
if (wf.funcid === 'LEAD') {
477+
targetIdx = k + offset;
478+
res[k][wf.as] = targetIdx < i && exprCol ? res[targetIdx][exprCol] : defVal;
479+
} else if (wf.funcid === 'LAG') {
480+
targetIdx = k - offset;
481+
res[k][wf.as] =
482+
targetIdx >= partStart && exprCol ? res[targetIdx][exprCol] : defVal;
483+
} else if (wf.funcid === 'FIRST_VALUE') {
484+
res[k][wf.as] = exprCol ? res[partStart][exprCol] : null;
485+
} else if (wf.funcid === 'LAST_VALUE') {
486+
res[k][wf.as] = exprCol ? res[i - 1][exprCol] : null;
539487
}
540488
}
541-
} else if (wf.funcid === 'FIRST_VALUE') {
542-
// Get first value in partition
543-
var firstIdx = indices[0];
544-
var firstValue = exprColumn ? res[firstIdx][exprColumn] : null;
545-
546-
for (var k = 0; k < partitionSize; k++) {
547-
res[indices[k]][wf.as] = firstValue;
548-
}
549-
} else if (wf.funcid === 'LAST_VALUE') {
550-
// Get last value in partition
551-
var lastIdx = indices[partitionSize - 1];
552-
var lastValue = exprColumn ? res[lastIdx][exprColumn] : null;
553-
554-
for (var k = 0; k < partitionSize; k++) {
555-
res[indices[k]][wf.as] = lastValue;
556-
}
489+
partStart = i;
557490
}
558-
});
491+
prevPart = currPart;
492+
}
559493
}
560494
}
561495

src/424select.js

Lines changed: 15 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -531,46 +531,21 @@ yy.Select.prototype.compileSelectGroup0 = function (query) {
531531
query.grouprownums.push({as: col.as, columnIndex: 0}); // Track which column to use for grouping
532532
}
533533

534-
// Handle window offset functions: LEAD, LAG, FIRST_VALUE, LAST_VALUE
535-
if (col.funcid) {
536-
var funcUpper = col.funcid.toUpperCase();
537-
if (
538-
funcUpper === 'LEAD' ||
539-
funcUpper === 'LAG' ||
540-
funcUpper === 'FIRST_VALUE' ||
541-
funcUpper === 'LAST_VALUE'
542-
) {
543-
if (col.over) {
544-
// Track window offset function for post-processing
545-
var windowFunc = {
546-
as: col.as,
547-
funcid: funcUpper,
548-
args: col.args || [],
549-
};
550-
551-
// Parse partition columns if present
552-
if (col.over.partition) {
553-
windowFunc.partitionColumns = col.over.partition.map(function (p) {
554-
return p.columnid || p.toString();
555-
});
556-
}
557-
558-
// Parse order by if present
559-
if (col.over.order) {
560-
windowFunc.orderColumns = col.over.order.map(function (o) {
561-
return {
562-
columnid: o.expression && o.expression.columnid,
563-
direction: o.direction || 'ASC',
564-
};
565-
});
566-
}
567-
568-
// Initialize window functions array if not exists
569-
if (!query.windowFuncs) {
570-
query.windowFuncs = [];
571-
}
572-
query.windowFuncs.push(windowFunc);
573-
}
534+
// Track window offset functions for post-processing
535+
if (col.funcid && col.over) {
536+
var fn = col.funcid.toUpperCase();
537+
if (fn === 'LEAD' || fn === 'LAG' || fn === 'FIRST_VALUE' || fn === 'LAST_VALUE') {
538+
if (!query.windowFuncs) query.windowFuncs = [];
539+
query.windowFuncs.push({
540+
as: col.as,
541+
funcid: fn,
542+
args: col.args || [],
543+
partitionColumns: col.over.partition
544+
? col.over.partition.map(function (p) {
545+
return p.columnid || p.toString();
546+
})
547+
: [],
548+
});
574549
}
575550
}
576551
// console.log("colas:",colas);

src/55functions.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,24 +251,20 @@ stdlib.GROUP_ROW_NUMBER = function () {
251251
return '1';
252252
};
253253

254-
// Window offset functions - these return placeholders that are replaced during post-processing
254+
// Window offset functions - return null during compilation, actual values computed after query execution
255255
stdlib.LEAD = function (expr, offset, defaultValue) {
256-
// Return null as placeholder - actual value computed in post-processing
257256
return 'null';
258257
};
259258

260259
stdlib.LAG = function (expr, offset, defaultValue) {
261-
// Return null as placeholder - actual value computed in post-processing
262260
return 'null';
263261
};
264262

265263
stdlib.FIRST_VALUE = function (expr) {
266-
// Return null as placeholder - actual value computed in post-processing
267264
return 'null';
268265
};
269266

270267
stdlib.LAST_VALUE = function (expr) {
271-
// Return null as placeholder - actual value computed in post-processing
272268
return 'null';
273269
};
274270

0 commit comments

Comments
 (0)