Class: Tina4::Drivers::OdbcDriver
- Inherits:
-
Object
- Object
- Tina4::Drivers::OdbcDriver
- Defined in:
- lib/tina4/drivers/odbc_driver.rb
Instance Attribute Summary collapse
-
#connection ⇒ Object
readonly
Returns the value of attribute connection.
Instance Method Summary collapse
-
#apply_limit(sql, limit, offset = 0) ⇒ Object
Build paginated SQL.
- #begin_transaction ⇒ Object
- #close ⇒ Object
-
#columns(table_name) ⇒ Object
Return column metadata for a table via ODBC metadata.
- #commit ⇒ Object
-
#connect(connection_string, username: nil, password: nil) ⇒ Object
Connect to an ODBC data source.
- #connected? ⇒ Boolean
-
#execute(sql, params = []) ⇒ Object
Execute DDL or DML without returning rows.
-
#execute_query(sql, params = []) ⇒ Object
Execute a SELECT query and return rows as an array of symbol-keyed hashes.
-
#last_insert_id ⇒ Object
ODBC does not expose a universal last-insert-id API.
- #placeholder ⇒ Object
- #placeholders(count) ⇒ Object
- #rollback ⇒ Object
-
#tables ⇒ Object
List all user tables via ODBC metadata.
Instance Attribute Details
#connection ⇒ Object (readonly)
Returns the value of attribute connection.
6 7 8 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 6 def connection @connection end |
Instance Method Details
#apply_limit(sql, limit, offset = 0) ⇒ Object
Build paginated SQL. Tries OFFSET/FETCH NEXT (SQL Server, newer ODBC sources) first. Falls back to LIMIT/OFFSET for sources that support it (MySQL, PostgreSQL via ODBC). The caller (Database#fetch) already gates on whether LIMIT is already present.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 108 def apply_limit(sql, limit, offset = 0) offset ||= 0 if offset > 0 # SQL Server / ANSI syntax — requires ORDER BY; add a no-op if absent if sql.upcase.include?("ORDER BY") "#{sql} OFFSET #{offset} ROWS FETCH NEXT #{limit} ROWS ONLY" else # LIMIT/OFFSET fallback (MySQL, PostgreSQL via ODBC, SQLite via ODBC) "#{sql} LIMIT #{limit} OFFSET #{offset}" end else "#{sql} LIMIT #{limit}" end end |
#begin_transaction ⇒ Object
123 124 125 126 127 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 123 def begin_transaction return if @in_transaction @connection.autocommit = false @in_transaction = true end |
#close ⇒ Object
47 48 49 50 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 47 def close @connection&.disconnect @connection = nil end |
#columns(table_name) ⇒ Object
Return column metadata for a table via ODBC metadata.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 160 def columns(table_name) stmt = @connection.columns(table_name.to_s) result = [] while (row = stmt.fetch_hash) name = row["COLUMN_NAME"] || row[:COLUMN_NAME] type = row["TYPE_NAME"] || row[:TYPE_NAME] nullable_val = row["NULLABLE"] || row[:NULLABLE] default = row["COLUMN_DEF"] || row[:COLUMN_DEF] result << { name: name.to_s, type: type.to_s, nullable: nullable_val.to_i == 1, default: default, primary_key: false # ODBC metadata does not reliably expose PK flag here } end stmt.drop result rescue => e stmt&.drop rescue nil raise e end |
#commit ⇒ Object
129 130 131 132 133 134 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 129 def commit return unless @in_transaction @connection.commit @connection.autocommit = true @in_transaction = false end |
#connect(connection_string, username: nil, password: nil) ⇒ Object
Connect to an ODBC data source.
Connection string formats:
odbc:///DSN=MyDSN
odbc:///DSN=MyDSN;UID=user;PWD=pass
odbc:///DRIVER={SQL Server};SERVER=host;DATABASE=db
The leading scheme prefix “odbc:///” is stripped; the remainder is passed verbatim to ODBC::Database.new as a connection string. username: and password: are appended as UID/PWD if not already present in the connection string.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 19 def connect(connection_string, username: nil, password: nil) begin require "odbc" rescue LoadError raise LoadError, "The 'ruby-odbc' gem is required for ODBC connections. Install one of:\n" \ " bundle add ruby-odbc # if your project uses Bundler\n" \ " gem install ruby-odbc # bare driver" end dsn_string = connection_string.to_s .sub(/^odbc:\/\/\//, "") .sub(/^odbc:\/\//, "") .sub(/^odbc:/, "") # Append credentials if provided and not already embedded if username && !dsn_string.match?(/\bUID=/i) dsn_string = "#{dsn_string};UID=#{username}" end if password && !dsn_string.match?(/\bPWD=/i) dsn_string = "#{dsn_string};PWD=#{password}" end @connection = ODBC::Database.new(dsn_string) @in_transaction = false self end |
#connected? ⇒ Boolean
52 53 54 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 52 def connected? !@connection.nil? end |
#execute(sql, params = []) ⇒ Object
Execute DDL or DML without returning rows.
79 80 81 82 83 84 85 86 87 88 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 79 def execute(sql, params = []) if params && !params.empty? stmt = @connection.prepare(sql) stmt.execute(*params) stmt.drop else @connection.do(sql) end nil end |
#execute_query(sql, params = []) ⇒ Object
Execute a SELECT query and return rows as an array of symbol-keyed hashes.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 57 def execute_query(sql, params = []) stmt = if params && !params.empty? s = @connection.prepare(sql) s.execute(*params) s else @connection.run(sql) end columns = stmt.columns(true).map { |c| c.name.to_s.to_sym } rows = [] while (row = stmt.fetch) rows << columns.zip(row).to_h end stmt.drop rows rescue => e stmt&.drop rescue nil raise e end |
#last_insert_id ⇒ Object
ODBC does not expose a universal last-insert-id API. Drivers that support it can be queried via execute_query after insert.
92 93 94 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 92 def last_insert_id nil end |
#placeholder ⇒ Object
96 97 98 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 96 def placeholder "?" end |
#placeholders(count) ⇒ Object
100 101 102 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 100 def placeholders(count) (["?"] * count).join(", ") end |
#rollback ⇒ Object
136 137 138 139 140 141 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 136 def rollback return unless @in_transaction @connection.rollback @connection.autocommit = true @in_transaction = false end |
#tables ⇒ Object
List all user tables via ODBC metadata.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 144 def tables stmt = @connection.tables rows = [] while (row = stmt.fetch_hash) type = row["TABLE_TYPE"] || row[:TABLE_TYPE] || "" name = row["TABLE_NAME"] || row[:TABLE_NAME] rows << name.to_s if type.to_s.upcase == "TABLE" && name end stmt.drop rows rescue => e stmt&.drop rescue nil raise e end |