วันอังคารที่ 15 เมษายน พ.ศ. 2557

Web server in 3 lines with Golang

ผมเริ่มเขียน Golang ด้วยโจทย์ที่ต้องเขียน RESTFul Service สำหรับ  Enterprise mobile application เชื่อมกับ MSSQL ที่มีอยู่แล้ว บน Windows เลยเลือก Golang เพราะสะดวกในการ deploy สุด

คำถามที่ทำให้ตัดสินใจในการเลือกใช้ Golang ตอนนั้นก็คือ ใช้อะไรก็ได้ ที่พอ build แล้วส่งไปให้ support copy ไปวางบน server เรียกโปรแกรมแล้วใช้ได้เลย

Golang นี่แหละครับ ทำมาแล้ว...

พอเขียน Rest service จบ ก็เริ่มมี Requirement เพิ่มมาว่า อยากได้ report ก็วางแผนว่าจะทำเป็น Web page นี่แหละ ให้เลือกตัวเลือกที่จะทำรายงาน แล้วส่งไป query ผ่าน REST API ที่มีอยู่แล้ว ออกมาแสดงผล

ปัญหาคือจะเอาอะไรมาทำ file server?

ถ้าเลือก NGINX หรือ Apache เราคงต้องเป็นคนไปติดตั้งให้แน่ๆ Support เอาไม่อยู่

Martini
Golang web application framework ที่พึ่งเกิดและได้รับความนิยมอย่างรวดเร็จ น่าสนใจตรง Martini บอกว่า สามารถ support static file out of the box เลย คือเพิ่มไฟล์ใหม่เข้าไปปุ๊ป ก็เรียกผ่าน http ได้เลย --- น่าสนใจ ---

แต่งานที่ทำอยู่ใช้  go-rest-json ของ Antonie กับ default SQL framework ของ Go เอง แค่นี้ก็ทำงานได้ดีอยู่แล้ว และด้วยความฝักไฝ่ใน Benchmark, Go pure นี่ทิ้ง Revel ขาดเลยนะครับ แล้ว Martini ทำความเร็วสู้ Gorlilla ไม่ได้ด้วย เลยสองจิตสองใจที่จะใช้ Martini

ตัดสินใจเขียนเอง
ต้องยอมรับว่า เอาเข้าจริงๆ ที่ผ่านมาผม focus ที่ project มากจนไม่ได้ดู net/http ว่าทำอะไรได้ขนาดไหน วันนี้เลยไล่ดูหน่อยพบว่า

จริงๆ แล้ว net/http นี่มันสมบูรณ์แบบนะครับ ที่ว่ากันว่า ไม่ต้องใช้ external framework นี่เรื่องจริง

เลยลองเขียน static file server ออกมาดูได้ตามนี้

package main
import "net/http"
func main()  { http.ListenAndServe(":8080", http.FileServer(http.Dir("/Users/McDuck/Home"))) }

วันอังคารที่ 25 มีนาคม พ.ศ. 2557

C, Token-parsing preprocessor

ระหว่างปี 1995-1997 เป็นช่วงเปลี่ยนถ่ายจาก 16bit เป็น 32bit ในโลกของ Windows (ก่อนเป็น Win32) หลายท่านอาจจำได้ ปัญหาใหญ่ๆ ของ "C" โปรแกรมเมอร์หลายคนก็คือ การนำ Code เก่า หรือ DLL เก่าที่เป็น 16bit port มาเป็น 32bit ถ้ามีเวลาก็ค่อยๆ port โดยเริ่มจาก GlobalAlloc, GlobalLock, GlobalFree etc. แทนที่ด้วย malloc, free แบบที่ใช้ในปัจจุบัน

หลายคนที่มี code ใหญ่ๆ หรือมี DLL ที่ไม่มี code ไม่สามารถ port ได้ แต่ต้องการทำ Application ให้เป็น Win32 ก็มีทางเลือกอีกทางคือการใช้ Thunk layer ในการ เรียก 16bit API ด้วย 32bit app หรือ 16bit app เรียก 32bit API (อย่างหลังนี่ไม่ค่อยเห็น)

ซึ่งผมก็เป็นหนึ่งในนั้น....

