# ruby MySQL for windows
# TEXCELL 2019.01 Ver.1.08  2011.12 Ver.0.98
require 'fiddle/import'
require 'resolv'
require 'socket'

module MySqlKernel32
   extend Fiddle::Importer
   dlload "kernel32"
   extern "int lstrlen(int)",:stdcall
   extern "void RtlMoveMemory(void*,int,int)",:stdcall
end

module LibMysql
   extend Fiddle::Importer
   dlload (__dir__.encode("utf-8") + "/libmysql").encode("windows-31j")
   typealias("MYSQL","int")
   typealias("MYSQL_RES","int")
   typealias("MYSQL_ROW","int")
   extern "MYSQL mysql_init(void*)",:stdcall
   extern "void mysql_close(MYSQL)",:stdcall
   extern "MYSQL mysql_real_connect(MYSQL,void*,void*,void*,void*,unsigned int,void*,unsigned int)",:stdcall
   extern "int mysql_select_db(MYSQL,void*)",:stdcall
   extern "int mysql_query(MYSQL,void*)",:stdcall
   extern "int mysql_field_count(MYSQL)",:stdcall
   extern "MYSQL_RES mysql_store_result(MYSQL)",:stdcall
   extern "MYSQL_ROW mysql_fetch_row(MYSQL_RES)",:stdcall
   extern "unsigned int mysql_num_fields(MYSQL_RES)",:stdcall
   extern "unsigned int mysql_fetch_lengths(MYSQL_RES)",:stdcall
   extern "unsigned int mysql_fetch_fields(MYSQL_RES)",:stdcall
   extern "long long mysql_num_rows(MYSQL_RES)",:stdcall
   extern "void mysql_free_result(MYSQL_RES)",:stdcall
   extern "unsigned int mysql_errno(MYSQL)",:stdcall
   extern "int mysql_set_character_set(MYSQL,void*)",:stdcall
   extern "int mysql_ping(MYSQL)",:stdcall
   extern "unsigned int mysql_get_client_info()",:stdcall
   extern "unsigned int mysql_get_client_version()",:stdcall
   extern "unsigned int mysql_get_server_info(MYSQL)",:stdcall
   extern "unsigned int mysql_get_server_version(MYSQL)",:stdcall
   extern "int mysql_error(MYSQL)",:stdcall
end

