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.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 107 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
122 123 124 125 126 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 122 def begin_transaction return if @in_transaction @connection.autocommit = false @in_transaction = true end |
#close ⇒ Object
46 47 48 49 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 46 def close @connection&.disconnect @connection = nil end |
#columns(table_name) ⇒ Object
Return column metadata for a table via ODBC metadata.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 159 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
128 129 130 131 132 133 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 128 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 |
# 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: gem install ruby-odbc" 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
51 52 53 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 51 def connected? !@connection.nil? end |
#execute(sql, params = []) ⇒ Object
Execute DDL or DML without returning rows.
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 78 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.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 56 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.
91 92 93 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 91 def last_insert_id nil end |
#placeholder ⇒ Object
95 96 97 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 95 def placeholder "?" end |
#placeholders(count) ⇒ Object
99 100 101 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 99 def placeholders(count) (["?"] * count).join(", ") end |
#rollback ⇒ Object
135 136 137 138 139 140 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 135 def rollback return unless @in_transaction @connection.rollback @connection.autocommit = true @in_transaction = false end |
#tables ⇒ Object
List all user tables via ODBC metadata.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/tina4/drivers/odbc_driver.rb', line 143 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 |