เรื่องที่จะเล่าไม่ใช่เรื่อง Thunk layer หรือ Interoperability ของ Win16 กับ Win32 แต่เป็น Technique ที่ผมทำเอามาจาก Microsoft Excel source code (95) มาใช้กับการสร้าง Thunk layer ณ. เวลานั้น

Technique ที่ว่าคือ การทำ Token-parsing preprocessor, ซึ่งมีประโยชน์มากในการ define ชื่อ API ที่ทำให้เราสามารถจัดกลุ่มของ API ที่จะเรียกใช้เป็นหมวดหมู่ได้ง่าย (ดูตัวอย่างครับ)

#include <stdio.h>

#define GROUP_FIRST     _1_
#define GROUP_SECOND    _2_
#define TYPE_16         16
#define TYPE_32         32

#define METHOD_HELPER(a, b, c) a##b##c
#define METHOD(a, b, c) METHOD_HELPER(a, b, c)

int method_1_16(int x) {
   printf("method_1_16 : %d\n", x);
}

int method_2_16(int x) {
   printf("method_2_16 : %d\n", x);
}

int method_1_32(int x) {
   printf("method_1_32 : %d\n", x);
}

int method_2_32(int x) {
   printf("method_2_32 : %d\n", x);
}

int main() {
   METHOD(method, GROUP_FIRST, TYPE_32);
   METHOD(method, GROUP_SECOND, TYPE_16);
   return 0;
}


Happy hacking!!! :)

วันเสาร์ที่ 1 มีนาคม พ.ศ. 2557

กลับมาหา ภาษาที่คุ้นเคย, C++

ในที่สุดก็ต้องกลับมาภาษาที่คุ้นเคย C/C++ ดีใจได้เขียนแบบจริงๆ จังๆ อีกครั้ง โครงการดีด้วย...

จะว่าไป C/C++ เป็นภาษาเดียวในชีวิตที่ไม่ต้องเปิดคู่มือ (นอกจาก features ใหม่ๆ บางตัวใน C++11)

ทำให้สะท้านเหมือนกัน ตอนเรียน Go (lang) ก็ชอบวิธีการ assignment อย่าง
X := 12 // X เป็น Integer
ใน C++11 ก็มี
auto X = 12; 

ตอนปี 2008 ผมเคยซื้อ Library Qt ตอนนั้น พึ่งจะเป็น Release version 4.0 มีลูกค้าจ้างพัฒนาโปรแกรมสอง Platform ตือ OSX และ Windows ตัดสินใจจ่ายค่า Library ให้กับ Troll ไป เกือบสองแสนบาทสำหรับ สอง Platform 

สถานะ Qt ตอนนี้ มันสุดยอดจริงๆ นอกจากเขียนบน Linux, OSX, Windows ได้แล้ว ยัง Cross compile ไป iOS และ Android ได้อีก 
Android นี่สะดวกกว่าเพื่อน เขียนได้ทั้งบน Linux, OSX และ Windows เลย ส่วน iOS ต้องเขียนบน OSX

ตอนนี้ได้กลับมาใช้อีก ดีจัง (ตังค์อยู่ครบ)

แเละเหมือนเดิมคือ Oracle Call Interface สำหรับ Qt (QOCI) ไม่สามารถใช้ได้ ต้อง Build เอง เพราะต้องไป download OCI Library และรับทราบ License agreement ของ Oracle ก่อน 

วิธี Build QOCI บน OSX

download instanceclient มาจาก Oracle.com ตาม blog ก่อนหน้านี้ http://nanusorn.blogspot.com/2014/02/build-oracle-call-interface-golang.html

เวลา install Qt 5.2.x จะอยู่ที่ ~/Qt5.2.1 ให้ change directory ไปที่ ~/Qt5.2.1/5.2.1/Src/qtbase/src/plugins/sqldrivers/oci

$ export PATH=$PATH;~/Qt5.2.1/5.2.1/clang_64/bin

เรียก qmake สร้าง Makefile ใน folder ~/Qt5.2.1/5.2.1/Src/qtbase/src/plugins/sqldrivers/oci
$ qmake -o Makefile

แก้ Makefile.Release และ Makefile.Debug
โดยเพิ่ม -I/usr/lib/instantclient_11_2/sdk/include ที่ท้ายบรรทัด INCPATH
และ
เพิ่ม LFLAGS        += -L/usr/lib/instantclient_11_2 -lclntsh ใต้บรรทัด LFLAGS

