All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mchange.sc.v1.sql.PsManager.scala Maven / Gradle / Ivy

The newest version!
/*
 * Distributed as part of mchange-commons-scala v0.4.0
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman 
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.sc.v1.sql;

import java.io.{BufferedInputStream,BufferedReader,ByteArrayInputStream,ByteArrayOutputStream,InputStream,Reader,StringReader};
import java.sql.{Blob,Clob,Connection,PreparedStatement,Ref,Time,Timestamp};
import java.sql.Types._;
import com.mchange.sc.v1.util.ClosableUtils._;
import scala.collection._;


object PsManager
{
  val BUFFER_SIZE = 16 * 1024 * 1024;

  def setParameter(ps       : PreparedStatement, 
		   i        : Int, 
		   value    : Any, 
		   typeCode : Int) : Unit =
		     setParameter( ps, i, value, typeCode, None );
		     

  def setParameter(ps       : PreparedStatement, 
		   i        : Int, 
		   value    : Any, 
		   typeCode : Int,
		   length   : Option[Long]) : Unit =
  {
    value match
    {
      case ( null ) => ps.setNull( i, typeCode );
      case ( bool : Boolean ) => ps.setBoolean( i, bool );
      case ( byte : Byte ) => ps.setByte( i, byte );
      case ( bs : Array[Byte] ) => ps.setBytes( i, bs );
      case ( s : Short ) => ps.setShort( i, s );
      case ( num : Int ) => ps.setInt( i, num );
      case ( f : Float ) => ps.setFloat( i, f );
      case ( d : Double ) => ps.setDouble( i, d );
      case ( arr : java.sql.Array ) => ps.setArray( i, arr );
      case ( sbd : BigDecimal ) => ps.setBigDecimal( i, sbd.bigDecimal );
      case ( jbd : java.math.BigDecimal ) => ps.setBigDecimal( i, jbd );
      case ( blob : Blob ) => ps.setBlob( i, blob );
      case ( clob : Clob ) => ps.setClob( i, clob );
      case ( sdate : java.sql.Date) => ps.setDate( i, sdate );
      case ( stime : Time ) => ps.setTime( i, stime );
      case ( stimestamp : Timestamp ) => ps.setTimestamp( i, stimestamp );
      case ( date : java.util.Date ) => 
	{
	  typeCode match
	  {
	    case DATE => ps.setDate( i, new java.sql.Date(date.getTime()) );
	    case TIME => ps.setTime( i, new java.sql.Time(date.getTime()) );
	    case _    => ps.setTimestamp( i, new java.sql.Timestamp(date.getTime()) ); // the default is TIMESTAMP
	  }
	}
      case ( ref : Ref ) => ps.setRef( i, ref );
      case ( str : String ) => ps.setString( i, str );
      case ( reader : Reader ) =>
	{
	  if ( length == None )
	    {
	      val sb = new StringBuilder( BUFFER_SIZE );

	      var br : BufferedReader = null;

	      try
	      {
		br = new BufferedReader( reader, BUFFER_SIZE );

		var c : Int = br.read();
		while ( c >= 0 )
		{
		  sb.append( c.toChar );
		  c = br.read();
		}

		val str = sb.toString();
		setParameter( ps, i, new StringReader( str ), typeCode, Some( str.length ) );
	      }
	      finally 
	      { attemptClose( br ); }
	    }
	  else
	    ps.setCharacterStream( i, reader, length.get );
	}
      case ( is : InputStream ) =>
	{
	  if ( length == None )
	    {
	      val baos = new ByteArrayOutputStream( BUFFER_SIZE );

	      var bis : BufferedInputStream = null;

	      try
	      {
		bis = new BufferedInputStream( is, BUFFER_SIZE );

		var b : Int = bis.read();
		while ( b >= 0 )
		{
		  baos.write(b)
		  b = bis.read();
		}

		val ba = baos.toByteArray;
		setParameter( ps, i, new ByteArrayInputStream( ba ), typeCode, Some( ba.length ) );
	      }
	      finally 
	      { attemptClose( bis ); }
	    }
	  else
	    ps.setBinaryStream( i, is, length.get );
	}
      case (any : AnyRef) =>
	{
	  if ( length == None )
	    ps.setObject( i, any, typeCode );
	  else
	    ps.setObject( i, any, typeCode, length.get.toInt );
	}
      case whatever @ _ => throw new IllegalArgumentException("Don't know what to do with parameter: " + whatever );
    }
  }

  def apply( sql : String, paramNames : List[String], paramTypeCodes : List[Int] ) = new PsManager( sql, paramNames, paramTypeCodes );

  // sql here refers to the package name (interesting)
  private[sql] def zipInfoLists(paramNames : List[String], paramTypeCodes : List[Int]) =
    {
      require( paramNames.length == paramTypeCodes.length );
      paramNames.zip( paramTypeCodes );
    }
}

/**
 *  info is a list of Tuples including the name of the column
 *  being substituted and the java.sql.Types typecode associated
 *  with the column.
 */ 
class PsManager( val sql : String, info : Vector[Tuple2[String,Int]] )
{
  // we work with info directly only in the following 5 functions,
  // 'cuz jumping between 1-based and zero-based is bug-prone
  // and annoying. we get these right, and do everything else
  // in terms of them.
  val indexForName : Map[String,Int] = { immutable.Map.empty[String,Int] ++ ( for (i <- 0 until info.length) yield ( info(i)._1 -> (i+1) ) ); }
  def tupForIndex( i : Int ) = info(i - 1);
  def nameForIndex( i : Int ) = tupForIndex(i)._1;
  def paramNames = info.map( _._1 );
  def tupForName( name : String ) = info( indexForName(name) - 1);
  val numParameters = info.length;


  def prepareStatement(con : Connection) : WrappedPreparedStatement = wrap( con.prepareStatement( sql ) );

  def apply(con : Connection) = prepareStatement(con);

  def setParameter( ps : PreparedStatement, i : Int, param : Any ) = 
    {
      val strTypeTup = tupForIndex(i);
      PsManager.setParameter(ps, i, param, strTypeTup._2);
    }

  def setParameter( ps : PreparedStatement, paramName : String, param : Any ) = 
    {
      val i = indexForName( paramName ); //throws an Exception if paramName is not found
      val strTypTup = tupForIndex(i);
      PsManager.setParameter(ps, i, param, strTypTup._2);
    }

  def overwriteParameters( ps : PreparedStatement, params : Map[String,Any] ) = params.foreach( tup => setParameter( ps, tup._1, tup._2 ) );

  def nullParameters( ps : PreparedStatement ) = for (i <- 1 to numParameters) setParameter( ps, i, null );

  def wrap( ps : PreparedStatement ) = new WrappedPreparedStatement( this, ps );

  def this( sql : String, paramInfos : List[Tuple2[String,Int]] ) = this( sql, Vector( paramInfos : _*) );

  def this( sql : String, paramNames : List[String], paramTypeCodes : List[Int] ) = this( sql, PsManager.zipInfoLists( paramNames, paramTypeCodes ) );
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy