2016-04-24

TDBCJDBC v0.1.1

Homepage


TDBCJDBC

About


Tcl DataBase Connectivity JDBC Driver

一些說明


使用 tclBlend 來呼叫 JDBC API。我並沒有每一個部份都測試過,只有執行過簡單的例子,用來確定我拿到的 JDBC driver 是可用的。

更新 2016-04-27:
增加 H2 database 與 Apache Derby 的簡單範例,這樣比較有名的 Java embedded database 選擇(HSQLDB, H2 and Apache Derby)就都有簡單範例了。

更新 2016-04-27:
加入 bigint type 的處理,更新 date, time, timestamp 與 numeric type 的處理方式

更新 2016-04-28:
使用 HSQLDB 測試更新後的 BLOB 處理,儲存一張小圖片進去,然後讀出來存成另外一張圖比對。目前看起來是正常工作的。

更新 2016-04-29:
binary type 處理加入 PostgreSQL 的 bytea type name 檢查,然後更正了一些我自己發現的小錯誤,並且更新文件。

更新 2016-05-04:
加入 CLOB type 的支援,同時更新 type 處理方面的  code。

2016-04-22

TDBC-ODBC and Firebird ODBC (update for Firebird 3.0.0)

測試環境:
Windows XP
Firebird 3.0.0
Firebird ODBC 2.0.4
Active Tcl 8.6.4.1

DSN 設定(和之前一樣):
TDBC-ODBC and Firebird ODBC 

package require tdbc::odbc

set connStr "DSN=Firebird DSN; UID=danilo; PWD=danilo;"
tdbc::odbc::connection create db $connStr

set statement [db prepare {create table person (id integer, name varchar(40))}]
$statement execute
$statement close

set statement [db prepare {insert into person values(1, 'leo')}]
$statement execute
$statement close

set statement [db prepare {insert into person values(2, 'yui')}]
$statement execute
$statement close

set statement [db prepare {SELECT * FROM person}]

$statement foreach row {
    puts [dict get $row ID]
    puts [dict get $row NAME]
}

$statement close

set statement [db prepare {drop table person}]
$statement execute
$statement close
db close

比較好的地方是,最後 drop table 不需要使用第二個 script 執行才行,之前會那樣做是因為如果直接 drop (可能會)有錯誤訊息而無法刪除,但是看起來 Firebird 3.0.0 已經和其它的資料庫行為一致了。

2016-04-20

Buiild tclBlend on Windows 7 (64bit)

使用 MSYS2 和 JDK8,需要更新 tcljava.m4,加入下列的段落:

        # Sun JDK 1.4 and 1.5 for Win32 (server JVM)

        F=lib/jvm.lib
        if test "x$ac_java_jvm_jni_lib_flags" = "x" ; then
            AC_MSG_LOG([Looking for $ac_java_jvm_dir/$F], 1)
            if test -f $ac_java_jvm_dir/$F ; then
                # jre/bin/client must contain jvm.dll
                # jre/bin/server directory could also contain jvm.dll,
                # just assume the user wants to use the server JVM.
                DLL=jre/bin/server/jvm.dll
                if test -f $ac_java_jvm_dir/$DLL ; then
                    AC_MSG_LOG([Found $ac_java_jvm_dir/$F], 1)
                    D1=$ac_java_jvm_dir/jre/bin
                    D2=$ac_java_jvm_dir/jre/bin/server
                    ac_java_jvm_jni_lib_runtime_path="${D1}:${D2}"
                    ac_java_jvm_jni_lib_flags="$ac_java_jvm_dir/$F"
                fi
            fi
        fi 

原來的只有 Sun JDK 1.4 and 1.5 for Win32 (client JVM),所以 server JVM 要自己加入。如果執行 autoconf 有困難,可以考慮直接修改 configure,找到 Sun JDK 1.4 and 1.5 for Win32 (client JVM) 的部份,把 jre/bin/client 的 client 改為 server。

其它的部份都一樣

2016-04-12

TDBC: get resultset columns type

只有在 TDBC::SQLite3 上測試過。目前可以拿到 table columns 的 type 資料,而 resultset 只傳回來 columns name list,利用 table columns 的 type 資料傳回一份對照表。

proc getColumnsType {COLUMNS TABLE_COLUMNS} {
    puts $COLUMNS
    puts $TABLE_COLUMNS
    set result [dict create]
    foreach column $COLUMNS {
        set column_2 [string tolower $column]
        set res [dict get $TABLE_COLUMNS $column_2]
        set type [dict get $res type]
        dict set result $column $type
    }
    
    return $result
}


set table_columns [db columns mylist]

set statement [db prepare {SELECT * FROM mylist}]
set resultset [$statement execute]
set res_columns [$resultset columns]