แล้วเรียก 
$ make

วิธีนี้ง่ายสุด เร็วสุด ไม่ต้อง re-build Qt ทั้งตัว...

---------------------------------------------------------------
ถาม : กลับมาเขียน Qt เพื่อ?
ตอบ : เพื่อต้องการใช้ C++ เขียน Consumer สำหรับ rabbitMQ 

ถาม : แค่นี้หรือ?
ตอบ : ป่าวจะเอาไปเขียน Client Application บน Cubieboard ด้วย

ถาม : แล้วอย่างอื่นล่ะ?
ตอบ : ตอนนี้สนใจเอามาใช้พัฒนา Web Application ด้วยนะ เช่น
ตอบจริงๆ : ที่ว่ามาน่ะ ตอบเล่นๆ :p แต่เล่นจริงๆ นะ ปี๊ดส์ เลย



วันจันทร์ที่ 17 กุมภาพันธ์ พ.ศ. 2557

Build : Oracle Call Interface สำหรับ Golang

คืออยากใช้นะครับ แต่แหม่... มีแค่ https://github.com/mattn/go-oci8 ที่ดูดีสุด แต่คนเขียนเหมือนจะเขียนแล้วหนีไปแอบที่ไหนไม่รู้ ไม่บอกอะไรเลย

อยากใช้ก็ต้อง งม.. สิครับ

OCI ก็ต้องใช้ downlaod instance client มาก่อน (ตามสูตร) ลงทะเบียนแล้ว download จาก http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html ผมใช้ OSX (Maverick)

Download

  • instantclient-basic-macos_x64-11.2.0.3.0.zip
  • instantclient-sdk-macos_x64-11.2.0.3.0.zip
แล้วผมทำแบบนี้ครับ
  • unzip basic แล้ว move ไปไว้ที่ /usr/lib/instantclient_11_2
  • sym link 
    • ln /usr/lib/instantclient_11_2/libclntsh.dylib.11.1 /usr/lib/libclntsh.dylib.11.1
    • ln /usr/lib/instantclient_11_2/libocci.dylib.* /usr/lib/libocci.dylib.*
    • ln /usr/lib/instantclient_11_2/libociei.dylib /usr/lib/libociei.dylib
    • ln /usr/lib/instantclient_11_2/libnnz11.dylib /usr/lib/libnnz11.dylib
  • ดูว่าเครื่องติดตั้ง pkg-config รึยัง ถ้ายังติดตั้ง ถ้ายังใช้ MacPort ก็ได้ครับ
    • sudo port install pkgconfig
  • แล้วสร้าง oci8.pc ไว้ที่ /usr/lib/pkconfig ตามนี้ครับ (ไฟล์จากเครื่องผม)
    • prefix=/usr/lib/instantclient_11_2
      libdir=${prefix}
      includedir=${prefix}/sdk/include/

      Name: OCI
      Description: Oracle database engine
      Version: 11.2
      Libs: -L${libdir} -lclntsh
      Libs.private: 
      Cflags: -I${includedir}
  • เริ่ม  Build 
    • ไปที่ project workspace path 
    • $ export GOPATH=`pwd`
    • $ go get github.com/mattn/go-oci8
    • Go จะ Build และ link OCI ให้อัตโนมัติ โดยใช้ oci8.pc ที่เราสร้างไว้
  • ถ้าไม่มีอะไรผิดพลาด (error report) ถือว่าเสร็จเรียบร้อย
  • ทดสอบ
    • เข้าไปที่ $GOPATH/src/src/github.com/mattn/go-oci8/example
    • แก้ไฟล์ oracle.go บรรทัด 
      • db, err := sql.Open("oci8", "scott/tiger")
      • เป็น config ของ ORA ของเรา
      • ของผมเป็นแบบนี้ครับ
      • db, err := sql.Open(
        "oci8", "system/manager@192.168.1.40:1521/hello")
    • เรียก $go run oracle.go

ไม่พบ Error เสร็จ เรียบร้อย...
Modified และ Fix error จาก : https://github.com/Centny/Centny/blob/master/Articles/How%20build%20github.com:mattn:go-oci8.md 

เขียน Windows Services ด้วย Golang

