parent
44ef416d2f
commit
0ece86c478
|
|
@ -31,7 +31,7 @@ const defaultMaxRows = 1000
|
|||
const oracleCharsetZHS32GB18030 = 854
|
||||
const legacyAgentSessionID = "__legacy__"
|
||||
const maxAgentSessions = 256
|
||||
const oracleLegacyLOBMaxMajorVersion = 11
|
||||
const oracleLegacyLOBMaxMajorVersion = 10
|
||||
const oracleDatabaseVersionProbeTimeout = 3 * time.Second
|
||||
|
||||
const oracleDatabaseVersionSQL = `
|
||||
|
|
@ -390,6 +390,7 @@ type triggerInfo struct {
|
|||
type server struct {
|
||||
db *sql.DB
|
||||
params connectParams
|
||||
legacyLOBFetchDeferred bool
|
||||
sessions map[string]*querySession
|
||||
tableReadSessions map[string]*querySession
|
||||
nextSessionID int64
|
||||
|
|
@ -669,6 +670,11 @@ func (s *server) handleLine(line string) (response, bool) {
|
|||
}
|
||||
|
||||
func (s *server) dispatch(method string, params map[string]json.RawMessage) (any, bool, error) {
|
||||
if oracleMethodMayReadLOB(method) {
|
||||
if err := s.ensureLegacyOracleLOBFetch(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
switch method {
|
||||
case "handshake":
|
||||
return map[string]any{
|
||||
|
|
@ -806,46 +812,29 @@ func (s *server) dispatch(method string, params map[string]json.RawMessage) (any
|
|||
|
||||
func (s *server) connect(params connectParams) error {
|
||||
_ = s.disconnect()
|
||||
db, effectiveParams, err := openSessionDB(params, 15*time.Second)
|
||||
db, err := openConfiguredSessionDB(params, 15*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
if _, err := db.ExecContext(ctx, "ALTER SESSION SET NLS_LANGUAGE='AMERICAN'"); err != nil {
|
||||
db.Close()
|
||||
return err
|
||||
}
|
||||
majorVersion, versionKnown := oracleServerMajorVersion(db, 15*time.Second)
|
||||
s.db = db
|
||||
s.params = effectiveParams
|
||||
s.params = params
|
||||
s.legacyLOBFetchDeferred = shouldUseLegacyOracleLOBFetch(params, majorVersion, versionKnown)
|
||||
return nil
|
||||
}
|
||||
|
||||
func openSessionDB(params connectParams, timeout time.Duration) (*sql.DB, connectParams, error) {
|
||||
func openConfiguredSessionDB(params connectParams, timeout time.Duration) (*sql.DB, error) {
|
||||
db, err := openAndPingDB(params, timeout)
|
||||
if err != nil {
|
||||
return nil, params, err
|
||||
return nil, err
|
||||
}
|
||||
if hasOracleLOBFetchOption(params) {
|
||||
return db, params, nil
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
if _, err := db.ExecContext(ctx, "ALTER SESSION SET NLS_LANGUAGE='AMERICAN'"); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
majorVersion, ok := oracleServerMajorVersion(db, timeout)
|
||||
if !shouldUseLegacyOracleLOBFetch(params, majorVersion, ok) {
|
||||
return db, params, nil
|
||||
}
|
||||
|
||||
effectiveParams := withOracleLOBFetchPost(params)
|
||||
if effectiveParams == params {
|
||||
return db, params, nil
|
||||
}
|
||||
if err := db.Close(); err != nil {
|
||||
return nil, params, err
|
||||
}
|
||||
db, err = openAndPingDB(effectiveParams, timeout)
|
||||
if err != nil {
|
||||
return nil, params, err
|
||||
}
|
||||
return db, effectiveParams, nil
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func oracleServerMajorVersion(db *sql.DB, timeout time.Duration) (int, bool) {
|
||||
|
|
@ -854,6 +843,9 @@ func oracleServerMajorVersion(db *sql.DB, timeout time.Duration) (int, bool) {
|
|||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
if majorVersion, ok := oracleServerMajorVersionFromDBConn(ctx, db); ok {
|
||||
return majorVersion, true
|
||||
}
|
||||
for _, query := range oracleDatabaseVersionQueries {
|
||||
var version string
|
||||
err := db.QueryRowContext(ctx, query).Scan(&version)
|
||||
|
|
@ -866,6 +858,40 @@ func oracleServerMajorVersion(db *sql.DB, timeout time.Duration) (int, bool) {
|
|||
return 0, false
|
||||
}
|
||||
|
||||
func oracleServerMajorVersionFromDBConn(ctx context.Context, db *sql.DB) (int, bool) {
|
||||
conn, err := db.Conn(ctx)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
defer conn.Close()
|
||||
var majorVersion int
|
||||
var versionKnown bool
|
||||
if err := conn.Raw(func(driverConn any) error {
|
||||
majorVersion, versionKnown = oracleServerMajorVersionFromDriverConn(driverConn)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return majorVersion, versionKnown
|
||||
}
|
||||
|
||||
func oracleServerMajorVersionFromDriverConn(driverConn any) (int, bool) {
|
||||
conn, ok := driverConn.(*go_ora.Connection)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return parseOracleAuthVersionNumber(conn.SessionProperties["AUTH_VERSION_NO"])
|
||||
}
|
||||
|
||||
func parseOracleAuthVersionNumber(value string) (int, bool) {
|
||||
encoded, err := strconv.ParseUint(strings.TrimSpace(value), 10, 32)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
majorVersion := int(encoded >> 24)
|
||||
return majorVersion, majorVersion > 0
|
||||
}
|
||||
|
||||
func parseOracleMajorVersion(version string) (int, bool) {
|
||||
match := oracleVersionNumberRegexp.FindStringSubmatch(strings.TrimSpace(version))
|
||||
if len(match) != 2 {
|
||||
|
|
@ -879,6 +905,38 @@ func shouldUseLegacyOracleLOBFetch(params connectParams, majorVersion int, versi
|
|||
return versionKnown && majorVersion <= oracleLegacyLOBMaxMajorVersion && !hasOracleLOBFetchOption(params)
|
||||
}
|
||||
|
||||
func oracleMethodMayReadLOB(method string) bool {
|
||||
switch method {
|
||||
case "get_table_ddl", "execute_query", "execute_query_page", "start_table_read", "execute_transaction":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) ensureLegacyOracleLOBFetch() error {
|
||||
if !s.legacyLOBFetchDeferred {
|
||||
return nil
|
||||
}
|
||||
effectiveParams := withOracleLOBFetchPost(s.params)
|
||||
if effectiveParams == s.params {
|
||||
s.legacyLOBFetchDeferred = false
|
||||
return nil
|
||||
}
|
||||
db, err := openConfiguredSessionDB(effectiveParams, 15*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldDB := s.db
|
||||
s.db = db
|
||||
s.params = effectiveParams
|
||||
s.legacyLOBFetchDeferred = false
|
||||
if oldDB != nil {
|
||||
_ = oldDB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasOracleLOBFetchOption(params connectParams) bool {
|
||||
connectionString := strings.TrimSpace(params.ConnectionString)
|
||||
if strings.HasPrefix(strings.ToLower(connectionString), "oracle://") {
|
||||
|
|
@ -922,6 +980,7 @@ func withOracleLOBFetchPost(params connectParams) connectParams {
|
|||
|
||||
func (s *server) disconnect() error {
|
||||
s.closeAllQuerySessions()
|
||||
s.legacyLOBFetchDeferred = false
|
||||
if s.db == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
go_ora "github.com/sijms/go-ora/v2"
|
||||
"github.com/sijms/go-ora/v2/configurations"
|
||||
)
|
||||
|
||||
|
|
@ -919,6 +920,41 @@ func TestParseOracleMajorVersion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseOracleAuthVersionNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
value string
|
||||
major int
|
||||
ok bool
|
||||
}{
|
||||
{value: "169870336", major: 10, ok: true},
|
||||
{value: "186647040", major: 11, ok: true},
|
||||
{value: "301989888", major: 18, ok: true},
|
||||
{value: "", ok: false},
|
||||
{value: "not-a-version", ok: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.value, func(t *testing.T) {
|
||||
major, ok := parseOracleAuthVersionNumber(tt.value)
|
||||
if major != tt.major || ok != tt.ok {
|
||||
t.Fatalf("parseOracleAuthVersionNumber(%q) = (%d, %t), want (%d, %t)", tt.value, major, ok, tt.major, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracleServerMajorVersionUsesDriverSessionProperties(t *testing.T) {
|
||||
major, ok := oracleServerMajorVersionFromDriverConn(&go_ora.Connection{
|
||||
SessionProperties: map[string]string{"AUTH_VERSION_NO": "186647040"},
|
||||
})
|
||||
if !ok || major != 11 {
|
||||
t.Fatalf("oracleServerMajorVersionFromDriverConn() = (%d, %t), want (11, true)", major, ok)
|
||||
}
|
||||
if _, ok := oracleServerMajorVersionFromDriverConn(struct{}{}); ok {
|
||||
t.Fatal("non-Oracle connections should not expose a server version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracleServerMajorVersionUsesProductComponentVersion(t *testing.T) {
|
||||
db, scripted := openOracleViewSourceTestDB(t, []oracleViewSourceQueryStep{
|
||||
{
|
||||
|
|
@ -1029,11 +1065,8 @@ func TestShouldUseLegacyOracleLOBFetchOnlyForLegacyServers(t *testing.T) {
|
|||
if !shouldUseLegacyOracleLOBFetch(params, 10, true) {
|
||||
t.Fatal("Oracle 10g should use streamed LOB reads")
|
||||
}
|
||||
if !shouldUseLegacyOracleLOBFetch(params, 11, true) {
|
||||
t.Fatal("Oracle 11g should use streamed LOB reads")
|
||||
}
|
||||
if shouldUseLegacyOracleLOBFetch(params, 12, true) || shouldUseLegacyOracleLOBFetch(params, 19, true) {
|
||||
t.Fatal("modern Oracle versions should retain the driver's default LOB mode")
|
||||
if shouldUseLegacyOracleLOBFetch(params, 11, true) || shouldUseLegacyOracleLOBFetch(params, 19, true) {
|
||||
t.Fatal("Oracle 11g and newer should retain the driver's default LOB mode")
|
||||
}
|
||||
if shouldUseLegacyOracleLOBFetch(params, 0, false) {
|
||||
t.Fatal("unknown Oracle versions should retain the driver's default LOB mode")
|
||||
|
|
@ -1043,6 +1076,19 @@ func TestShouldUseLegacyOracleLOBFetchOnlyForLegacyServers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOracleMethodMayReadLOB(t *testing.T) {
|
||||
for _, method := range []string{"get_table_ddl", "execute_query", "execute_query_page", "start_table_read", "execute_transaction"} {
|
||||
if !oracleMethodMayReadLOB(method) {
|
||||
t.Fatalf("%s should enable deferred legacy LOB reads", method)
|
||||
}
|
||||
}
|
||||
for _, method := range []string{"list_schemas", "list_tables", "list_objects", "get_columns", "list_indexes", "list_triggers"} {
|
||||
if oracleMethodMayReadLOB(method) {
|
||||
t.Fatalf("%s should not reconnect while loading metadata", method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOracleGB18030ConverterRoundTrip(t *testing.T) {
|
||||
converter := oracleGB18030Converter{}
|
||||
input := "DBX \u4e2d\u6587 \U00020000"
|
||||
|
|
|
|||
Loading…
Reference in New Issue