# test method
puts [getColumnType $res_columns $table_columns]

$statement close


我想我搞懂問題點在哪裡了。如果我操作超過一個表格,那該怎麼做?

如果有時間,我寫一份 TDBC 某個 driver 的研究看看 TDBC 的機制,然後再思考看看該怎麼做。

更新:
看起來我目前不會有操作超過一個表格要 get column type 的情況,所以我想…… 如果有需要再研究好了。


更新:

目前來看,TDBC 實作的策略可以分為二個。

第一個策略,建立 stubs 以後,運用 Tcl 8.6 Tcl_LoadFile,使用 database API
第二個策略,使用原有的 Tcl 套件,然後加上一層 TDBC interface

tdbc::connection

連線到 database, database tables and table columns, primary key..., etc meta data

tdbc::statement

當使用者使用 tdbc::connection prepare 函式,就會建立一個 tdbc::statement 物件。

建立物件時就會使用 tdbc::tokenize (Tcl code) 或者是 Tdbc_TokenizeSql 找出來使用者變數,然後使用 ? (或者是 SQL 語法接受的)取代使用者變數產生 params (有 type 等資料)並且產生 database 可以接受的 SQL 字串

但是這並不是 SQL 語法分析,只是找出 : 或者是 $ 開頭的字串。

tdbc::resultset

當使用者使用 statement object execute method 或者是其它的 command 會觸發 execute method 時就會建立一個 tdbc::resultset 物件。

在建立的時候,就會使用 params 的資料與取得使用者所連結的變數,真正的執行並且取得結果。

So...

如果真的有需要的話,應該是要更改 resultset 相關的部份,另外還要考慮如果不支援這樣操作的資料庫應該要怎麼做。

2016-04-10

TclBlend and JDBC

使用 MonetDB 的 JDBC Driver 進行測試。

參考資料:
[Activetcl] TclBlend - Examples. 

設定 CLASSPATH:
set CLASSPATH=c:\monetdb-jdbc-2.19.jar

#########################################################  
## Source packages. 
#########################################################  
#
package require java

#################################################################
# putsLog with timestamp.
####################################################################
proc putsLog { a } {

    set host [ info host ]

    set compTime [clock format [clock seconds] -format "%Y-%m-%d-%H.%M.%S"]

    puts "\[$host:$compTime\] $a"

}
#######################################
## Proc - jdbcConnect. 
#######################################
proc jdbcConnect { className url username password sqlQuery } {

   putsLog "proc - [info level 0 ]"

   # import required classes 
   java::import java.sql.Connection
   java::import java.sql.DriverManager
   java::import java.sql.ResultSet
   java::import java.sql.SQLWarning
   java::import java.sql.Statement
   java::import java.sql.ResultSetMetaData 
   java::import java.sql.DatabaseMetaData 

   # load database driver .
   java::call Class forName $className

   putsLog "connection URL is:  $url\n"   
   
   set ConnectionI [ java::call DriverManager getConnection $url $username $password] 

   putsLog "transaction isolation level is [ $ConnectionI getTransactionIsolation ]"

   putsLog "#########################################"
   putsLog "### Database connection details"
   putsLog "#########################################"

   # get the database metadata information.
   #Retrieves a DatabaseMetaData object that contains metadata about the database
   #to which this Connection object represents a connection.

   set DatabaseMetaDataI [ $ConnectionI getMetaData ]

   putsLog [ $DatabaseMetaDataI getDatabaseProductName ]
   putsLog [ $DatabaseMetaDataI getDatabaseProductVersion ]
   putsLog "database version [ $DatabaseMetaDataI getDatabaseMajorVersion ]\.[ $DatabaseMetaDataI getDatabaseMinorVersion ]"
   putsLog "driver version   [ $DatabaseMetaDataI getDriverName ] [ $DatabaseMetaDataI getDriverMajorVersion ]\.[ $DatabaseMetaDataI getDriverMinorVersion ]"
   putsLog "jdbc version     [ $DatabaseMetaDataI getJDBCMajorVersion  ]\.[ $DatabaseMetaDataI getJDBCMinorVersion  ]"
   putsLog "connect username [ $DatabaseMetaDataI getUserName ]"
   putsLog "transaction isolation level is [ $ConnectionI getTransactionIsolation ] \n"

   # get a list of table names in database.
   # if there are no tables the results set is empty.  

   set opt1 [java::field ResultSet TYPE_SCROLL_INSENSITIVE]
   set ResultSetI [ $DatabaseMetaDataI getCatalogs ]
   set ResultSetMetaDataI [ $ResultSetI getMetaData ] 
   set columnCount        [ $ResultSetMetaDataI getColumnCount ]

   puts "Create query\n" 

   set opt1 [java::field ResultSet TYPE_SCROLL_INSENSITIVE]
   set opt2 [java::field ResultSet CONCUR_READ_ONLY ]   
   set StatementI [ $ConnectionI createStatement $opt1 $opt2 ]
   
   $StatementI execute $sqlQuery   
   set ResultSetI         [ $StatementI getResultSet ]  

   puts "get a list of return columns\n" 
   set ResultSetMetaDataI [ $ResultSetI getMetaData ]  
   set columnCount        [ $ResultSetMetaDataI getColumnCount ]
   set i 1

   while { $i <= $columnCount } {
       set columnName [ $ResultSetMetaDataI getColumnName $i ]      
       lappend columnList $columnName
       incr i 
   }    

   unset i
   puts "loop over the results set and print column name, column value.\n" 

   while { [ $ResultSetI next ] == 1 } {
      foreach i $columnList {
          puts [ format "%-5s %-30s %-s" " " "$i" "[ $ResultSetI getString $i ]" ]
      } 

       puts [ format "\n%-5s \n" [ string repeat "#" 50] ]
   }    

   puts "Close Connections\n" 

  
   $ResultSetI  close 
   $ConnectionI close

}
######################################
# Main Control.
######################################

