Class: Tina4::Drivers::FirebirdDriver

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/drivers/firebird_driver.rb

Constant Summary collapse

DEAD_CONN_MARKERS =

Substring markers (lowercased) that identify a dead-socket Firebird error worth reconnecting for. Idle Firebird connections die silently behind NAT timeouts, server-side ConnectionIdleTimeout, or Docker network rotation; without this the next prepare crashes the request.

[
  "error writing data to the connection",
  "error reading data from the connection",
  "connection shutdown",
  "connection lost",
  "network error",
  "connection is not active",
  "broken pipe"
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#connectionObject (readonly)

Returns the value of attribute connection.



6
7
8
# File 'lib/tina4/drivers/firebird_driver.rb', line 6

def connection
  @connection
end

Class Method Details

.dead_connection?(error_or_message) ⇒ Boolean

Public so specs (and curious operators) can verify the matcher behaviour without poking private methods.

Returns:

  • (Boolean)


76
77
78
79
80
81
# File 'lib/tina4/drivers/firebird_driver.rb', line 76

def self.dead_connection?(error_or_message)
  msg = error_or_message.respond_to?(:message) ? error_or_message.message : error_or_message.to_s
  return false if msg.nil? || msg.empty?
  lower = msg.downcase
  DEAD_CONN_MARKERS.any? { |m| lower.include?(m) }
end

Instance Method Details

#apply_limit(sql, limit, offset = 0) ⇒ Object



95
96
97
# File 'lib/tina4/drivers/firebird_driver.rb', line 95

def apply_limit(sql, limit, offset = 0)
  "SELECT FIRST #{limit} SKIP #{offset} * FROM (#{sql})"
end

#begin_transactionObject



99
100
101
# File 'lib/tina4/drivers/firebird_driver.rb', line 99

def begin_transaction
  @transaction = @connection.transaction
end

#closeObject



49
50
51
# File 'lib/tina4/drivers/firebird_driver.rb', line 49

def close
  @connection&.close
end

#columns(table_name) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/tina4/drivers/firebird_driver.rb', line 117

def columns(table_name)
  sql = "SELECT RF.RDB\$FIELD_NAME, F.RDB\$FIELD_TYPE, RF.RDB\$NULL_FLAG, RF.RDB\$DEFAULT_SOURCE " \
        "FROM RDB\$RELATION_FIELDS RF " \
        "JOIN RDB\$FIELDS F ON RF.RDB\$FIELD_SOURCE = F.RDB\$FIELD_NAME " \
        "WHERE RF.RDB\$RELATION_NAME = ?"
  rows = execute_query(sql, [table_name.upcase])
  rows.map do |r|
    {
      name: (r["RDB\$FIELD_NAME"] || r["rdb\$field_name"] || "").strip,
      type: r["RDB\$FIELD_TYPE"] || r["rdb\$field_type"],
      nullable: (r["RDB\$NULL_FLAG"] || r["rdb\$null_flag"]).nil?,
      default: r["RDB\$DEFAULT_SOURCE"] || r["rdb\$default_source"],
      primary_key: false
    }
  end
end

#commitObject



103
104
105
# File 'lib/tina4/drivers/firebird_driver.rb', line 103

def commit
  @transaction&.commit
end

#connect(connection_string, username: nil, password: nil) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/tina4/drivers/firebird_driver.rb', line 22

def connect(connection_string, username: nil, password: nil)
  require "fb"
  require "uri"
  uri = URI.parse(connection_string)
  host = uri.host
  port = uri.port || 3050
  db_path = uri.path&.sub(/^\//, "")
  db_user = username || uri.user
  db_pass = password || uri.password

  database = if host
               "#{host}/#{port}:#{db_path}"
             else
               db_path || connection_string.sub(/^firebird:\/\//, "")
             end

  # Cache for transparent reconnect — never logged, lives only in
  # driver memory alongside the connection it owns.
  @connect_opts = { database: database }
  @connect_opts[:username] = db_user if db_user
  @connect_opts[:password] = db_pass if db_pass

  open_connection
rescue LoadError
  raise "Firebird driver requires the 'fb' gem. Install it with: gem install fb"
end

#execute(sql, params = []) ⇒ Object



64
65
66
67
68
69
70
71
72
# File 'lib/tina4/drivers/firebird_driver.rb', line 64

def execute(sql, params = [])
  with_reconnect do
    if params.empty?
      @connection.execute(sql)
    else
      @connection.execute(sql, *params)
    end
  end
end

#execute_query(sql, params = []) ⇒ Object



53
54
55
56
57
58
59
60
61
62
# File 'lib/tina4/drivers/firebird_driver.rb', line 53

def execute_query(sql, params = [])
  rows = with_reconnect do
    if params.empty?
      @connection.query(:hash, sql)
    else
      @connection.query(:hash, sql, *params)
    end
  end
  rows.map { |row| decode_blobs(stringify_keys(row)) }
end

#last_insert_idObject



83
84
85
# File 'lib/tina4/drivers/firebird_driver.rb', line 83

def last_insert_id
  nil
end

#placeholderObject



87
88
89
# File 'lib/tina4/drivers/firebird_driver.rb', line 87

def placeholder
  "?"
end

#placeholders(count) ⇒ Object



91
92
93
# File 'lib/tina4/drivers/firebird_driver.rb', line 91

def placeholders(count)
  (["?"] * count).join(", ")
end

#rollbackObject



107
108
109
# File 'lib/tina4/drivers/firebird_driver.rb', line 107

def rollback
  @transaction&.rollback
end

#tablesObject



111
112
113
114
115
# File 'lib/tina4/drivers/firebird_driver.rb', line 111

def tables
  sql = "SELECT RDB\$RELATION_NAME FROM RDB\$RELATIONS WHERE RDB\$SYSTEM_FLAG = 0 AND RDB\$VIEW_BLR IS NULL"
  rows = execute_query(sql)
  rows.map { |r| (r["RDB\$RELATION_NAME"] || r["rdb\$relation_name"] || "").strip }
end