pike.git / src / modules / Odbc / odbc.cmod

version» Context lines:

pike.git/src/modules/Odbc/odbc.cmod:1: + /* -*- c -*- + || This file is part of Pike. For copyright information see COPYRIGHT. + || Pike is distributed under GPL, LGPL and MPL. See the file COPYING + || for more information. + */    -  + /* +  * Pike interface to ODBC compliant databases. +  * +  * Henrik Grubbström +  */ +  + /* +  * Includes +  */ +  + #include "config.h" + #include "module.h" +  + #include "interpret.h" + #include "threads.h" + #include "pike_compiler.h" + #include "program.h" + #include "module_support.h" + #include "bignum.h" + #include "builtin_functions.h" +  + #include "precompiled_odbc.h" +  + #ifdef HAVE_ODBC +  + /* #define ODBC_DEBUG */ +  + /* +  * Globals +  */ +  + struct program *odbc_program = NULL; +  + SQLHENV odbc_henv = SQL_NULL_HENV; +  + #ifdef PIKE_THREADS + /* See f_connect_lock doc below. */ + static int enable_connect_lock = 1; + static PIKE_MUTEX_T connect_mutex STATIC_MUTEX_INIT; + #endif +  + #define DEFAULT_CMOD_STORAGE +  + /* +  * Functions +  */ +  + static unsigned int odbc_driver_flags(HDBC odbc_conn) + { +  unsigned int res = 0; +  +  char buf[128]; +  SQLSMALLINT len; +  SQLHSTMT hstmt = SQL_NULL_HSTMT; +  RETCODE code; +  SWORD num_fields = 0; +  SQLLEN num_rows = 0; +  const char stmt[] = "SELECT 'bar' AS foo"; +  const p_wchar1 stmt1[] = { 'S', 'E', 'L', 'E', 'C', 'T', ' ', '\'', 'b', +  'a', 'r', '\'', ' ', 'A', 'S', ' ', 'f', 'o', +  'o', 0, }; +  const p_wchar2 stmt2[] = { 'S', 'E', 'L', 'E', 'C', 'T', ' ', '\'', 'b', +  'a', 'r', '\'', ' ', 'A', 'S', ' ', 'f', 'o', +  'o', 0, }; +  SWORD name_len = 0; +  SWORD sql_type; +  SQLULEN precision; +  SWORD scale; +  SWORD nullable = 0; +  SQLLEN field_len = 0; +  unsigned int wdata_shift = 0; +  unsigned int wlabel_shift = 0; +  unsigned int wquery_shift = 0; +  +  /* Default to the shifts from the header files. */ +  switch(sizeof(SQLWCHAR)) { +  default: +  case 1: +  break; +  case 2: +  wdata_shift = wlabel_shift = wquery_shift = 1; +  break; +  case 4: +  wdata_shift = wlabel_shift = wquery_shift = 2; +  break; +  } +  +  if(SQLGetInfo(odbc_conn, SQL_DRIVER_NAME, buf, sizeof(buf), &len) == SQL_SUCCESS) { +  int is_tds = 0; +  push_text(buf); +  f_lower_case(1); +  is_tds = strstr(Pike_sp[-1].u.string->str, "libtdsodbc") ? 1 : 0; +  pop_stack(); +  +  if(is_tds) { +  if(SQLGetInfo(odbc_conn, SQL_DRIVER_VER, buf, sizeof(buf), &len) == SQL_SUCCESS) { +  double version = atof(buf); + #ifdef ODBC_DEBUG +  fprintf(stderr, "ODBC: detected freetds version: %f\n", version); + #endif +  if(version < 0.8 ) +  res |= PIKE_ODBC_OLD_TDS_KLUDGE; +  } +  } +  } +  +  code = SQLAllocStmt(odbc_conn, &hstmt); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* Out of memory? */ +  /* fprintf(stderr, "AllocStmt: code: %ld\n", (long)code); */ +  } else { +  do { +  void *b = buf; +  /* NB: The above assignment is used to reduce the number of warnings +  * from gcc. +  */ +  +  /* +  * First determine the width of wide queries. +  */ +  do { +  code = SQLExecDirectW(hstmt, (void *)stmt1, (SQLINTEGER)strlen(stmt)); +  if ((code == SQL_SUCCESS) || (code == SQL_SUCCESS_WITH_INFO)) { +  wquery_shift = 1; +  break; +  } +  /* fprintf(stderr, "WQuery [1]: code: %d\n", code); */ +  code = SQLExecDirectW(hstmt, (void *)stmt2, (SQLINTEGER)strlen(stmt)); +  if ((code == SQL_SUCCESS) || (code == SQL_SUCCESS_WITH_INFO)) { +  wquery_shift = 2; +  break; +  } +  /* fprintf(stderr, "WQuery [2]: code: %d\n", code); */ +  wquery_shift = 0; +  code = SQLExecDirect(hstmt, (void *)stmt, (SQLINTEGER)strlen(stmt)); +  if ((code == SQL_SUCCESS) || (code == SQL_SUCCESS_WITH_INFO)) { +  wquery_shift = 0; +  break; +  } +  } while (0); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* fprintf(stderr, "Exec: code: %ld\n", (long)code); */ +  /* Syntax in test query not supported by driver. +  * Assume that the defaults are correct. +  */ +  break; +  } +  +  /* +  * Then determine the width of lables and data. +  */ +  code = SQLNumResultCols(hstmt, &num_fields); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* fprintf(stderr, "NumResult: code: %ld\n", (long)code); */ +  break; +  } +  if (num_fields != 1) { +  /* fprintf(stderr, "num_fields: %ld\n", (long)num_fields); */ +  break; +  } +  code = SQLRowCount(hstmt, &num_rows); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* fprintf(stderr, "RowCount: code: %ld\n", (long)code); */ +  break; +  } +  if ((num_rows != 1) && (num_rows > 0)) { +  /* fprintf(stderr, "num_rows: %ld\n", (long)num_rows); */ +  break; +  } +  code = SQLDescribeColW(hstmt, 1, b, +  (SQLSMALLINT)(sizeof(buf)/sizeof(p_wchar2)), +  &name_len, &sql_type, &precision, +  &scale, &nullable); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* fprintf(stderr, "DescribeCol: code: %ld\n", (long)code); */ +  break; +  } +  if (name_len != 3) { +  /* fprintf(stderr, "name_len: %ld\n", (long)name_len); */ +  break; +  } +  /* NB: Expected buffer content: "foo"L in some character width. */ +  /* NB: The query shift is usually the same as the label shift. */ +  if (((p_wchar2 *)b)[0] == 'f') { +  wquery_shift = wlabel_shift = 2; +  } else if (((p_wchar1 *)b)[0] == 'f') { +  wquery_shift = wlabel_shift = 1; +  } else if (((char *)b)[0] == 'f') { +  wquery_shift = wlabel_shift = 0; +  } +  code = SQLFetch(hstmt); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* fprintf(stderr, "Fetch: code: %ld\n", (long)code); */ +  break; +  } +  code = SQLGetData(hstmt, 1, SQL_C_WCHAR, buf, sizeof(buf), &field_len); +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  /* fprintf(stderr, "GetData: code: %ld\n", (long)code); */ +  break; +  } +  /* NB: Expected field content: "bar"L in some character width. */ +  /* fprintf(stderr, "field_len: %ld\n", (long)field_len); */ +  switch(field_len) { +  case 3*4: +  wdata_shift = 2; +  break; +  case 3*2: +  wdata_shift = 1; +  break; +  case 3: +  wdata_shift = 0; +  break; +  default: +  /* fprintf(stderr, "SQLGetData: Unexpected field_len: %ld\n", */ +  /* (long)field_len); */ +  break; +  } +  } while(0); +  +  SQLFreeStmt(hstmt, SQL_DROP); +  } + #ifdef ODBC_DEBUG +  fprintf(stderr, "ODBC: Wide shifts: data: %d, label: %d, query: %d\n", +  wdata_shift, wlabel_shift, wquery_shift); + #endif +  res |= wdata_shift<<PIKE_ODBC_WDATA_SHIFT_SHIFT; +  res |= wlabel_shift<<PIKE_ODBC_WLABEL_SHIFT_SHIFT; +  res |= wquery_shift<<PIKE_ODBC_WQUERY_SHIFT_SHIFT; +  return res; + } +  + /*! @module Odbc +  *! +  *! Low-level interface to Open DataBase Connectivity SQL-drivers. +  *! +  *! @note +  *! You typically don't want to access this module directly, but +  *! instead use @[Sql.Sql()] with an @expr{"odbc://"@} or +  *! @expr{"dsn://"@} URL. +  *! +  *! @seealso +  *! @[Sql.Sql()] +  */ +  + /*! @class odbc +  *! +  *! Low-level connection to an ODBC or DSN database. +  *! +  *! @note +  *! You typically don't want to access this module directly, but +  *! instead use the @[Sql.odbc] or @[Sql.dsn] created by @[Sql.Sql()]. +  *! +  *! @seealso +  *! @[Sql.odbc], @[Sql.dsn] +  */ +  + DECLARATIONS; +  + PIKECLASS odbc + { +  CVAR SQLHDBC hdbc; +  CVAR SQLLEN affected_rows; +  CVAR unsigned int flags; +  CVAR struct pike_string *last_error; +  +  DECLARE_STORAGE; +  +  /*! @decl inherit __builtin.Sql.Connection +  */ +  INHERIT "__builtin.Sql.Connection"; +  + /* +  * Helper functions +  */ +  +  SQLHDBC pike_odbc_get_hdbc(struct Odbc_odbc_struct *odbc) +  { +  return odbc->hdbc; +  } +  void pike_odbc_set_affected_rows(struct Odbc_odbc_struct *odbc, SQLLEN rows) +  { +  odbc->affected_rows = rows; +  } +  unsigned int pike_odbc_get_flags(struct Odbc_odbc_struct *odbc) +  { +  return odbc->flags; +  } +  + struct pike_string *make_shared_binary_sqlwchar(SQLWCHAR *str, +  size_t len) + { +  int shift = 1; +  if (sizeof(SQLWCHAR) == 4) shift = 2; +  return make_shared_binary_pcharp(MKPCHARP(str, shift), len); + } +  + void push_sqlwchar(SQLWCHAR *str, size_t len) + { +  push_string(make_shared_binary_sqlwchar(str, len)); + } +  + void odbc_error(const char *fun, const char *msg, +  struct Odbc_odbc_struct *odbc, SQLHSTMT hstmt, +  RETCODE code, void (*clean)(void*), void *clean_arg); +  + static inline void odbc_check_error(const char *fun, const char *msg, +  RETCODE code, +  void (*clean)(void *), void *clean_arg) + { +  if ((code != SQL_SUCCESS) && (code != SQL_SUCCESS_WITH_INFO)) { +  odbc_error(fun, msg, PIKE_ODBC, SQL_NULL_HSTMT, code, clean, clean_arg); +  } + } +  + void odbc_error(const char *fun, const char *msg, +  struct Odbc_odbc_struct *odbc, SQLHSTMT hstmt, +  RETCODE code, void (*clean)(void *), void *clean_arg) + { +  RETCODE _code; +  SQLWCHAR errcode[256]; +  SQLWCHAR errmsg[SQL_MAX_MESSAGE_LENGTH]; +  SWORD errmsg_len = 0; +  SQLINTEGER native_error; +  HDBC hdbc = odbc?odbc->hdbc:SQL_NULL_HDBC; +  +  ODBC_ALLOW(); +  _code = SQLErrorW(odbc_henv, hdbc, hstmt, errcode, &native_error, +  errmsg, (SQL_MAX_MESSAGE_LENGTH-1), &errmsg_len); +  errmsg[errmsg_len] = '\0'; +  ODBC_DISALLOW(); +  +  if (odbc) { +  if (odbc->last_error) { +  free_string(odbc->last_error); +  } +  odbc->last_error = make_shared_binary_sqlwchar(errmsg, errmsg_len); +  } +  +  if (clean) { +  clean(clean_arg); +  } +  switch(_code) { +  case SQL_SUCCESS: +  case SQL_SUCCESS_WITH_INFO: +  Pike_error("%s(): %s:\n" +  "%d:%ls:%ls\n", +  fun, msg, code, errcode, errmsg); +  break; +  case SQL_ERROR: +  Pike_error("%s(): %s:\n" +  "SQLError failed (%d:SQL_ERROR)\n", +  fun, msg, code); +  break; +  case SQL_NO_DATA_FOUND: +  Pike_error("%s(): %s:\n" +  "SQLError failed (%d:SQL_NO_DATA_FOUND)\n", +  fun, msg, code); +  break; +  case SQL_INVALID_HANDLE: +  Pike_error("%s(): %s:\n" +  "SQLError failed (%d:SQL_INVALID_HANDLE)\n", +  fun, msg, code); +  break; +  default: +  Pike_error("%s(): %s:\n" +  "SQLError failed (%d:%d)\n", +  fun, msg, code, _code); +  break; +  } + } +  + /* +  * Clean-up functions +  */ +  + static void clean_last_error(void) + { +  if (PIKE_ODBC->last_error) { +  free_string(PIKE_ODBC->last_error); +  PIKE_ODBC->last_error = NULL; +  } + } +  + /* +  * Glue functions +  */ +  + INIT + { +  HDBC hdbc = SQL_NULL_HDBC; +  RETCODE code; +  +  PIKE_ODBC->affected_rows = 0; +  PIKE_ODBC->flags = 0; +  PIKE_ODBC->last_error = NULL; +  +  ODBC_ALLOW(); +  code = SQLAllocConnect(odbc_henv, &hdbc); +  ODBC_DISALLOW(); +  PIKE_ODBC->hdbc = hdbc; +  +  odbc_check_error("init_odbc_struct", "ODBC initialization failed", +  code, NULL, NULL); + } +  + EXIT + { +  SQLHDBC hdbc = PIKE_ODBC->hdbc; +  +  if (hdbc != SQL_NULL_HDBC) { +  unsigned int flags = PIKE_ODBC->flags; +  RETCODE code; +  const char *err_msg = NULL; +  +  PIKE_ODBC->flags &= ~PIKE_ODBC_CONNECTED; +  ODBC_ALLOW(); +  if (flags & PIKE_ODBC_CONNECTED) { +  code = SQLDisconnect(hdbc); +  if (code != SQL_SUCCESS && code != SQL_SUCCESS_WITH_INFO) +  err_msg = "Disconnecting HDBC"; +  } +  if (!err_msg) { +  code = SQLFreeConnect(hdbc); +  if (code != SQL_SUCCESS && code != SQL_SUCCESS_WITH_INFO) +  err_msg = "Freeing HDBC"; +  hdbc = SQL_NULL_HDBC; +  } +  ODBC_DISALLOW(); +  PIKE_ODBC->hdbc = hdbc; +  +  if (err_msg) +  odbc_check_error("odbc_error", err_msg, code, NULL, NULL); +  } +  +  clean_last_error(); + } +  + EXTRA + { +  init_odbc_res_programs(); + } +  + /* +  * Pike functions +  */ +  + PIKEFUN string error(void|int clear) + { +  int clearit = clear && clear->u.integer; +  struct Odbc_odbc_struct *odbc = PIKE_ODBC; +  +  pop_n_elems(args); +  +  if (odbc->last_error) { +  ref_push_string(odbc->last_error); +  if (clearit) { +  free_string(odbc->last_error); +  odbc->last_error = 0; +  } +  } else { +  push_int(0); +  } + } +  + PIKEFUN void create(string|void server, string|void database, +  string|void user, string|void pwd, +  mapping|int|void options) +  flags ID_PROTECTED; + { +  HDBC hdbc = PIKE_ODBC->hdbc; +  RETCODE code; +  + #define NO_WIDE_STRING(s) do { \ +  if ((s)->size_shift) \ +  Pike_error("Bad argument. Must be 8-bit string.\n"); \ +  } while(0) +  +  if (pwd) { +  NO_WIDE_STRING(pwd); +  add_ref(pwd); +  pop_n_elems(args-3); +  /* NB: Potential but very unlikely leak of pwd here +  * if "CENSORED" fails to allocate. +  */ +  push_static_text("CENSORED"); +  push_string(pwd); /* Let the stack hold the reference. */ +  args = 5; +  } +  if(user) NO_WIDE_STRING(user); +  if(database) NO_WIDE_STRING(database); +  if(server) NO_WIDE_STRING(server); +  + #undef GET_ARG +  +  /* +  * NOTE: +  * +  * If no database has been specified, use the server argument. +  * If neither has been specified, connect to the database named "default". +  */ +  +  if (!database || !database->len) { +  if (!server || !server->len) { +  push_static_text("default"); +  database = Pike_sp[-1].u.string; +  } else { +  database = server; +  } +  } +  if (!user) +  add_ref(user = empty_pike_string); +  if (!pwd) +  add_ref(pwd = empty_pike_string); +  if (PIKE_ODBC->flags & PIKE_ODBC_CONNECTED) { +  PIKE_ODBC->flags &= ~PIKE_ODBC_CONNECTED; +  ODBC_ALLOW(); +  code = SQLDisconnect(hdbc); +  ODBC_DISALLOW(); +  /* Disconnect old hdbc */ +  odbc_check_error("odbc->create", "Disconnecting HDBC", code, NULL, NULL); +  } +  +  /* FIXME: Support wide strings. */ +  +  ODBC_ALLOW(); + #ifdef PIKE_THREADS +  { +  int lock_enabled = enable_connect_lock; +  if (lock_enabled) mt_lock (&connect_mutex); + #endif +  code = SQLConnect(hdbc, (unsigned char *)database->str, +  (SQLSMALLINT)database->len, +  (unsigned char *)user->str, +  (SQLSMALLINT)user->len, +  (unsigned char *)pwd->str, +  (SQLSMALLINT)pwd->len); + #ifdef PIKE_THREADS +  if (lock_enabled) mt_unlock (&connect_mutex); +  } + #endif +  ODBC_DISALLOW(); +  odbc_check_error("odbc->create", "Connect failed", code, NULL, NULL); +  PIKE_ODBC->flags |= PIKE_ODBC_CONNECTED | odbc_driver_flags(hdbc); + } +  + PIKEFUN void create_dsn(string connectstring) + { +  SQLCHAR outconnectionstring[1024]; /* Smallest allowed buffer = 1024 */ +  SQLSMALLINT stringlength2; +  +  if (!connectstring->len) { +  Pike_error("odbc->create_dsn connection string empty.\n"); +  } +  if (PIKE_ODBC->flags & PIKE_ODBC_CONNECTED) { +  PIKE_ODBC->flags &= ~PIKE_ODBC_CONNECTED; +  /* Disconnect old hdbc */ +  odbc_check_error("odbc->create_dsn", "Disconnecting HDBC", +  SQLDisconnect(PIKE_ODBC->hdbc), NULL, NULL); +  } + /* Microsoft ODBC >= 1.0 +  SQLRETURN SQLDriverConnect( +  SQLHDBC ConnectionHandle, +  SQLHWND WindowHandle, +  SQLCHAR * InConnectionString, +  SQLSMALLINT StringLength1, +  SQLCHAR * OutConnectionString, +  SQLSMALLINT BufferLength, +  SQLSMALLINT * StringLength2Ptr, +  SQLUSMALLINT DriverCompletion); + */ +  odbc_check_error("odbc->create_dsn", "Connect failed", +  SQLDriverConnect(PIKE_ODBC->hdbc, +  NULL, +  (unsigned char *)connectstring->str, +  (SQLSMALLINT)connectstring->len, +  outconnectionstring, +  (SQLSMALLINT)1024, +  &stringlength2, +  SQL_DRIVER_NOPROMPT), +  NULL, NULL); +  PIKE_ODBC->flags |= PIKE_ODBC_CONNECTED; + } +  +  + PIKEFUN int affected_rows() + { +  push_int64(PIKE_ODBC->affected_rows); + } +  + PIKEFUN void select_db(string db) + { +  /**********************************************/ + } +  + /* Needed since free_string() can be a macro */ + static void odbc_free_string(struct pike_string *s) + { +  if (s) { +  free_string(s); +  } + } +  + PIKEFUN object big_query(string q) +  flags ID_VARIANT; + { +  ONERROR ebuf; + #ifdef PIKE_DEBUG +  struct svalue *save_sp = Pike_sp + 1 - args; + #endif /* PIKE_DEBUG */ +  +  add_ref(q); +  SET_ONERROR(ebuf, odbc_free_string, q); +  +  pop_n_elems(args); +  +  clean_last_error(); +  +  /* Allocate the statement (Result) object */ +  apply_current(Odbc_Result_program_fun_num, 0); +  +  if (TYPEOF(Pike_sp[-1]) != T_OBJECT) { +  Pike_error("big_query(): Unexpected return value from Result().\n"); +  } +  +  UNSET_ONERROR(ebuf); +  +  /* Potential return value is now in place */ +  +  PIKE_ODBC->affected_rows = 0; +  +  /* Do the actual query */ +  push_string(q); +  apply(Pike_sp[-2].u.object, "execute", 1); +  +  if (TYPEOF(Pike_sp[-1]) != T_INT) { +  Pike_error("big_query(): Unexpected return value from execute().\n"); +  } +  +  if (!Pike_sp[-1].u.integer) { +  pop_n_elems(2); /* Zap the result object too */ +  + #ifdef ENABLE_IMPLICIT_COMMIT +  /* This breaks with Free TDS. */ +  { +  HDBC hdbc = PIKE_ODBC->hdbc; +  RETCODE code; +  ODBC_ALLOW(); +  code = SQLTransact(odbc_henv, hdbc, SQL_COMMIT); +  ODBC_DISALLOW(); +  odbc_check_error("odbc->big_query", "Couldn't commit query", +  code, NULL, NULL); +  } + #endif /* ENABLE_IMPLICIT_COMMIT */ +  +  push_int(0); +  } else { +  pop_stack(); /* Keep the result object */ +  } + #ifdef PIKE_DEBUG +  if (Pike_sp != save_sp) { +  Pike_fatal("Stack error in odbc->big_query().\n"); +  } + #endif /* PIKE_DEBUG */ + } +  + PIKEFUN object big_typed_query(string q) +  flags ID_VARIANT; + { +  ONERROR ebuf; + #ifdef PIKE_DEBUG +  struct svalue *save_sp = Pike_sp + 1 - args; + #endif /* PIKE_DEBUG */ +  +  add_ref(q); +  SET_ONERROR(ebuf, odbc_free_string, q); +  +  pop_n_elems(args); +  +  clean_last_error(); +  +  /* Allocate the statement (result) object */ +  apply_current(Odbc_TypedResult_program_fun_num, 0); +  +  if (TYPEOF(Pike_sp[-1]) != T_OBJECT) { +  Pike_error("big_typed_query(): Unexpected return value from " +  "TypedResult().\n"); +  } +  +  UNSET_ONERROR(ebuf); +  +  /* Potential return value is now in place */ +  +  PIKE_ODBC->affected_rows = 0; +  +  /* Do the actual query */ +  push_string(q); +  apply(Pike_sp[-2].u.object, "execute", 1); +  +  if (TYPEOF(Pike_sp[-1]) != T_INT) { +  Pike_error("big_typed_query(): Unexpected return value from execute().\n"); +  } +  +  if (!Pike_sp[-1].u.integer) { +  pop_n_elems(2); /* Zap the result object too */ +  + #ifdef ENABLE_IMPLICIT_COMMIT +  /* This breaks with Free TDS. */ +  { +  HDBC hdbc = PIKE_ODBC->hdbc; +  RETCODE code; +  ODBC_ALLOW(); +  code = SQLTransact(odbc_henv, hdbc, SQL_COMMIT); +  ODBC_DISALLOW(); +  odbc_check_error("odbc->big_query", "Couldn't commit query", +  code, NULL, NULL); +  } + #endif /* ENABLE_IMPLICIT_COMMIT */ +  +  push_int(0); +  } else { +  pop_stack(); /* Keep the result object */ +  } + #ifdef PIKE_DEBUG +  if (Pike_sp != save_sp) { +  Pike_fatal("Stack error in odbc->big_typed_query().\n"); +  } + #endif /* PIKE_DEBUG */ + } +  + PIKEFUN object list_tables(string|void pattern) + { + #ifdef PIKE_DEBUG +  struct svalue *save_sp = Pike_sp + 1 - args; + #endif /* PIKE_DEBUG */ +  ONERROR ebuf; +  +  if (pattern) { +  if (pattern->size_shift) { +  Pike_error("odbc->list_tables(): " +  "Bad argument 1. Expected 8-bit string.\n"); +  } +  add_ref(pattern); +  } +  +  SET_ONERROR(ebuf, odbc_free_string, pattern); +  +  pop_n_elems(args); +  +  clean_last_error(); +  +  /* Allocate the statement (result) object */ +  apply_current(Odbc_Result_program_fun_num, 0); +  +  UNSET_ONERROR(ebuf); +  +  /* Potential return value is now in place */ +  +  PIKE_ODBC->affected_rows = 0; +  +  /* Do the actual query */ +  if (pattern) { +  push_string(pattern); +  apply(Pike_sp[-1].u.object, "list_tables", 1); +  } else { +  apply(Pike_sp[-1].u.object, "list_tables", 0); +  } +  +  if (TYPEOF(Pike_sp[-1]) != T_INT) { +  Pike_error("list_tables(): Unexpected return value from " +  "Result()->list_tables().\n"); +  } +  +  if (!Pike_sp[-1].u.integer) { +  pop_n_elems(2); /* Zap the result object too */ +  +  push_int(0); +  } else { +  pop_stack(); /* Keep the result object */ +  } + #ifdef PIKE_DEBUG +  if (Pike_sp != save_sp) { +  Pike_fatal("Stack error in odbc->list_tables().\n"); +  } + #endif /* PIKE_DEBUG */ + } +  + PIKEFUN void create_db(string db) + { +  /**************************************************/ + } +  + PIKEFUN void drop_db(string db) + { +  /**************************************************/ + } +  + PIKEFUN void shutdown() + { +  /**************************************************/ + } +  + PIKEFUN void reload() + { +  /**************************************************/ + } +  + } + /*! @endclass +  */ +  + /*! @decl array(string) list_dbs() +  *! +  *! List the configured ODBC database sources. +  */ + PIKEFUN array(string) list_dbs() + { +  static SQLWCHAR buf[SQL_MAX_DSN_LENGTH+1]; +  static SQLWCHAR descr[256]; +  SQLSMALLINT buf_len = 0; +  SQLSMALLINT descr_len = 0; +  int cnt = 0; +  RETCODE ret; +  +  ODBC_ALLOW(); +  ret = SQLDataSourcesW(odbc_henv, SQL_FETCH_FIRST, +  buf, SQL_MAX_DSN_LENGTH, &buf_len, +  descr, 255, &descr_len); +  ODBC_DISALLOW(); +  +  while ((ret == SQL_SUCCESS) || (ret == SQL_SUCCESS_WITH_INFO)) { +  push_sqlwchar(buf, buf_len); +  cnt++; +  +  ODBC_ALLOW(); +  ret = SQLDataSourcesW(odbc_henv, SQL_FETCH_NEXT, +  buf, SQL_MAX_DSN_LENGTH, &buf_len, +  descr, 255, &descr_len); +  ODBC_DISALLOW(); +  } +  +  f_aggregate(cnt); + } +  + #ifdef PIKE_THREADS + /*! @decl int(0..1) connect_lock (void|int enable) +  *! +  *! Enable or disable a mutex that serializes all ODBC SQLConnect +  *! calls (i.e. when ODBC connections are created). This lock might be +  *! necessary to work around bugs in ODBC drivers. +  *! +  *! @param enable +  *! Enables the mutex if nonzero, disables it otherwise. The state +  *! is not changed if this argument is left out. +  *! +  *! @returns +  *! The old state of the flag. +  *! +  *! @note +  *! This is currently enabled by default due to bugs in the current +  *! FreeTDS library (version 0.63), but that might change if the +  *! demand for this kludge ceases in the future. Therefore, if this +  *! setting is important to you then always set it explicitly. +  *! Hopefully most users don't need to bother with it. +  */ + PIKEFUN int(0..1) connect_lock(int|void enable) + { +  int old = enable_connect_lock; +  if (enable) { +  enable_connect_lock = !UNSAFE_IS_ZERO(enable); +  } +  push_int (old); + } + #endif +  + #endif /* HAVE_ODBC */ +  + /*! @endmodule +  */ +  + /* +  * Module linkage +  */ +  + PIKE_MODULE_INIT + { + #ifdef HAVE_ODBC +  RETCODE err = SQLAllocEnv(&odbc_henv); +  +  if (err != SQL_SUCCESS) { +  odbc_henv = SQL_NULL_HENV; +  return; +  /* Pike_error("odbc_module_init(): SQLAllocEnv() failed with code %08x\n", err); */ +  } +  + #if defined (PIKE_THREADS) && !defined (HAS_STATIC_MUTEX_INIT) +  mt_init (&connect_mutex); + #endif +  +  INIT; +  + #else +  HIDE_MODULE(); + #endif /* HAVE_ODBC */ + } +  + PIKE_MODULE_EXIT + { + #ifdef HAVE_ODBC +  exit_odbc_res(); +  + #if defined (PIKE_THREADS) && !defined (HAS_STATIC_MUTEX_INIT) +  mt_destroy (&connect_mutex); + #endif +  +  EXIT; +  +  if (odbc_henv != SQL_NULL_HENV) { +  RETCODE err = SQLFreeEnv(odbc_henv); +  odbc_henv = SQL_NULL_HENV; +  +  if ((err != SQL_SUCCESS) && (err != SQL_SUCCESS_WITH_INFO)) { +  my_yyerror("Odbc: SQLFreeEnv() failed with code %08x\n", err); +  } +  } + #endif /* HAVE_ODBC */ + }   Newline at end of file added.