putsLog "executing [info script]"

# make script drive independent.

set drive [lindex [file split [info nameofexecutable]] 0 ] 
set reportFile   C:\\reports\\oracleConnect.txt
set reportFileId [ open $reportFile w ] 

set className    {nl.cwi.monetdb.jdbc.MonetDriver}
set url          jdbc:monetdb://localhost:50000/demo   
set username     monetdb
set password     monetdb

set sqlQuery "SELECT id, name FROM tables"  

jdbcConnect $className $url $username $password $sqlQuery


使用 HSQLDB 的 JDBC Driver 進行測試。

設定 CLASSPATH:
set CLASSPATH=c:\hsqldb.jar


#########################################################  
## Source packages. 
#########################################################  
#
package require java

#################################################################
# putsLog with timestamp.
####################################################################
proc putsLog { a } {

    set host [ info host ]

    set compTime [clock format [clock seconds] -format "%Y-%m-%d-%H.%M.%S"]

    puts "\[$host:$compTime\] $a"

}
#######################################
## Proc - jdbcConnect. 
#######################################
proc jdbcConnect { className url username password sqlQuery } {

   putsLog "proc - [info level 0 ]"

   # import required classes 
   java::import java.sql.Connection
   java::import java.sql.DriverManager
   java::import java.sql.ResultSet
   java::import java.sql.SQLWarning
   java::import java.sql.Statement
   java::import java.sql.ResultSetMetaData 
   java::import java.sql.DatabaseMetaData 

   # load database driver .
   java::call Class forName $className

   putsLog "connection URL is:  $url\n"   
   
   set ConnectionI [ java::call DriverManager getConnection $url $username $password] 

   putsLog "transaction isolation level is [ $ConnectionI getTransactionIsolation ]"

   putsLog "#########################################"
   putsLog "### Database connection details"
   putsLog "#########################################"

   # get the database metadata information.
   #Retrieves a DatabaseMetaData object that contains metadata about the database
   #to which this Connection object represents a connection.

   set DatabaseMetaDataI [ $ConnectionI getMetaData ]

   putsLog [ $DatabaseMetaDataI getDatabaseProductName ]
   putsLog [ $DatabaseMetaDataI getDatabaseProductVersion ]
   putsLog "database version [ $DatabaseMetaDataI getDatabaseMajorVersion ]\.[ $DatabaseMetaDataI getDatabaseMinorVersion ]"
   putsLog "driver version   [ $DatabaseMetaDataI getDriverName ] [ $DatabaseMetaDataI getDriverMajorVersion ]\.[ $DatabaseMetaDataI getDriverMinorVersion ]"
   putsLog "jdbc version     [ $DatabaseMetaDataI getJDBCMajorVersion  ]\.[ $DatabaseMetaDataI getJDBCMinorVersion  ]"
   putsLog "connect username [ $DatabaseMetaDataI getUserName ]"
   putsLog "transaction isolation level is [ $ConnectionI getTransactionIsolation ] \n"

   # get a list of table names in database.
   # if there are no tables the results set is empty.  

   set opt1 [java::field ResultSet TYPE_SCROLL_INSENSITIVE]
   set ResultSetI [ $DatabaseMetaDataI getCatalogs ]
   set ResultSetMetaDataI [ $ResultSetI getMetaData ] 
   set columnCount        [ $ResultSetMetaDataI getColumnCount ]

   puts "Create query\n" 

   set opt1 [java::field ResultSet TYPE_SCROLL_INSENSITIVE]
   set opt2 [java::field ResultSet CONCUR_READ_ONLY ]   
   set StatementI [ $ConnectionI createStatement $opt1 $opt2 ]
   
   $StatementI execute $sqlQuery   
   set ResultSetI         [ $StatementI getResultSet ]  

   puts "get a list of return columns\n" 
   set ResultSetMetaDataI [ $ResultSetI getMetaData ]  
   set columnCount        [ $ResultSetMetaDataI getColumnCount ]
   set i 1

   while { $i <= $columnCount } {
       set columnName [ $ResultSetMetaDataI getColumnName $i ]      
       lappend columnList $columnName
       incr i 
   }    

   unset i
   puts "loop over the results set and print column name, column value.\n" 

   while { [ $ResultSetI next ] == 1 } {
      foreach i $columnList {
          puts [ format "%-5s %-30s %-s" " " "$i" "[ $ResultSetI getString $i ]" ]
      } 

       puts [ format "\n%-5s \n" [ string repeat "#" 50] ]
   }    

   puts "Close Connections\n" 

  
   $ResultSetI  close 
   $ConnectionI close

}
######################################
# Main Control.
######################################