ช่วงปีหลัง หนีไม่พ้นต้องเขียนอะไรที่เกี่ยวกับ Windows อย่างหลีกเลี่ยงไม่ได้ พอจะตัดสินใจใช้ Golang คำถามที่ตามมาคือแล้วถ้าต้องเขียน services ล่ะ ใช้ golang ได้รึเปล่า

คำตอบวันนี้คือ "ได้"

ผมเอา code จาก RESTful ของ Blog ที่แล้วมาใช้ทำ service โดยการ compile code บน windows ให้เป็น .exe ก่อน

> go build restmain.go

จะได้ restmain.exe จากนั้นผมใช้ NSSM - the Non-Sucking Service Manager (มาพร้อม source code และ execute binary) unzip แล้ว

> nssm McDuckService [path to]restmain.exe

เรียบร้อยลองเปิด service management อ่า... McDuckService มาแล้ว Double Click แล้ว Start เลย... 


[เพิ่มเติม]

  • เวลาจะลบ service ออกใช้
    • > sc delete McDuckService
  • command line ที่เกียวกับ service ต้องเป็น administrator right 
  • อ้างอิง http://sanatgersappa.blogspot.com/2013/07/windows-service-with-go-easy-way.html
    • เขียนก่อนผมเกือบปี ใช้ nssm เหมือนกันเลย





วันศุกร์ที่ 14 กุมภาพันธ์ พ.ศ. 2557

ลองเขียน REST API ด้วย Go (with framework)

ก่อนนี้ลองเขียน web application ด้วย pure Go มาแล้ว และได้เห็นพิษสงของ Go พอสมควร คราวนี้ขอลอง REST API สักหน่อย แต่อยากได้แบบ ง่ายมากๆ ประมาณ KISS เลย (Keep It Siimple & Studip) เลยขอลองใช้ Framework ในตลาดดูบ้าง

ผมเลือก http://github.com/ant0ine/go-json-rest มาลองเล่นเป็นตัวแรก เริ่มเลย...

สร้างไฟล์ชื่อ restmain.go (หรืออะไรก็ได้)
แทรก import ก่อน

import (
   "net/http"
   "github.com/ant0ine/go-json-rest"
)

แล้วสร้าง func main

func main() {
handler := rest.ResourceHandler{}
handler.SetRoutes(
rest.Route{"Get", "/users/:id", GetUser},
)
http.ListenAndServe(":8080", &handler)
}

อธิบายได้ว่า ประกาศ handler ด้าย rest ของ go-json-rest แล้ว SetRoutes ไปที่ url ที่เราต้องการ จากนั้นยัดเข้าท่ามาตรฐานของ net/http package ของ Go

ไปดูที่ handler กัน

func GetUser(w *rest.ResponseWriter, req *rest.Request) {
user := User{
Id:   req.PathParam("id"),
Name: "McDuck",
}
w.WriteJson(&user)
}

ทดลองง่ายๆ ด้วยการส่งอะไรมา (ผ่าน url parameter) ก็จัดการส่งกลับ (ด้วย json format) เราก็ต้องสร้าง struct สำหรับการเก็บข้อมูลก่อนแปลงเป็น json

type User struct {
Id   string
Name string
}

แล้วก็ assign (:=) user โดยมี Id = ค่า id ที่ส่งผ่านมาทาง url parameter และ name (อันนี้ใส่เอง) จากนั้นใช้ res.ResponseWriter เขียน Json กลับไปที่ resquester

ก่อน run set GOPATH ก่อน (path เดียวกับ ที่เราเขียน code ด้านบน...)
$ export GOPATH=`pwd`

(windows platform ไปหาทางเอาเอง นาทีนี้กำลังงอล Windows,  Hang อยู่ได้ แม่ม...)

จากนั้นเรียก
$ go get .
go จะ download source มาจาก github.com/ant0ine/go-json-rest ไว้ที่ src ให้เรา จากนั้น
$ go run restmain.go

แล้วเปิด browser ไปที่ http://localhost:8080/users/1001100
Result :

{
  "Id": "10010",
  "Name": "McDuck"
}

[ง่ายดีจังตังค์อยู่ครบ]

source restmain.go

//-------------------------------------------------------------
package main

import (
 "github.com/ant0ine/go-json-rest"
 "net/http"
)

