Index: trunk/skirmish/config.mk.sample |
— | — | @@ -20,3 +20,7 @@ |
21 | 21 | # Build Oracle client ($ORACLE_HOME must be set) |
22 | 22 | BUILD_ORACLE = YES |
23 | 23 | # BUILD_ORACLE = NO |
| 24 | + |
| 25 | +# Build ODBC client (currently requires unixODBC) |
| 26 | +BUILD_ODBC = YES |
| 27 | +# BUILD_ODBC = NO |
Index: trunk/skirmish/odbc.cc |
— | — | @@ -0,0 +1,300 @@ |
| 2 | +#include <iostream> |
| 3 | +#include <boost/lexical_cast.hpp> |
| 4 | +#include <boost/format.hpp> |
| 5 | +#include <boost/algorithm/string/case_conv.hpp> |
| 6 | +#include <unistd.h> |
| 7 | +#include <sql.h> |
| 8 | +#include <sqlext.h> |
| 9 | + |
| 10 | +#include "odbc.h" |
| 11 | + |
| 12 | +namespace odbc { |
| 13 | + |
| 14 | +connection::connection(std::string const &desc) |
| 15 | + : env(0) |
| 16 | + , dbc(0) |
| 17 | +{ |
| 18 | + /* desc is "user[/password]@SID" */ |
| 19 | + std::string::size_type i; |
| 20 | + std::string userpart; |
| 21 | + std::string d; |
| 22 | + |
| 23 | + d = desc.substr(desc.find(':') + 1); |
| 24 | + |
| 25 | + if ((i = d.find('@')) != std::string::npos) { |
| 26 | + userpart = d.substr(0, i); |
| 27 | + sid = d.substr(i + 1); |
| 28 | + } else |
| 29 | + userpart = d; |
| 30 | + |
| 31 | + if ((i = userpart.find('/')) != std::string::npos) { |
| 32 | + user = userpart.substr(0, i); |
| 33 | + password = userpart.substr(i + 1); |
| 34 | + } else { |
| 35 | + user = userpart; |
| 36 | + char *p = getpass("Enter password: "); |
| 37 | + if (p) |
| 38 | + password = p; |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +connection::~connection() |
| 43 | +{ |
| 44 | + close(); |
| 45 | +} |
| 46 | + |
| 47 | +void |
| 48 | +connection::open(void) |
| 49 | +{ |
| 50 | + if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env))) |
| 51 | + throw db::error("cannot create ODBC environment"); |
| 52 | + |
| 53 | + if (!SQL_SUCCEEDED(SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0))) |
| 54 | + throw db::error(error(env, SQL_HANDLE_ENV)); |
| 55 | + |
| 56 | + if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc))) |
| 57 | + throw db::error(error(env, SQL_HANDLE_ENV)); |
| 58 | + |
| 59 | + std::string conn = "DSN=" + sid; |
| 60 | + if (!user.empty()) |
| 61 | + conn += ";UID=" + user; |
| 62 | + if (!password.empty()) |
| 63 | + conn += ";PWD=" + password; |
| 64 | + |
| 65 | + if (!SQL_SUCCEEDED(SQLDriverConnect(dbc, 0, (SQLCHAR *)conn.c_str(), conn.size(), 0, 0, 0, SQL_DRIVER_NOPROMPT))) |
| 66 | + throw db::error(error(dbc, SQL_HANDLE_DBC)); |
| 67 | +} |
| 68 | + |
| 69 | +void |
| 70 | +connection::close(void) |
| 71 | +{ |
| 72 | + if (dbc) { |
| 73 | + SQLDisconnect(dbc); |
| 74 | + SQLFreeHandle(SQL_HANDLE_DBC, dbc); |
| 75 | + dbc = 0; |
| 76 | + } |
| 77 | + if (env) { |
| 78 | + SQLFreeHandle(SQL_HANDLE_ENV, env); |
| 79 | + env = 0; |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +std::string |
| 84 | +connection::error(SQLHANDLE handle, int type) |
| 85 | +{ |
| 86 | + SQLCHAR state[7]; |
| 87 | + SQLCHAR text[1024]; |
| 88 | + SQLSMALLINT len; |
| 89 | + SQLINTEGER native; |
| 90 | + int i = 0; |
| 91 | + std::string ret; |
| 92 | + |
| 93 | + while (SQL_SUCCEEDED(SQLGetDiagRec(type, handle, ++i, state, &native, text, sizeof(text), &len))) { |
| 94 | + ret += str(boost::format("%s:%d") % state % text); |
| 95 | + } |
| 96 | + |
| 97 | + return ret; |
| 98 | +} |
| 99 | + |
| 100 | +db::resultptr |
| 101 | +connection::execute_sql(std::string const &sql) |
| 102 | +{ |
| 103 | + db::resultptr r = prepare_sql(sql); |
| 104 | + r->execute(); |
| 105 | + return r; |
| 106 | +} |
| 107 | + |
| 108 | +db::resultptr |
| 109 | +connection::prepare_sql(std::string const &sql) |
| 110 | +{ |
| 111 | + return db::resultptr(new result(this, sql)); |
| 112 | +} |
| 113 | + |
| 114 | +result::result(connection *conn, std::string const &q) |
| 115 | + : stmt(0) |
| 116 | + , conn(conn) |
| 117 | + , sql(q) |
| 118 | +{ |
| 119 | + if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_STMT, conn->dbc, &stmt))) |
| 120 | + throw db::error(conn->error(conn->dbc, SQL_HANDLE_DBC)); |
| 121 | +} |
| 122 | + |
| 123 | +void |
| 124 | +result::execute(void) |
| 125 | +{ |
| 126 | + if (!SQL_SUCCEEDED(SQLExecDirect(stmt, (SQLCHAR *)sql.c_str(), sql.size()))) |
| 127 | + throw db::error(conn->error(stmt, SQL_HANDLE_STMT)); |
| 128 | + |
| 129 | + SQLSMALLINT ncols; |
| 130 | + SQLNumResultCols(stmt, &ncols); |
| 131 | + if (ncols == 0) |
| 132 | + return; |
| 133 | + |
| 134 | + SQLCHAR name[128]; |
| 135 | + SQLSMALLINT namelen; |
| 136 | + SQLUINTEGER colsize; |
| 137 | + fields.resize(ncols); |
| 138 | + for (int i = 0; i < ncols; ++i) { |
| 139 | + if (!SQL_SUCCEEDED(SQLDescribeCol(stmt, i + 1, |
| 140 | + name, sizeof(name), &namelen, |
| 141 | + NULL, &colsize, NULL, NULL))) |
| 142 | + throw db::error(conn->error(stmt, SQL_HANDLE_STMT)); |
| 143 | + fields[i].name.assign(name, name + namelen); |
| 144 | + fields[i].size = colsize; |
| 145 | + fields[i].data.resize(colsize + 1); |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +void |
| 150 | +result::bind(std::string const &key, std::string const &value) |
| 151 | +{ |
| 152 | + throw db::error("prepared statements not supported for ODBC"); |
| 153 | +} |
| 154 | + |
| 155 | +result::~result() |
| 156 | +{ |
| 157 | + SQLFreeHandle(SQL_HANDLE_STMT, stmt); |
| 158 | +} |
| 159 | + |
| 160 | +bool |
| 161 | +result::empty(void) |
| 162 | +{ |
| 163 | + return fields.size() == 0; |
| 164 | +} |
| 165 | + |
| 166 | +int |
| 167 | +result::num_fields(void) |
| 168 | +{ |
| 169 | + return fields.size(); |
| 170 | +} |
| 171 | + |
| 172 | +int |
| 173 | +result::affected_rows(void) |
| 174 | +{ |
| 175 | + return 0; /* XXX */ |
| 176 | +} |
| 177 | + |
| 178 | +result_row * |
| 179 | +result::next_row(void) |
| 180 | +{ |
| 181 | + SQLRETURN r; |
| 182 | + r = SQLFetch(stmt); |
| 183 | + if (!SQL_SUCCEEDED(r)) { |
| 184 | + if (r == SQL_NO_DATA) |
| 185 | + return NULL; |
| 186 | + throw db::error(conn->error(stmt, SQL_HANDLE_STMT)); |
| 187 | + } |
| 188 | + |
| 189 | + for (int i = 0; i < fields.size(); ++i) { |
| 190 | + SQLINTEGER indicator; |
| 191 | + r = SQLGetData(stmt, i + 1, SQL_C_CHAR, |
| 192 | + &fields[i].data[0], fields[i].data.size(), |
| 193 | + &indicator); |
| 194 | + if (!SQL_SUCCEEDED(r)) |
| 195 | + throw db::error(conn->error(stmt, SQL_HANDLE_STMT)); |
| 196 | + if (indicator == SQL_NULL_DATA) { |
| 197 | + fields[i].data.resize(5); |
| 198 | + std::strcpy(&fields[i].data[0], "NULL"); |
| 199 | + } |
| 200 | + } |
| 201 | + return new result_row(this); |
| 202 | +} |
| 203 | + |
| 204 | +result_row::result_row(result *er) |
| 205 | + : er(er) |
| 206 | +{ |
| 207 | +} |
| 208 | + |
| 209 | +std::string |
| 210 | +result_row::string_value(int col) |
| 211 | +{ |
| 212 | + return std::string(&er->fields[col].data[0]); |
| 213 | + |
| 214 | +} |
| 215 | + |
| 216 | +std::string |
| 217 | +result::field_name(int col) |
| 218 | +{ |
| 219 | + return fields[col].name; |
| 220 | +} |
| 221 | + |
| 222 | +std::vector<db::table> |
| 223 | +connection::describe_tables(std::string const &schema) |
| 224 | +{ |
| 225 | + SQLHANDLE stmt; |
| 226 | + SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); |
| 227 | + SQLTables(stmt, NULL, 0, (SQLCHAR *)schema.c_str(), schema.size(), NULL, 0, NULL, 0); |
| 228 | + |
| 229 | + SQLRETURN r; |
| 230 | + std::vector<std::pair<std::string, std::string> > names; |
| 231 | + for (;;) { |
| 232 | + r = SQLFetch(stmt); |
| 233 | + if (!SQL_SUCCEEDED(r)) { |
| 234 | + if (r == SQL_NO_DATA) |
| 235 | + break; |
| 236 | + |
| 237 | + std::string e = error(stmt, SQL_HANDLE_STMT); |
| 238 | + SQLFreeHandle(SQL_HANDLE_STMT, stmt); |
| 239 | + throw db::error(e); |
| 240 | + } |
| 241 | + |
| 242 | + SQLINTEGER ind; |
| 243 | + char name[256]; |
| 244 | + char schema[256]; |
| 245 | + SQLGetData(stmt, 2, SQL_C_CHAR, schema, sizeof(schema), &ind); |
| 246 | + SQLGetData(stmt, 3, SQL_C_CHAR, name, sizeof(name), &ind); |
| 247 | + names.push_back(std::pair<std::string, std::string>( |
| 248 | + schema, name)); |
| 249 | + } |
| 250 | + SQLFreeHandle(SQL_HANDLE_STMT, stmt); |
| 251 | + |
| 252 | + std::vector<db::table> ret; |
| 253 | + for (int i = 0; i < names.size(); ++i) { |
| 254 | + ret.push_back(describe_table(names[i].first, names[i].second)); |
| 255 | + } |
| 256 | + |
| 257 | + return ret; |
| 258 | +} |
| 259 | + |
| 260 | +db::table |
| 261 | +connection::describe_table(std::string const &schema, std::string const &name) |
| 262 | +{ |
| 263 | + db::table ret; |
| 264 | + ret.name = name; |
| 265 | + ret.schema = schema; |
| 266 | + |
| 267 | + SQLHANDLE stmt; |
| 268 | + SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt); |
| 269 | + SQLColumns(stmt, NULL, 0, (SQLCHAR *)schema.c_str(), schema.size(), |
| 270 | + (SQLCHAR *)name.c_str(), name.size(), NULL, 0); |
| 271 | + SQLRETURN r; |
| 272 | + for (;;) { |
| 273 | + r = SQLFetch(stmt); |
| 274 | + if (!SQL_SUCCEEDED(r)) { |
| 275 | + if (r == SQL_NO_DATA) |
| 276 | + break; |
| 277 | + |
| 278 | + std::string e = error(stmt, SQL_HANDLE_STMT); |
| 279 | + SQLFreeHandle(SQL_HANDLE_STMT, stmt); |
| 280 | + throw db::error(e); |
| 281 | + } |
| 282 | + |
| 283 | + db::column c; |
| 284 | + SQLINTEGER ind; |
| 285 | + char name[256]; |
| 286 | + char type[256]; |
| 287 | + char nullable[5]; |
| 288 | + SQLGetData(stmt, 4, SQL_C_CHAR, name, sizeof(name), &ind); |
| 289 | + SQLGetData(stmt, 6, SQL_C_CHAR, type, sizeof(type), &ind); |
| 290 | + SQLGetData(stmt, 18, SQL_C_CHAR, nullable, sizeof(nullable), &ind); |
| 291 | + c.name = name; |
| 292 | + c.type = type; |
| 293 | + c.nullable = !strcmp(nullable, "YES"); |
| 294 | + ret.columns.push_back(c); |
| 295 | + } |
| 296 | + SQLFreeHandle(SQL_HANDLE_STMT, stmt); |
| 297 | + |
| 298 | + return ret; |
| 299 | +} |
| 300 | + |
| 301 | +} // namespace odbc |
Property changes on: trunk/skirmish/odbc.cc |
___________________________________________________________________ |
Added: svn:keywords |
1 | 302 | + Id Revision |
Index: trunk/skirmish/odbc.h |
— | — | @@ -0,0 +1,77 @@ |
| 2 | +#ifndef ODBC_H |
| 3 | +#define ODBC_H |
| 4 | + |
| 5 | +#include <string> |
| 6 | +#include <vector> |
| 7 | + |
| 8 | +#include <sql.h> |
| 9 | + |
| 10 | +#include "db.h" |
| 11 | + |
| 12 | +namespace odbc { |
| 13 | + |
| 14 | +struct result; |
| 15 | +struct connection; |
| 16 | + |
| 17 | +struct odbcfield { |
| 18 | + std::string name; |
| 19 | + SQLUINTEGER size; |
| 20 | + std::vector<char> data; |
| 21 | +}; |
| 22 | + |
| 23 | +struct result_row : db::result_row { |
| 24 | + result_row(result *er); |
| 25 | + |
| 26 | + std::string string_value(int col); |
| 27 | + |
| 28 | + result *er; |
| 29 | +}; |
| 30 | + |
| 31 | +struct result : db::result { |
| 32 | + result(connection *, std::string const &); |
| 33 | + ~result(); |
| 34 | + |
| 35 | + void bind(std::string const &, std::string const &); |
| 36 | + void execute(void); |
| 37 | + |
| 38 | + bool empty(void); |
| 39 | + int num_fields(void); |
| 40 | + int affected_rows(void); |
| 41 | + std::string field_name(int col); |
| 42 | + |
| 43 | + result_row *next_row(void); |
| 44 | + |
| 45 | + std::string sql; |
| 46 | + std::vector<odbcfield> fields; |
| 47 | + connection *conn; |
| 48 | + SQLHANDLE stmt; |
| 49 | +}; |
| 50 | + |
| 51 | +struct connection : db::connection { |
| 52 | + connection(std::string const &desc); |
| 53 | + ~connection(); |
| 54 | + |
| 55 | + void open(void); |
| 56 | + void close(void); |
| 57 | + |
| 58 | + std::string error(SQLHANDLE, int); |
| 59 | + |
| 60 | + db::resultptr execute_sql(std::string const &); |
| 61 | + db::resultptr prepare_sql(std::string const &); |
| 62 | + |
| 63 | + std::vector<db::table> describe_tables(std::string const &); |
| 64 | + db::table describe_table(std::string const &, std::string const &); |
| 65 | + |
| 66 | +private: |
| 67 | + friend class result; |
| 68 | + |
| 69 | + SQLHENV env; |
| 70 | + SQLHDBC dbc; |
| 71 | + SQLRETURN err; |
| 72 | + |
| 73 | + std::string sid, user, password; |
| 74 | +}; |
| 75 | + |
| 76 | +} // namespace oracle |
| 77 | + |
| 78 | +#endif |
Property changes on: trunk/skirmish/odbc.h |
___________________________________________________________________ |
Added: svn:keywords |
1 | 79 | + Id Revision |
Index: trunk/skirmish/rules.mk |
— | — | @@ -20,6 +20,13 @@ |
21 | 21 | DB_SRCS += pgsql.cc |
22 | 22 | endif |
23 | 23 | |
| 24 | +ifeq ($(BUILD_ODBC),YES) |
| 25 | +INCLUDES += |
| 26 | +CPPFLAGS += $(shell odbc_config --cflags) -DSKIRMISH_ODBC |
| 27 | +LIBS += $(shell odbc_config --libs) |
| 28 | +DB_SRCS += odbc.cc |
| 29 | +endif |
| 30 | + |
24 | 31 | .cc.o: |
25 | 32 | $(CXX) $(CPPFLAGS) $(INCLUDES) $(CXXFLAGS) -c $< |
26 | 33 | |
Index: trunk/skirmish/db.cc |
— | — | @@ -18,6 +18,10 @@ |
19 | 19 | # include "ora.h" |
20 | 20 | #endif |
21 | 21 | |
| 22 | +#ifdef SKIRMISH_ODBC |
| 23 | +# include "odbc.h" |
| 24 | +#endif |
| 25 | + |
22 | 26 | namespace db { |
23 | 27 | |
24 | 28 | connection::connection() |
— | — | @@ -51,9 +55,18 @@ |
52 | 56 | typedef std::map<std::string, boost::function<connectionptr (std::string const &)> > schemelist_t; |
53 | 57 | |
54 | 58 | static schemelist_t schemes = boost::assign::map_list_of |
| 59 | +#ifdef SKIRMISH_MYSQL |
55 | 60 | ("mysql", construct<mysql::connection>) |
| 61 | +#endif |
| 62 | +#ifdef SKIRMISH_POSTGRES |
56 | 63 | ("postgres", construct<postgres::connection>) |
| 64 | +#endif |
| 65 | +#ifdef SKIRMISH_ORACLE |
57 | 66 | ("oracle", construct<oracle::connection>) |
| 67 | +#endif |
| 68 | +#ifdef SKIRMISH_ODBC |
| 69 | + ("odbc", construct<odbc::connection>) |
| 70 | +#endif |
58 | 71 | ; |
59 | 72 | |
60 | 73 | schemelist_t::iterator it = schemes.find(type); |
Index: trunk/skirmish/db.h |
— | — | @@ -85,8 +85,6 @@ |
86 | 86 | virtual void open(void) = 0; |
87 | 87 | virtual void close(void) = 0; |
88 | 88 | |
89 | | - virtual std::string error(void) = 0; |
90 | | - |
91 | 89 | virtual ~connection(); |
92 | 90 | |
93 | 91 | virtual resultptr execute_sql(std::string const &) = 0; |