All files server/executors/sqlite-executor.ts

92.1% Statements 35/38
89.18% Branches 33/37
100% Functions 7/7
93.93% Lines 31/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                        565x   547x 547x 33532x           18x     18x 12x 12x 3x   3x 6x   3x 3x   3x     9x               33535x   33535x 33535x     176550x 101458x   75092x     33535x               101476x     101363x     18x   18x 15x       3x 3x                 23x                     222x  
/**
 * SQLite database executor  
 * Works with better-sqlite3 driver
 */
 
import type { SQL } from 'drizzle-orm'
import type { DrizzleDatabase } from '../types'
import { BaseDatabaseExecutor } from './base-executor'
 
export class SQLiteExecutor extends BaseDatabaseExecutor {
  async execute<T = any[]>(query: SQL | any, numericFields?: string[]): Promise<T> {
    // Handle Drizzle query objects directly
    if (query && typeof query === 'object' && typeof query.execute === 'function') {
      // This is a Drizzle query object, execute it directly
      const result = await query.execute()
      Eif (Array.isArray(result)) {
        return result.map(row => this.convertNumericFields(row, numericFields)) as T
      }
      return result as T
    }
    
    // SQLite is synchronous, but we wrap in Promise for consistency
    try {
      // For SQLite with better-sqlite3, we need to execute through the Drizzle instance
      // The query is already a prepared Drizzle SQL object that handles parameter binding
      if (this.db.all) {
        const result = this.db.all(query)
        if (Array.isArray(result)) {
          return result.map(row => this.convertNumericFields(row, numericFields)) as T
        }
        return result as T
      } else if (this.db.run) {
        // Fallback to run method if all is not available
        const result = this.db.run(query)
        return result as T
      } else {
        throw new Error('SQLite database instance must have an all() or run() method')
      }
    } catch (error) {
      throw new Error(`SQLite execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
    }
  }
 
  /**
   * Convert numeric string fields to numbers (only for measure fields)
   */
  private convertNumericFields(row: any, numericFields?: string[]): any {
    Iif (!row || typeof row !== 'object') return row
    
    const converted: any = {}
    for (const [key, value] of Object.entries(row)) {
      // Only convert measure fields to numbers
      // Dimensions and time dimensions should keep their original types
      if (numericFields && numericFields.includes(key)) {
        converted[key] = this.coerceToNumber(value)
      } else {
        converted[key] = value
      }
    }
    return converted
  }
 
  /**
   * Coerce a value to a number if it represents a numeric type
   */
  private coerceToNumber(value: any): any {
    // Handle null/undefined - preserve null values for aggregations
    if (value == null) return value
    
    // Already a number
    if (typeof value === 'number') return value
    
    // Handle string representations of numbers
    Eif (typeof value === 'string') {
      // Check for exact numeric strings
      if (/^-?\d+(\.\d+)?$/.test(value)) {
        return value.includes('.') ? parseFloat(value) : parseInt(value, 10)
      }
      
      // Check for other numeric formats (scientific notation, etc.)
      Eif (!isNaN(parseFloat(value)) && isFinite(parseFloat(value))) {
        return parseFloat(value)
      }
    }
    
    // Return as-is for non-numeric values
    return value
  }
 
  getEngineType(): 'sqlite' {
    return 'sqlite'
  }
}
 
/**
 * Factory function for creating SQLite executors
 */
export function createSQLiteExecutor(
  db: DrizzleDatabase,
  schema?: any
): SQLiteExecutor {
  return new SQLiteExecutor(db, schema, 'sqlite')
}