putsLog "executing [info script]"

# make script drive independent.

set drive [lindex [file split [info nameofexecutable]] 0 ] 
set reportFile   C:\\reports\\oracleConnect.txt
set reportFileId [ open $reportFile w ] 

set className    {org.hsqldb.jdbc.JDBCDriver}
set url          jdbc:hsqldb:file:testdb 
set username     SA
set password     ""

set sqlQuery "SELECT * FROM INFORMATION_SCHEMA.TABLES"  

jdbcConnect $className $url $username $password $sqlQuery

請參考上一篇文章(再加上上一次 Build TclBlend 的文章),二個都進行 patch 以後,在 8.6.5 看起來還不錯,至少可以正確的執行 MonetDB 與 HSQLDB JDBC driver。

TclBlend: Tcl core patch

Info from Tcler's wiki


在之前我把範圍縮小到 8.6.0 到 8.6.1 以後,就不知道要怎麼樣才能夠修正,不過有人找到 root cause 了。

Within the function "Tcl_GetCommandFromObj", change
from:
    if (SetCmdNameFromAny(interp, objPtr) != TCL_OK) {
        return NULL;
    }
to:
 if (tclCmdNameType.setFromAnyProc(interp, objPtr) != TCL_OK) {
        return NULL;
    }

經過檢查 8.6.5,確定之前的小測試程式不會當機。如果 Tcl core 會接納這個 patch,那麼至少 Tcl core 這邊造成的當機因素就消失了。

2016-04-07

Fix SVN "Failed to run the WC DB work queue" problem

svn failed to run the wc db work queue associated with file (windows symbol ? issue)

如果使用 SQLite3 的 command line tool,
cd {work-dir-base}
sqlite3 .svn/wc.db "delete from work_queue"

因為我有裝 TDBC-SQLite3,所以就沒有下載 SQLite3 的 command line tool,直接使用 TDBC 來解決問題,下面是大概的樣子:
#!/usr/bin/tclsh
package require tdbc::sqlite3

# Fix SVN "Failed to run the WC DB work queue" problem
tdbc::sqlite3::connection create db "d:/android/.svn/wc.db" 

set statement [db prepare {delete from work_queue}]
$statement execute
$statement close

db close

這是在 Android 6.0.x code base 上發生的,原因請參閱 Forbidden file and folder names on Windows

The following are reserved names, which cannot be assigned to a folder or file (normally):
  • CON
  • PRN
  • AUX
  • CLOCK$ (NT and older)
  • NUL
  • COM1
  • COM2
  • COM3
  • COM4
  • COM5
  • COM6
  • COM7
  • COM8
  • COM9
  • LPT1
  • LPT2
  • LPT3
  • LPT4
  • LPT5
  • LPT6
  • LPT7
  • LPT8
  • LPT9
而我發現,有一個 kernel 下的檔案叫做 aux.c,所以在 Windows 平台下視為不正常的檔名。解決的方法是先建立與刪除檔案,再 svn update 或者是 svn cleanup。

建立檔案:
echo "" >  \\.\d:\android\kernel\drivers\gpu\drm\nouveau\core\subdev\i2c\aux.c

刪除檔案:
del \\.\d:\android\kernel\drivers\gpu\drm\nouveau\core\subdev\i2c\aux.c

更新: android\external\libunwind 下有一個 aux 目錄,我發覺 Windows 會非常直接的擋下來,所以我想要在 Windows 平台上看 Android 6.0.x 的 source code 將成為困難的任務,除非 Windows 修改檢查檔名的規則,不然就…… 放生吧。

tcljsonnet v0.3

首頁:
tcljsonnet


主要更新:
將 Jsonnet code base 版本升到 v0.8.8 版。