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                        7x   1x 1x 1x           6x     6x 4x 4x 1x   1x 2x   1x 1x   1x     3x               2x   2x 2x     4x 2x   2x     2x               8x     6x     5x   5x 4x       1x 1x                 1x                     14x  
/**
 * 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')
}