type User struct {
 Id   string
 Name string
}

func GetUser(w *rest.ResponseWriter, req *rest.Request) {
 user := User{
  Id:   req.PathParam("id"),
  Name: "McDuck",
 }
 w.WriteJson(&user)
}

func main() {
 handler := rest.ResourceHandler{}
 handler.SetRoutes(
  rest.Route{"Get", "/users/:id", GetUser},
 )
 http.ListenAndServe(":8080", &handler)
}

//-------------------------------------------------------------

วันพุธที่ 12 กุมภาพันธ์ พ.ศ. 2557

Tough Week : Oracle ODBC บน Linux

เป็นอาทิตย์ที่ยากลำบากมากช่วงหนึ่งแต่ก็ผ่านมาได้ จนต้อง blog ไว้เผื่อมีประโยชน์กับ ตัวเองและคนอื่น :)

กว่าสองเดือนที่ผมได้เขียน Server สำหรับการทำ Semi-Realtime transaction processing server (ตั้งชื่อให้มันยากๆ ไปงั้นแหละ) โดยใช้ Erlang ในการพัฒนา โจทย์ก็คือต้องเก็บ transaction ที่ส่งผ่านเข้ามาเป็น XML จากนั้น Encode XML เก็บเข้า Oracle แล้ว Archive XML ไว้ใน Document Base สักตัว (ผมเลือกใช้ MongoDB)

แรกๆ ใช้ Erlang OCI (Oracle Call Interface) ก็ดูดี แต่พบว่า มีปัญหาหลายส่วน จึงตัดสินใจเปลี่ยนจากการ code โดยการ generate SQL statement ใน Erlang แล้วยิง Statement ผ่าน OCI มาเป็นการเรียกน Stored Procedure ผ่าน ODBC แทน

Code เสร็จแล้ว ถึงเวลาต้อง Deploy แล้ว... ผมเลือก Debian 7.4 ที่ต้องติดตั้งเพิ่มก็มี

  • Erlang (ใช้ R16B03-1) ติดตั้ง package เพิ่มเติมตามนี้
    • sudo apt-get install build-essential libncurses5-dev m4
    • sudo apt-get install openssl libssl-dev
    • sudo apt-get install unixodbc-dev
  • git
  • mongodb (ใช้ repo ของ 10 gen : http://docs.mongodb.org/manual/tutorial/install-mongodb-on-debian/ )
  • และ ODBC 
ปัญหาก็คือ Instance Client ของ Oracle (download จาก http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html ) support .rpm ผมใช้ alien แปลง .rpm เป็น .deb ซึ่งก็ติดตั้งได้ แต่ไม่สามารถ connect กับ server ได้ เลยเปลี่ยนไปจาก Debian  ไปเป็น CentOS แทน เพราะไม่รู้ว่า ปัญหาจริงๆ คืออะไร

ย้ายบ้านมา CentOS (ขั่วคราว)
ข้อดีของ VMWare คือตรงนี้แหละ อยากลอง Distro ไหนง่ายนิดเดียว
ติดตั้ง CentOS เสร็จ ไม่ต้องคิดมาก download rpm จาก Oracle's instance-client site page มาเลย ไม่ต้องเยอะเอาแค่
  • oracle-instantclient12.1-basic-12.1.0.1.0-1.x86_64.rpm 
  • oracle-instantclient12.1-sqlplus-12.1.0.1.0-1.x86_64.rpm
  • instantclient-odbc-linux.x64-12.1.0.1.0.zip 
แล้ว $sudo rpm -ivh oracle-instanceclient12.1-basic-12.1.0.1.0-1.x86_64.rpm ไปก่อน ตากด้วย $unzip  instantclient-odbc-linux.x64-12.1.0.1.0.zip แล้ว copy libsqora.so.12.1 ที่ได้จากการ unzip ไปไว้ที่ /usr/lib/oracle/12.1/client64/lib

จากนั้นติดตั้ง unixODBC 
$sudo yum install unixODBC

export environment 

$ export ORACLE_HOME=/usr/lib/oracle/12.1/client64
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib

$ export TWO_TASK=//xxx.xxx.xxx.xxx:1521/listener
(note : listener ต้องตรงกับ server นะครับ)

note : ผมสร้าง oracle.conf ที่มี 
/usr/lib/oracle/12.1/client64/lib
หนึ่งบรรทัด ไว้ที่ /etc/ld.so.conf.d แล้วเรียก 
$ sudo ldconfig
หนึ่งที ด้วย :D

จากนั้นก็ไป copy tnsnames.ora มาจาก server (server ผมเป็น windows 7) เอามาจาก c:/app/[username]/product/11.2.0/dbhome_1/network/admin (ลองไล่ๆ ดูนะครับ แต่ละเครื่องไม่เหมือนกัน อยู่ที่การติดตั้ง)
ผมแก้ tnsnames.ora นิดหน่อยครับ เหลือแต่ 


_SID_NAME_ =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)
        (HOST = xxx.xxx.xxx.xxx)
        (PORT = 1521)
      )
    )
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = _SID_NAME_)
    )
  )

