@@ -163,16 +163,61 @@ export class DataFrame<T extends Row = Row> {
163163 return this . where ( column , op , value ) ;
164164 }
165165
166+ /** Filter rows where a column is not null. */
167+ whereNotNull ( column : string ) : DataFrame < T > {
168+ return this . derive ( { filters : [ ...this . _filters , { column, op : "is_not_null" , value : 0 } ] } ) ;
169+ }
170+
171+ /** Filter rows where a column is null. */
172+ whereNull ( column : string ) : DataFrame < T > {
173+ return this . derive ( { filters : [ ...this . _filters , { column, op : "is_null" , value : 0 } ] } ) ;
174+ }
175+
166176 /** Select specific columns. Only these byte ranges are fetched from R2. */
167177 select ( ...columns : string [ ] ) : DataFrame {
168178 return this . derive ( { projections : columns } ) as DataFrame ;
169179 }
170180
181+ /** Exclude specific columns (inverse of select). */
182+ drop ( ...columns : string [ ] ) : DataFrame < T > {
183+ const dropSet = new Set ( columns ) ;
184+ const remaining = this . _projections . length > 0
185+ ? this . _projections . filter ( c => ! dropSet . has ( c ) )
186+ : [ ] ; // If no projections set, drop will be applied at collect time via computed exclusion
187+ // Store dropped columns as a negative projection marker
188+ return this . derive ( {
189+ projections : remaining ,
190+ computedColumns : [
191+ ...this . _computedColumns ,
192+ ...columns . map ( c => ( { alias : `__drop__${ c } ` , fn : ( ) => undefined } ) ) ,
193+ ] ,
194+ } ) ;
195+ }
196+
197+ /** Rename columns. Returns a new DataFrame with renamed output columns. */
198+ rename ( mapping : Record < string , string > ) : DataFrame {
199+ const renames = Object . entries ( mapping ) ;
200+ return this . derive ( {
201+ computedColumns : [
202+ ...this . _computedColumns ,
203+ ...renames . map ( ( [ from , to ] ) => ( {
204+ alias : to ,
205+ fn : ( row : Row ) => row [ from ] ,
206+ } ) ) ,
207+ ] ,
208+ } ) as DataFrame ;
209+ }
210+
171211 /** Sort results by a column. With .limit(), uses a top-K heap (O(K) memory). */
172212 sort ( column : string , direction : "asc" | "desc" = "asc" ) : DataFrame < T > {
173213 return this . derive ( { sortColumn : column , sortDirection : direction } ) ;
174214 }
175215
216+ /** Alias for .sort() — common in SQL-style APIs. */
217+ orderBy ( column : string , direction : "asc" | "desc" = "asc" ) : DataFrame < T > {
218+ return this . sort ( column , direction ) ;
219+ }
220+
176221 /** Limit the number of returned rows. Enables early termination. */
177222 limit ( n : number ) : DataFrame < T > {
178223 if ( n < 0 ) throw new Error ( "limit() must be non-negative" ) ;
@@ -181,6 +226,7 @@ export class DataFrame<T extends Row = Row> {
181226
182227 /** Skip the first N rows. Enables offset-based pagination. */
183228 offset ( n : number ) : DataFrame < T > {
229+ if ( n < 0 ) throw new Error ( "offset() must be non-negative" ) ;
184230 return this . derive ( { offset : n } ) ;
185231 }
186232
@@ -617,6 +663,8 @@ export class MaterializedExecutor implements QueryExecutor {
617663 for ( const f of query . filters ) {
618664 rows = rows . filter ( row => {
619665 const v = row [ f . column ] ;
666+ if ( f . op === "is_null" ) return v === null || v === undefined ;
667+ if ( f . op === "is_not_null" ) return v !== null && v !== undefined ;
620668 if ( v === null ) return false ;
621669 const fv = f . value ;
622670 switch ( f . op ) {
0 commit comments