class Mysqllib

   NOT_NULL_FLAG = 1
   PRI_KEY_FLAG = 2
   UNIQUE_KEY_FLAG  = 4
   MULTIPLE_KEY_FLAG  = 8
   BLOB_FLAG = 16
   UNSIGNED_FLAG = 32
   ZEROFILL_FLAG = 64
   BINARY_FLAG = 128
   ENUM_FLAG = 256
   AUTO_INCREMENT_FLAG = 512
   TIMESTAMP_FLAG = 1024
   SET_FLAG = 2048
   PART_KEY_FLAG = 16384
   GROUP_FLAG = 32768
   UNIQUE_FLAG = 65536

   MYSQLFIELD = Struct.new(:sname, :sorg_name, :stable, :sorg_table, :sdb, :scatalog, :sdef,
                        :ilength, :imax_length, :iname_length, :iorg_name_length, :itable_length,
                        :iorg_table_length, :idb_length, :icatalog_length, :idef_length,
                        :iflags, :idecimals, :icharsetnr, :itype, :stype)

   FName             = 4 * 0
   FOrg_name         = 4 * 1
   FTable            = 4 * 2
   FOrg_table        = 4 * 3
   FDb               = 4 * 4
   FCatalog          = 4 * 5
   FDef              = 4 * 6
   FLength           = 4 * 7
   FMax_length       = 4 * 8
   FName_length      = 4 * 9
   FOrg_name_length  = 4 * 10
   FTable_length     = 4 * 11
   FOrg_table_length = 4 * 12
   FDb_length        = 4 * 13
   FCatalog_length   = 4 * 14
   FDef_length       = 4 * 15
   FFlags            = 4 * 16
   FDecimals         = 4 * 17
   FCharsetnr        = 4 * 18
   FType             = 4 * 19
   FNON              = -1

   def initialize

      @ilport = 3306
      @pMysql = 0
      @pMySqlRes = 0
      @pMySqlField = 0
      @pMySqlRow = 0
      @iItemCount = 0
      @ilRecCount = 0
      @bipadconv = true
      @sencode = "UTF-8"
      @ftype = {
         0=>"MYSQL_TYPE_DECIMAL", 1=>"MYSQL_TYPE_TINY", 2=>"MYSQL_TYPE_SHORT", 3=>"MYSQL_TYPE_LONG",
         4=>"MYSQL_TYPE_FLOAT", 5=>"MYSQL_TYPE_DOUBLE", 6=>"MYSQL_TYPE_NULL", 7=>"MYSQL_TYPE_TIMESTAMP",
         8=>"MYSQL_TYPE_LONGLONG", 9=>"MYSQL_TYPE_INT24", 10=>"MYSQL_TYPE_DATE", 11=>"MYSQL_TYPE_TIME",
         12=>"MYSQL_TYPE_DATETIME", 13=>"MYSQL_TYPE_YEAR", 14=>"MYSQL_TYPE_NEWDATE", 15=>"MYSQL_TYPE_VARCHAR",
         16=>"MYSQL_TYPE_BIT", 246=>"MYSQL_TYPE_NEWDECIMAL", 247=>"MYSQL_TYPE_ENUM", 248=>"MYSQL_TYPE_SET",
         249=>"MYSQL_TYPE_TINY_BLOB", 250=>"MYSQL_TYPE_MEDIUM_BLOB", 251=>"MYSQL_TYPE_LONG_BLOB", 252=>"MYSQL_TYPE_BLOB",
         253=>"MYSQL_TYPE_VAR_STRING", 254=>"MYSQL_TYPE_STRING", 255=>"MYSQL_TYPE_GEOMETRY" }
      @cliver = getClientVersion[0]
   end

   def connect(shost,suser,spwd)

      if @bipadconv
         if (Resolv::IPv4::Regex =~ shost) == nil
            begin
               shost = Socket.gethostbyname(shost)[4].unpack("C4").join(".")
            rescue
               return 3
            end
         end
      end
      @pMysql = LibMysql.mysql_init(nil)
      if @pMysql == 0
         return 1
      end
      paMysql = LibMysql.mysql_real_connect(@pMysql,shost,suser,spwd,nil,@ilport,nil,0)
      if paMysql == 0
         return 2
      end
      return 0
   end

   def close

      freeresult
      LibMysql.mysql_close(@pMysql)
      @pMysql = 0
   end

   def errno

      ierno = LibMysql.mysql_errno(@pMysql)
      return ierno
   end

   def errmes

      pcem = LibMysql.mysql_error(@pMysql)
      il = MySqlKernel32.lstrlen(pcem)
      smes = "\0" * (il + 1)
      MySqlKernel32.RtlMoveMemory(smes,pcem,il)
      return smes[0,il]
   end

   def selectDB(sDBName)

      ir = LibMysql.mysql_select_db(@pMysql,sDBName)
      return ir
   end

   def query(sSql)

      ir = LibMysql.mysql_query(@pMysql,sSql)
      return ir
   end

   def store_Result

      @pMySqlRes = LibMysql.mysql_store_result(@pMysql)
      if @pMySqlRes == 0
         return 1
      end
      return 0
   end

   def execSQL(sSQL)

      freeresult
      ir = query(sSQL)
      if ir == 0
         ic = LibMysql.mysql_field_count(@pMysql)
         if ic > 0
            ie = store_Result
            if ie == 0
               @iItemCount = LibMysql.mysql_num_fields(@pMySqlRes)
               @ilRecCount = LibMysql.mysql_num_rows(@pMySqlRes)
               @pMySqlRow = LibMysql.mysql_fetch_row(@pMySqlRes)
               @pMySqlField = LibMysql.mysql_fetch_fields(@pMySqlRes)
            end
         end
      end
      return ir
   end

   def getRecord

      ardt = Array.new
      if @pMySqlRes == 0
         return ardt
      end
      itemlen = LibMysql.mysql_fetch_lengths(@pMySqlRes)
      if itemlen == 0
         return ardt
      end
      arItemlen = Array.new(@iItemCount)
      ilen = "\0" * 4
      for ia in 0..(@iItemCount - 1)
         MySqlKernel32.RtlMoveMemory(ilen,itemlen + ia * 4,4)
         arItemlen[ia] = ilen.unpack("I")[0]
      end
      for ia in 0..(arItemlen.length - 1)
         ps = "\0" * 4
         MySqlKernel32.RtlMoveMemory(ps,@pMySqlRow + ia * 4,4)
         pad = ps.unpack("I")[0]
         if pad != 0
            sdt = "\0" * (arItemlen[ia] + 1)
            MySqlKernel32.RtlMoveMemory(sdt,pad,arItemlen[ia])
            sa = sdt[0,arItemlen[ia]]
            if @sencode != nil
               sa = sa.force_encoding(@sencode)
            end
            ardt << sa
         else
            ardt << nil
         end
      end
      return ardt
   end

   def nextR

      if @pMySqlRes == 0
         return 2
      end
      @pMySqlRow = LibMysql.mysql_fetch_row(@pMySqlRes)
      if @pMySqlRow == 0
         return 1
      end
      return 0
   end

   def fielddt(pfield, ilenpos, idatapos)
   
      ps = "\0" * 4
      MySqlKernel32.RtlMoveMemory(ps,pfield + ilenpos,4)
      il = ps.unpack("I")[0]
      if idatapos != FNON
         MySqlKernel32.RtlMoveMemory(ps,pfield + idatapos,4)
         pdt = ps.unpack("I")[0]
         sdt = "\0" * (il + 1)
         MySqlKernel32.RtlMoveMemory(sdt,pdt,il)
         sda = sdt[0,il]
         if @sencode != nil
            sda = sda.force_encoding(@sencode)
         end
      else
         sda = nil
      end
      return il,sda
   end
   private :fielddt

   def getfields(icno)

      if @pMySqlField == 0 || icno < 0 || icno >= @iItemCount
         return nil
      end
      mysqlfield = MYSQLFIELD.new
      iw = if @cliver[0,3] == "5.5" then 84 else 80 end
      pfield = @pMySqlField + icno * iw
      mysqlfield.iname_length, mysqlfield.sname = fielddt(pfield,FName_length,FName)
      mysqlfield.iorg_name_length, mysqlfield.sorg_name = fielddt(pfield,FOrg_name_length,FOrg_name)
      mysqlfield.itable_length, mysqlfield.stable = fielddt(pfield,FTable_length,FTable)
      mysqlfield.iorg_table_length, mysqlfield.sorg_table = fielddt(pfield,FOrg_table_length,FOrg_table)
      mysqlfield.idb_length, mysqlfield.sdb = fielddt(pfield,FDb_length,FDb)
      mysqlfield.icatalog_length, mysqlfield.scatalog = fielddt(pfield,FCatalog_length,FCatalog)
      mysqlfield.idef_length, mysqlfield.sdef = fielddt(pfield,FDef_length,FDef)
      mysqlfield.ilength, sdy = fielddt(pfield,FLength,FNON)
      mysqlfield.imax_length, sdy = fielddt(pfield,FMax_length,FNON)
      mysqlfield.iflags, sdy = fielddt(pfield,FFlags,FNON)
      mysqlfield.idecimals, sdy = fielddt(pfield,FDecimals,FNON)
      mysqlfield.icharsetnr, sdy = fielddt(pfield,FCharsetnr,FNON)
      mysqlfield.itype, sdy = fielddt(pfield,FType,FNON)
      mysqlfield.stype = @ftype[mysqlfield.itype]
      return mysqlfield
   end

   def mysqlSetCharacterSet(csname)

      ir = LibMysql.mysql_set_character_set(@pMysql,csname)
      return ir
   end

   def ping

      ir = LibMysql.mysql_ping(@pMysql)
      return ir
   end

   def getClientVersion

      pc = LibMysql.mysql_get_client_info()
      il = MySqlKernel32.lstrlen(pc)
      bf = "\0" * (il + 1)
      MySqlKernel32.RtlMoveMemory(bf,pc,il)
      sinf = bf[0,il]
      iv = LibMysql.mysql_get_client_version()
      return sinf,iv
   end

   def getServerVersion

      pc = LibMysql.mysql_get_server_info(@pMysql)
      il = MySqlKernel32.lstrlen(pc)
      bf = "\0" * (il + 1)
      MySqlKernel32.RtlMoveMemory(bf,pc,il)
      sinf = bf[0,il]
      iv = LibMysql.mysql_get_server_version(@pMysql)
      return sinf,iv
   end

   def freeresult

      if @pMySqlRes != 0
         LibMysql.mysql_free_result(@pMySqlRes);
         @pMySqlRes = 0
         @pMySqlField = 0
         @pMySqlRow = 0
         @iItemCount = 0
         @ilRecCount = 0
      end
   end
   private :freeresult

   attr_accessor :ilport
   attr_accessor :bipadconv
   attr_accessor :sencode
   attr_reader :ilRecCount
   attr_reader :iItemCount

end