_SID_NAME_ ตรงกับ SID ที่ server 
ไปตามอ่านใน oracle document พบว่าให้สร้าง tnsnames.ora ไว้ที่ $ORACLE_HOME/network/admin ก็ไปสร้างไว้ตรงนั้นตามเอกสาร 

จากนั้นสร้าง ODBC's drive profile ใน /etc/odbcinst.ini
ตามนี้ครับ 

[OracleODBC-12g]
Description = Oracle ODBC driver for Oracle 12g
Driver64 = /usr/lib/oracle/12.1/client64/lib/libsqora.so.12.1
FileUsage = 1
Driver Logging = 7

เน้นนะครับ : Driver64 เพราะผมลง x86_64 ถ้า google จะเจอแต่ Driver เฉยๆ (งมตั้งนาน) 
แล้วชี้ไปที่ libsqora.so.12.1 ที่ได้จากตอนที่เรา unzip ตอนต้นๆ

จากนั้นสร้าง DSN ใน /etc/odbc.ini ตามนี้ครับ

[simple]
Driver = OracleODBC-12g
DSN = OracleODBC-12g
ServerName = _SID_NAME_
UserID = _USER_
Password = _PASSWORD_

Note : Driver ต้องตรงกับที่เรากำหนดไว้ใน odbcinst.ini, ServerName ตรงกับ _SID_NAME_ ใน tnsnames.ora ส่วน _USER_ _PASSWORD_ ก็ตรงกับ oracle server ที่เราตั้งไว้

ทดสอบ....
ทดสอบ DSN ที่ส้รางขึ้นด้วย 
$ isql simple
simple คือชื่อ DSN ที่เราสร้างขึ้นใน odbc.ini

ผลลัพท์
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+

ผมเจอ error ไม่ต่ำกว่า 4-5 ตัว (แต่ละตัวทำเอามึน) เช่น
Error ORA-12154 :  TNS:could not resolve the connect identifier specified
อันนี้ตำแหน่ง tnsnames.ora อยู่ผิดที่ 

Debug ด้วย Log
หากพบปัญหาตอนเรียก isql ให้ใส่  -v เพื่อดู ORA error number แล้ว google error number ดู ถ้ายังไม่ได้อีก ผมใช้ strace ตามนี้

$ strace -fac -o isql.log isql -v simple

เช่นตอนกำหนดตำแหน่ง tnsnames.ora ผิด ผมเปิด isql.log พบว่า นั่นไง by default isql จะเปิด tnsnames.ora จาก 
"/home/[user]/.tnsnames.ora"
"/etc/tnsnames.ora"
"/usr/lib/oracle/12.1/client64/lib/network/admin/tnsnames.ora"
เป็นต้น

การใช้ sqlplus64 ตามด้วย custom tns connect ORA server ได้ ไม่ได้หมายความว่า ODBC จะ connect ได้ เช่น 
$ sqlplus64 user/password@//XXX.XXX.XXX.XXX/_SID_NAME_
connect ได้ query ได้ แต่ isql ไม่ works

การติดตั้ง sqleveloper Connect ORA server ได้ ไม่ได้หมายความว่า ODBC จะ connect ได้ เช่นกัน

อาจไม่ cover ทุก error ที่เจอ แต่ใคร install Oracle ODBC Driver บน Linux แล้วพบปัญหาลองแลกเปลี่ยนกันดูครับ 

ผมยังใหม่กับ Oracle มาก :D