From de3862a49ca6061c97b03bbeacc8fd3a1a0522eb Mon Sep 17 00:00:00 2001
From: Francesco Chemolli <li@kinkie.it>
Date: Sun, 26 Mar 2000 22:43:08 +0200
Subject: [PATCH] First public release

Rev: src/modules/sybase/BUGS:1.1
Rev: src/modules/sybase/Makefile.in:1.1
Rev: src/modules/sybase/TODO:1.1
Rev: src/modules/sybase/acconfig.h:1.1
Rev: src/modules/sybase/configure.in:1.1
Rev: src/modules/sybase/sybase.c:1.1
Rev: src/modules/sybase/sybase.h:1.1
---
 .gitattributes                  |    5 +
 src/modules/sybase/BUGS         |   27 +
 src/modules/sybase/Makefile.in  |   19 +
 src/modules/sybase/TODO         |   13 +
 src/modules/sybase/acconfig.h   |   51 ++
 src/modules/sybase/configure.in |  153 ++++
 src/modules/sybase/sybase.c     | 1252 +++++++++++++++++++++++++++++++
 src/modules/sybase/sybase.h     |   44 ++
 8 files changed, 1564 insertions(+)
 create mode 100644 src/modules/sybase/BUGS
 create mode 100644 src/modules/sybase/Makefile.in
 create mode 100644 src/modules/sybase/TODO
 create mode 100644 src/modules/sybase/acconfig.h
 create mode 100755 src/modules/sybase/configure.in
 create mode 100644 src/modules/sybase/sybase.c
 create mode 100644 src/modules/sybase/sybase.h

diff --git a/.gitattributes b/.gitattributes
index 1ccfedf400..fe05754110 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -456,6 +456,11 @@ testfont binary
 /src/modules/sprintf/configure.in foreign_ident
 /src/modules/sprintf/sprintf.c foreign_ident
 /src/modules/static_module_makefile.in foreign_ident
+/src/modules/sybase/BUGS foreign_ident
+/src/modules/sybase/Makefile.in foreign_ident
+/src/modules/sybase/TODO foreign_ident
+/src/modules/sybase/acconfig.h foreign_ident
+/src/modules/sybase/sybase.c foreign_ident
 /src/modules/system/Makefile.in foreign_ident
 /src/modules/system/acconfig.h foreign_ident
 /src/modules/system/configure.in foreign_ident
diff --git a/src/modules/sybase/BUGS b/src/modules/sybase/BUGS
new file mode 100644
index 0000000000..816515db19
--- /dev/null
+++ b/src/modules/sybase/BUGS
@@ -0,0 +1,27 @@
+# BUGS file for the sybase driver
+# By Francesco Chemolli, (C) Roxen IS
+# $Id: BUGS,v 1.1 2000/03/26 20:43:08 kinkie Exp $
+
+While this could not be traced to a fault in the driver, we were reported
+memory leaks when running multi-threaded under Linux with 
+Sybase ASE 11.9.2.
+
+The possible explanation is that while the linux libraries are thread-safe,
+they're not thread-hot (this is sybase's saying, not ours). Until better
+checks are implemented (see the TODO file), refrain from using this driver
+in a multi-threaded fashion unless the reentrant libraries can be found on
+your system (i.e. /opt/sybase/lib/libtcl_r.so).
+
+Due to the design of the sybase interface, some SQL-interface methods are
+not implemented, you'll get exceptions when trying to use those. This
+driver however behaves mostly nicely when used with the Sql.sql interface.
+
+Also, due to the uniqueness of the sybase interface design, it is NOT
+possible to have multiple pending results for the same connection. Kludging
+around this would be very dangerous in terms of memory consumption (it
+would require fetching the whole results-set before issuing the new query)
+that it won't be done.
+
+Another current limitation of the current implementation is that it lacks a
+cancel method. This means, if you wish to issue a query, you must have
+fetched all the rows for the previous one.
\ No newline at end of file
diff --git a/src/modules/sybase/Makefile.in b/src/modules/sybase/Makefile.in
new file mode 100644
index 0000000000..2f634a7c6f
--- /dev/null
+++ b/src/modules/sybase/Makefile.in
@@ -0,0 +1,19 @@
+# $Id: Makefile.in,v 1.1 2000/03/26 20:43:08 kinkie Exp $
+# by Francesco Chemolli <kinkie@roxen.com>
+# (C) Roxen IS
+
+@make_variables@
+SRCDIR=@srcdir@
+VPATH=@srcdir@:@srcdir@/../..:../..
+MODULE_CPPFLAGS=@DEFS@ @CPPFLAGS@
+MODULE_LDFLAGS=@LDFLAGS@ @LIBS@
+OBJS=sybase.o
+
+CONFIG_HEADERS=@CONFIG_HEADERS@
+
+@dynamic_module_makefile@
+@dependencies@
+
+spotless: clean
+	-rm config.* pgres_config.h
+
diff --git a/src/modules/sybase/TODO b/src/modules/sybase/TODO
new file mode 100644
index 0000000000..3d46be11b8
--- /dev/null
+++ b/src/modules/sybase/TODO
@@ -0,0 +1,13 @@
+# TODO file for the sybase pike driver
+# by Francesco Chemolli, (C) Roxen IS
+# $Id: TODO,v 1.1 2000/03/26 20:43:08 kinkie Exp $
+
+Improve the checks for the sybase-libraries, and use a lock to serialize
+all accesses unless the reentrant (thread-hot) libraries are found. This
+could solve the memory leaks experienced when running multi-threaded on
+Linux.
+
+Re-implement the whole show, with better wrappers. Remove most logic from
+the C module in favor of the pike module, and move towards a more standard
+big_query approach (with a separate pike module for the
+result-object). This will allow for better emulation of the other drivers.
\ No newline at end of file
diff --git a/src/modules/sybase/acconfig.h b/src/modules/sybase/acconfig.h
new file mode 100644
index 0000000000..b9b3bae182
--- /dev/null
+++ b/src/modules/sybase/acconfig.h
@@ -0,0 +1,51 @@
+/*
+ * $Id: acconfig.h,v 1.1 2000/03/26 20:43:08 kinkie Exp $
+ * 
+ * Config-file for the Pike sybase driver module
+ *
+ * by Francesco Chemolli
+ * (C) Roxen IS
+ */
+
+#ifndef __PIKE_SYBASE_CONFIG_H
+#define __PIKE_SYBASE_CONFIG_H
+
+@TOP@
+@BOTTOM@
+
+/* End of automatic session. Doing stuff now */
+
+#if defined(HAVE_LIBCOMN) || defined(HAVE_LIBCOMN64) || \
+  defined(HAVE_LIBCOMN_DCE) || defined(HAVE_LIBCOMN_DCE64) || \
+  defined(HAVE_LIBCOMN_R)
+#define PIKE_HAVE_LIBCOMN
+#endif
+
+#if defined(HAVE_LIBCS) || defined(HAVE_LIBCS64) || \
+  defined(HAVE_LIBCS_R) || defined(HAVE_LIBCS_R64)
+#define PIKE_HAVE_LIBCS
+#endif
+
+#if defined(HAVE_LIBCT) || defined(HAVE_LIBCT64) || \
+  defined(HAVE_LIBCT_R) || defined(HAVE_LIBCT_R64)
+#define PIKE_HAVE_LIBCT
+#endif
+
+#if defined(HAVE_LIBINTL) || defined(HAVE_LIBINTL64) || \
+  defined(HAVE_LIBINTL_R) || defined(HAVE_LIBINTL_R64)
+#define PIKE_HAVE_LIBINTL
+#endif
+
+#if defined(HAVE_LIBSYBTCL) || defined(HAVE_LIBTCL) || \
+  defined(HAVE_LIBTCL64) || defined(HAVE_LIBTCL_DCE) || \
+  defined(HAVE_LIBTCL_DCE64) || defined(HAVE_LIBTCL_R)
+#define PIKE_HAVE_LIBSYBTCL
+#endif
+
+#if defined(PIKE_HAVE_LIBCOMN) && defined(PIKE_HAVE_LIBCS) \
+  && defined(PIKE_HAVE_LIBCT) && defined(PIKE_HAVE_LIBINTL) \
+  && defined(PIKE_HAVE_LIBSYBTCL) && defined(HAVE_CTPUBLIC_H)
+#define HAVE_SYBASE
+#endif
+
+#endif /* __PIKE_SYBASE_CONFIG_H */
diff --git a/src/modules/sybase/configure.in b/src/modules/sybase/configure.in
new file mode 100755
index 0000000000..2a9846df62
--- /dev/null
+++ b/src/modules/sybase/configure.in
@@ -0,0 +1,153 @@
+dnl This file is part of the Sybase driver for the Pike programming language
+dnl by Francesco Chemolli <kinkie@roxen.com> 10/12/1999
+dnl (C) Roxen IS
+
+
+AC_INIT(sybase.c)
+AC_CONFIG_HEADER(sybase_config.h)
+
+echo "Configureing for sybase support"
+OLD_CPPFLAGS="$CPPFLAGS"
+OLD_CFLAGS="$CFLAGS"
+OLD_LIBS="$LIBS"
+OLD_LDFLAGS="$LDFLAGS"
+
+AC_ARG_WITH(sybase,
+    [  --with(out)-sybase               Include the Sybase database driver],
+    [with_sybase=no],[with_sybase=yes])
+AC_ARG_WITH(sybase-include-dir,
+    [  --with-sybase-include-dir        Sybase headers directory location],
+    [pike_sybase_include_dir=$withval])
+AC_ARG_WITH(sybase-lib-dir,
+    [  --with-sybase-lib-dir            Sybase libraries directory location],
+    [pike_sybase_lib_dir=$withval])
+
+
+dnl "reasonable" search paths we'll look in $root/$prefix/$path
+pike_sybase_reasonable_roots="$SYBASE / /usr/local /usr /opt /usr $HOME"
+pike_sybase_reasonable_prefixes="sybase include lib /"
+pike_sybase_reasonable_paths="sybase include lib /"
+pike_sybase_reasonable_extra_paths="$pike_sybase_include_dir $pike_sybase_lib_dir"
+pike_sybase_reasonable_libs_tosearch="libtcl.a libsybtcl.a libtcl64.a libtcl_r.a libtcl_dce.a libtcl_dce64.a libtcl.so libsybtcl.so libtcl64.so libtcl_r.so libtcl_dce.so libtcl_dce64.so"
+
+AC_MSG_CHECKING(for include files location)
+if test x$pike_sybase_include_dir != x; then
+    AC_MSG_RESULT(user-provided: $pike_sybase_include_dir)
+    pike_cv_sybase_include_dir=$pike_sybase_include_dir
+else
+    AC_MSG_RESULT(going hunting...)
+fi
+
+AC_CACHE_VAL(pike_cv_sybase_include_dir,
+    [
+    for sybroot in $pike_sybase_reasonable_roots
+    do
+        for sybprefix in $pike_sybase_reasonable_prefixes
+        do
+            for sybpath in $pike_sybase_reasonable_paths
+            do
+                AC_MSG_CHECKING(in $sybroot/$sybprefix/$sybpath)
+                if test -f $sybroot/$sybprefix/$sybpath/ctpublic.h; then
+                    pike_cv_sybase_include_dir="$sybroot/$sybprefix/$sybpath"
+                    AC_MSG_RESULT("Found")
+                    break 3;
+                else
+                    AC_MSG_RESULT("Not Found")
+                fi
+            done
+        done
+    done
+    ])
+
+if test x$pike_cv_sybase_include_dir != x; then
+    AC_MSG_RESULT(Found: $pike_cv_sybase_include_dir)
+else
+    AC_MSG_RESULT(Not found.)
+fi
+
+AC_MSG_CHECKING(for library files location)
+if test x$pike_sybase_lib_dir != x; then
+    AC_MSG_RESULT(user-provided: $pike_sybase_lib_dir)
+    pike_cv_sybase_lib_dir=$pike_sybase_lib_dir
+else
+    AC_MSG_RESULT(going hunting...)
+fi
+
+AC_CACHE_VAL(pike_cv_sybase_lib_dir,
+    [
+    for sybroot in $pike_sybase_reasonable_roots
+    do
+        for sybprefix in $pike_sybase_reasonable_prefixes
+        do
+            for sybpath in $pike_sybase_reasonable_paths
+            do
+                AC_MSG_CHECKING(in $sybroot/$sybprefix/$sybpath)
+                for syblib in $pike_sybase_reasonable_libs_tosearch
+                do
+                    if test -f $sybroot/$sybprefix/$sybpath/$syblib; then
+                        pike_cv_sybase_lib_dir="$sybroot/$sybprefix/$sybpath"
+                        AC_MSG_RESULT("Found")
+                        break 4;
+                    fi
+                done
+            AC_MSG_RESULT("Not Found")
+            done
+        done
+    done
+    ])
+
+if test x$pike_cv_sybase_lib_dir != x; then
+    AC_MSG_RESULT(Found: $pike_cv_sybase_lib_dir)
+else
+    AC_MSG_RESULT(Not found.)
+fi
+
+
+if test x$pike_cv_sybase_include_dir != x; then
+    CPPFLAGS="-I$pike_cv_sybase_include_dir $CPPFLAGS"
+fi
+if test x$pike_cv_sybase_lib_dir != x; then
+    LDFLAGS="-L$pike_cv_sybase_lib_dir $LDFLAGS"
+fi
+
+AC_MODULE_INIT()
+
+if test x$with_sybase != xno; then
+    AC_CHECK_LIB(m,floor)
+    AC_CHECK_LIB(dl,dlopen)
+    AC_CHECK_LIB(nsl,gethostbyname)
+    AC_CHECK_LIB(intl_r64,intl_datetime)
+    AC_CHECK_LIB(intl_r,intl_datetime)
+    AC_CHECK_LIB(intl64,intl_datetime)
+    AC_CHECK_LIB(intl,intl_datetime)
+    AC_CHECK_LIB(comn_dce64,comn_free)
+    AC_CHECK_LIB(comn_dce,comn_free)
+    AC_CHECK_LIB(comn_r,comn_free)
+    AC_CHECK_LIB(comn64,comn_free)
+    AC_CHECK_LIB(comn,comn_free)
+    AC_CHECK_LIB(tcl_dce64,syb_net_connect)
+    AC_CHECK_LIB(tcl_dce,syb_net_connect)
+    AC_CHECK_LIB(tcl_r,syb_net_connect)
+    AC_CHECK_LIB(tcl64,syb_net_connect)
+    AC_CHECK_LIB(sybtcl,syb_net_connect)
+    AC_CHECK_LIB(tcl,syb_net_connect)
+    AC_CHECK_LIB(cs64,cs_ctx_alloc)
+    AC_CHECK_LIB(cs_r64,cs_ctx_alloc)
+    AC_CHECK_LIB(cs_r,cs_ctx_alloc)
+    AC_CHECK_LIB(cs,cs_ctx_alloc)
+    AC_CHECK_LIB(ct64,ct_callback)
+    AC_CHECK_LIB(ct_r64,ct_callback)
+    AC_CHECK_LIB(ct_r,ct_callback)
+    AC_CHECK_LIB(ct,ct_callback)
+
+    AC_CHECK_HEADERS(ctpublic.h)
+
+fi
+
+dnl this is to allow compilation with both pike/0.6 and pike/0.7
+make_variables="/dev/null"
+AC_SUBST_FILE(make_variables)
+
+
+AC_OUTPUT(Makefile,echo FOO >stamp-h)
+
diff --git a/src/modules/sybase/sybase.c b/src/modules/sybase/sybase.c
new file mode 100644
index 0000000000..3d315c3524
--- /dev/null
+++ b/src/modules/sybase/sybase.c
@@ -0,0 +1,1252 @@
+/*
+ * Sybase driver for the Pike programming language.
+ *
+ * By Francesco Chemolli <kinkie@roxen.com> 10/12/1999
+ * (C) Roxen IS
+ *
+ */
+
+/* The sybase libraries are thread-safe, and very well-documented
+ * about that (page 2-131)
+ * For simplicity's sake, for now I'll use a per-connection (== per-object)
+ * lock to serialize connection-related calls, and a program-level lock
+ * for context-related operations. I could use more locks, to have a
+ * more fine-grained locking, but since I now aim to generic SQL-interface
+ * compliancy, this should do nicely.
+ */
+
+/*
+ * wishlist/todolist:
+ * - have an asynchronous messages reporter
+ * - solve the signals-related problems
+ */
+
+#include "sybase_config.h"
+#include "global.h"
+
+RCSID("$Id: sybase.c,v 1.1 2000/03/26 20:43:08 kinkie Exp $");
+
+#ifdef HAVE_SYBASE
+
+
+#include "stralloc.h"
+#include "error.h"
+#include "program.h"
+#include "las.h"
+#include "threads.h"
+#include "module_support.h"
+#include "builtin_functions.h"
+#include "dmalloc.h"
+#include "port.h"
+#include "multiset.h"
+#include "mapping.h"
+
+#include "sybase.h"
+
+
+
+/* define this to enable debugging */
+/* #define SYBDEBUG */
+
+/* define this to cancel pending results instead of retrieving them */
+#define FLUSH_BY_CANCELING
+
+/* the sybase libraries are not particularly clever in letting the user
+ * know how much memory space he'll need to allocate in order to completely
+ * fit a column. They _will_ notify him if an overflow occurs, but then
+ * it's too late, isn't it? Some complex strategies, involving re-fetching
+ * an offending row (if an overflow occurs, ct_fetch will return CS_ROW_FAIL),
+ * or trying to query sybase about the needed sizes for each fetched row.
+ * For now it's just easier to reserve more space than actually necessary
+ * This define marks how much extra memory is to be reserved for each
+ * column.
+ */
+#define EXTRA_COLUMN_SPACE 10240
+
+/* big. VERY big. (2^31) */
+#define MAX_RESULTS_SIZE 0x7FFFFFFF
+
+#ifdef SYBDEBUG
+#define sybdebug(X) fprintf X
+#define errdebug(err) if(err) sybdebug((stderr,err))
+#else
+#define sybdebug(X)
+#define errdebug(err)
+#endif
+
+/* pike 0.6 compatibility stuff */
+#ifndef ADD_STORAGE
+#define ADD_STORAGE(X) add_storage(sizeof(X))
+#endif
+
+
+/* Actual code */
+#ifdef _REENTRANT
+/* note to self. Whenever the mainlock is needed and there is an
+ * object involved, always acquire the object's lock first.
+ * post scriptum. Override this: if the main lock is needed, acquire
+ * only that. However, if anybody experiences deadlocks, restore this
+ * behaviour.
+ */
+static MUTEX_T mainlock;
+#define SYB_LOCK(X) sybdebug((stderr,"locking %s\n",#X));mt_lock(&(X))
+#define SYB_UNLOCK(X) sybdebug((stderr,"unlocking %s\n",#X));mt_unlock(&(X))
+#define SYB_MT_INIT(X) mt_init(&(X))
+#define SYB_MT_EXIT(X) mt_destroy(&(X))
+#else
+#define SYB_LOCK(lock)
+#define SYB_UNLOCK(lock)
+#define SYB_MT_INIT(lock)
+#define SYB_MT_EXIT(lock)
+#endif
+
+#define FAILED(status) (status!=CS_SUCCEED)
+#define OK(status) (status==CS_SUCCEED)
+
+#define THIS ((pike_sybase_connection *) (fp->current_storage))
+
+
+
+/*************/
+/* Utilities */
+/*************/
+
+#ifdef SYBDEBUG
+
+/* just a small thing. see f_fetch_row to understand how it's used */
+#define SHOW_STATUS(Y,X) case X:sybdebug((stderr,"\t%s is %s\n",Y,#X));break
+
+static void show_status(CS_RETCODE ret) {
+    switch(ret) {
+      SHOW_STATUS("status",CS_SUCCEED);
+      SHOW_STATUS("status",CS_PENDING);
+      SHOW_STATUS("status",CS_BUSY);
+      SHOW_STATUS("status",CS_END_RESULTS);
+      SHOW_STATUS("status",CS_FAIL);
+      SHOW_STATUS("status",CS_END_DATA);
+    default: 
+      sybdebug((stderr,"Unknown status: %d\n",(int)ret));
+      break;
+    }
+}
+static void show_results_type (CS_INT rtype) {
+    switch(rtype) {
+      SHOW_STATUS("type",CS_ROW_RESULT);
+      SHOW_STATUS("type",CS_CURSOR_RESULT);
+      SHOW_STATUS("type",CS_PARAM_RESULT);
+      SHOW_STATUS("type",CS_STATUS_RESULT);
+      SHOW_STATUS("type",CS_COMPUTE_RESULT);
+      SHOW_STATUS("type",CS_MSG_RESULT);
+      SHOW_STATUS("type",CS_DESCRIBE_RESULT);
+      SHOW_STATUS("type",CS_ROWFMT_RESULT);
+      SHOW_STATUS("type",CS_COMPUTEFMT_RESULT);
+      SHOW_STATUS("type",CS_CMD_DONE);
+      SHOW_STATUS("type",CS_CMD_SUCCEED);
+      SHOW_STATUS("type",CS_CMD_FAIL);
+    default:
+      sybdebug((stderr,"Unknown type: %d\n",(int)rtype));
+      break;
+    }
+}
+static void show_severity(CS_INT severity) {
+    switch (severity) {
+      SHOW_STATUS("severity",CS_SV_INFORM);
+      SHOW_STATUS("severity",CS_SV_CONFIG_FAIL);
+      SHOW_STATUS("severity",CS_SV_RETRY_FAIL);
+      SHOW_STATUS("severity",CS_SV_API_FAIL);
+      SHOW_STATUS("severity",CS_SV_RESOURCE_FAIL);
+      SHOW_STATUS("severity",CS_SV_COMM_FAIL);
+      SHOW_STATUS("severity",CS_SV_INTERNAL_FAIL);
+      SHOW_STATUS("severity",CS_SV_FATAL);
+    }  
+}
+#else /* SYBDEBUG */
+#define show_status(X)
+#define show_results_type(X)
+#define show_severity(X)
+#endif
+
+/*
+ * Must be called in a MT-safe fashion
+ */
+static INT32 guess_column_length (CS_COMMAND *cmd, int column_number) {
+  CS_DATAFMT description;
+
+  ct_describe(cmd,column_number,&description);
+  return (INT32)description.maxlength;
+}
+
+/* this function does _NOT_ involve any pike processing
+ * If run in MT-enabled sections, locking is to be insured by the caller
+ * on a per-command-structure basis.
+ */
+static void flush_results_queue(pike_sybase_connection *this) {
+  CS_INT rtype;
+  CS_RETCODE ret;
+  int j;
+  CS_COMMAND *cmd;
+
+
+  sybdebug((stderr,"Flushing the results queue\n"));
+#ifdef FLUSH_BY_CANCELING
+  ct_cancel(this->connection,NULL,CS_CANCEL_ALL);
+#else
+  cmd=this->cmd;
+  for (j=0;j<100;j++) { /* safety valve, I don't want to loop forever */
+    
+    sybdebug((stderr,"Getting results #%d\n",j));
+    ret=ct_results(cmd,&rtype);
+    show_status(ret);
+    show_results_type(rtype);
+
+    switch(ret) {
+    case CS_END_RESULTS:
+      sybdebug((stderr,"Finished flushing results\n"));
+      return;
+    case CS_FAIL:
+      ret=ct_cancel(this->connection,NULL,CS_CANCEL_ALL);
+      continue;
+    }
+    
+    /* I'd probably be getting a result back here. I don't need it, so
+     * I'll just cancel */
+    switch(rtype) {
+    case CS_ROW_RESULT:
+    case CS_STATUS_RESULT:
+    case CS_PARAM_RESULT:
+      sybdebug((stderr,"Unexpected result. Cancel-ing\n"));
+      ret=ct_cancel(NULL,this->cmd,CS_CANCEL_CURRENT);
+      show_status(ret);
+      break;
+    }
+  }
+#endif
+}
+
+/*
+ * returns 1 if there's any error
+ * the same thread-safety rules as flush_results_queue apply
+ * QUESTION: 
+ * Should I explore all messages, and leave in this->error the
+ * last one with an error-level severity?
+ * or should we maybe only consider server messages?
+ */
+static int handle_errors (pike_sybase_connection *this) {
+  SQLCA message;
+  CS_INT num,j, severity;
+  CS_RETCODE ret;
+  
+  sybdebug((stderr,"Handling errors\n"));
+  ret=ct_diag(this->connection,CS_STATUS,CS_ALLMSG_TYPE,CS_UNUSED,&num);
+  show_status(ret);
+  if (FAILED(ret)) {
+    sybdebug((stderr,"Error while retrieving errors number"));
+    return 1;
+  }
+  sybdebug ((stderr, "I have %d messages in queue\n",(int)num));
+
+  if (!num) /* no need to go through the moves */
+    return 0;
+
+  for (j=1;j<=num;j++) {
+    ct_diag(this->connection, CS_GET, SQLCA_TYPE, j, &message);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"Error while retrieving last message\n"));
+      return 1;
+    }
+    sybdebug((stderr,"\nMessage %d length: %ld; text:\n%s\n",j,
+             ((int)message.sqlerrm.sqlerrml),message.sqlerrm.sqlerrmc));
+    /* If it's severe enough? */
+    severity=CS_SEVERITY(message.sqlcode);
+    show_severity(severity);
+  }
+
+  MEMCPY(this->error,message.sqlerrm.sqlerrmc,
+         message.sqlerrm.sqlerrml+1);
+
+  this->had_error=1;
+  ct_diag(this->connection,CS_CLEAR,SQLCA_TYPE,CS_UNUSED,NULL);
+  
+  return 0;
+}
+
+
+/*******************/
+/* low_level stuff */
+/*******************/
+
+static void sybase_create (struct object * o) {
+  pike_sybase_connection *this=THIS;
+  
+  sybdebug((stderr,"sybase_create()\n"));
+  this->context=NULL;
+  this->connection=NULL;
+  this->cmd=NULL;
+  this->busy=0;
+  this->results=NULL;
+  this->results_lengths=NULL;
+  this->nulls=NULL;
+  this->numcols=0;
+  this->had_error=0;
+  SYB_MT_INIT(THIS->lock);
+}
+
+
+static void sybase_destroy (struct object * o) {
+  pike_sybase_connection *this=THIS;
+  CS_RETCODE ret;
+  int j;
+
+  sybdebug((stderr,"sybase_destroy()\n"));
+  THREADS_ALLOW();
+  SYB_LOCK(this->lock);
+
+
+  if (this->busy > 0) {
+    sybdebug((stderr,"We're busy while destroying. Trying to cancel\n"));
+    ret=ct_cancel(this->connection,NULL,CS_CANCEL_ALL); 
+    show_status(ret);
+    /* we only have one active command, but what the hell.. */
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tUh oh... failed\n"));
+    }
+    this->busy--;
+    sybdebug((stderr,"Busy status: %d\n",this->busy));
+    /* if we fail, it's useless anyways. Maybe we should fatal() */
+  }
+  
+  if (this->cmd) {
+    sybdebug((stderr,"this->cmd still active. Dropping\n"));
+    ret=ct_cmd_drop(this->cmd);
+    show_status(ret);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tHm... failed\n"));
+    }
+    this->cmd=NULL; 
+    /* if we fail, it's useless anyways. Maybe we should fatal() */
+  }
+
+  if (this->results) {
+    sybdebug((stderr,"Freeing results buffers\n"));
+    for (j=0;j<this->numcols;j++) {
+      free(this->results[j]);
+    }
+    free (this->results);
+    free (this->results_lengths);
+    free (this->nulls);
+    this->results=NULL;
+    this->results_lengths=NULL;
+    this->nulls=NULL;
+    this->numcols=0;
+  }
+
+  if (this->connection) {
+    sybdebug((stderr,"this->connection still active. Closing\n"));
+    ret=ct_close(this->connection,CS_UNUSED);
+    show_status(ret);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tArgh! Failed!\n"));
+    }
+    sybdebug((stderr,"Dropping connection\n"));
+    ret=ct_con_drop(this->connection);
+    show_status(ret);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"Gasp! Failed!\n"));
+    }
+    this->connection=NULL;
+    /* if we fail, it's useless anyways. Maybe we should fatal() */
+  }
+
+  SYB_LOCK(mainlock); /* this is really needed only here */
+
+  if (this->context) {
+    sybdebug((stderr,"this->context still active. Exiting\n"));
+    ret = ct_exit(this->context, CS_UNUSED);
+    show_status(ret);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tGosh Batman! We failed!\n"));
+    }
+    ret = cs_ctx_drop(this->context);
+    show_status(ret);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tGosh Robin! You're right!\n"));
+    }
+    this->context=NULL;
+    /* if we fail, it's useless anyways. Maybe we should fatal() */
+  }
+
+  SYB_UNLOCK(mainlock);
+  SYB_UNLOCK(this->lock);
+  
+  THREADS_DISALLOW();
+  SYB_MT_EXIT(THIS->lock);
+}
+
+/**********************/
+/* exported functions */
+/**********************/
+
+static void f_error(INT32 args) {
+  pike_sybase_connection *this=THIS;
+
+  pop_n_elems(args);
+
+  if (this->had_error)
+    push_text(this->error);
+  else
+    push_int(0);
+}
+
+
+/* void connect(host, database, username, password, port) */
+/*  The port and database arguments are silently ignored  */
+static void f_connect (INT32 args) {
+  CS_RETCODE ret;
+  CS_CONTEXT *context;
+  CS_CONNECTION *connection;
+  char *username=NULL, *pass=NULL, *hostname=NULL, *err=NULL;
+  int usernamelen=0, passlen=0, hostnamelen=CS_UNUSED;
+  pike_sybase_connection *this=THIS;
+
+  
+  sybdebug((stderr,"sybase::connect(args=%d)\n",args));
+  check_all_args("sybase->connect",args,
+                 BIT_STRING|BIT_VOID,BIT_STRING|BIT_VOID,
+                 BIT_STRING|BIT_VOID,BIT_STRING|BIT_VOID,
+                 BIT_INT|BIT_VOID,0);
+
+  /* fetch arguments, so that we can enable threads */
+  if (sp[2-args].type == T_STRING && sp[2-args].u.string->len) { /*username*/
+    username=sp[2-args].u.string->str;
+    usernamelen=sp[2-args].u.string->len;
+    sybdebug((stderr,"\tgot username: %s\n",username));
+  }
+  if (sp[3-args].type == T_STRING && sp[2-args].u.string->len) { /*password*/
+      pass=sp[3-args].u.string->str;
+    passlen=sp[3-args].u.string->len;
+    sybdebug((stderr,"\tgot password: %s\n",pass));
+  }
+  if (sp[-args].type==T_STRING && sp[-args].u.string->len) { /*hostname*/
+    hostname=sp[-args].u.string->str;
+    hostnamelen=sp[-args].u.string->len;
+    sybdebug((stderr,"\tgot hostname: %s\n",hostname));
+  }
+
+  THREADS_ALLOW();
+  SYB_LOCK(mainlock); 
+  
+  /* It's OK not to lock here. It's just a check that should never happen.
+   * if it happens, we're in deep sh*t already.*/
+  if (!(context=this->context)) {
+    err="Internal error: connection attempted, but no context available\n";
+  }
+  
+  if (!err) {
+    sybdebug((stderr,"\tallocating context\n"));
+    ret=ct_con_alloc(context,&connection); /*sybase says it's thread-safe..*/
+    show_status(ret);
+    if (FAILED(ret)) {
+      err="Cannot initialize connection\n";
+    }
+  }
+  errdebug(err);
+  
+  if (!err) { /* initialize error-handling code */
+    ret=ct_diag(connection,CS_INIT,CS_UNUSED,CS_UNUSED,NULL);
+    show_status(ret);
+    if (FAILED(ret)) {
+      err="Can't initialize error-handling code\n";
+    }
+  }
+  errdebug(err);
+
+  /* if there already was an error, we just locked uselessly.
+   * No big deal, it should never happen anyways... */
+
+  /* username */
+  if (!err && usernamelen) {
+    sybdebug((stderr,"\tsetting username to \"%s\"\n",username));
+    ret=ct_con_props(connection,CS_SET,CS_USERNAME,
+                     username,usernamelen,NULL);
+    show_status(ret);
+    if (FAILED(ret)) {
+      err="Can't set username\n";
+    }
+  }
+  errdebug(err);
+
+  /* password */
+  if (!err && passlen) {
+    sybdebug((stderr,"\tsetting password to \"%s\"\n",pass));
+    ret=ct_con_props(connection,CS_SET,CS_PASSWORD,
+                     pass,passlen,NULL);
+    show_status(ret);
+    if (FAILED(ret)) {
+      err="Can't set password\n";
+    }
+  }
+  errdebug(err);
+
+  /* connect, finally */
+  if (!err) {
+    if (hostname) {
+      sybdebug((stderr,"\tconnecting to hostname is \"%s\"\n",hostname));
+    }
+    ret=ct_connect(connection,(CS_CHAR *)hostname, hostnamelen);
+    show_status(ret);
+    if (FAILED(ret)) {
+      err="Can't connect\n";
+    } else {
+      this->connection=connection;
+    }
+  }
+  errdebug(err);
+
+  if (err) ct_con_drop(connection);
+      
+  SYB_UNLOCK(mainlock);
+  THREADS_DISALLOW();
+
+  if (err) {
+    handle_errors(this); /* let's centralize */
+    error(err);
+  }
+
+  pop_n_elems(args);
+  sybdebug((stderr,"sybase::connect exiting\n"));
+  
+}
+
+/* create (host,database,username,password,port) */
+static void f_create (INT32 args) {
+  char *host,*db,*user,*pass;
+  CS_RETCODE ret;
+  CS_CONTEXT *context;
+  char* err=NULL;
+  pike_sybase_connection *this=THIS;
+  
+  sybdebug((stderr,"sybase::create(args=%d)\n",args));
+
+  check_all_args("sybase->create",args,
+                 BIT_STRING|BIT_VOID,BIT_STRING|BIT_VOID,
+                 BIT_STRING|BIT_VOID,BIT_STRING|BIT_VOID,
+                 BIT_INT|BIT_VOID,0);
+  
+  /* if connected, disconnect */
+  if (this->context)
+    sybase_destroy(fp->current_object);
+
+  THREADS_ALLOW();
+  SYB_LOCK(mainlock);
+
+  /* allocate context */
+  ret=cs_ctx_alloc(CS_VERSION_110,&(this->context));
+  show_status(ret);
+  if (FAILED(ret))
+    err = "Cannot allocate context!\n";
+  
+  context=this->context;
+
+  /* initialize open client-library emulation */
+  if (!err) {
+    ret=ct_init(context,CS_VERSION_110);
+    if (FAILED(ret)) {
+      ct_exit(context,CS_UNUSED);
+      err="Cannot initialize library version 1.11.X!\n";
+    }
+  }
+
+  /* if there was no error, we can try to connect. Since f_connect
+   * will do its own threads meddling, we must disable threads first
+   */
+
+  if (err) { /* there was an error. bail out */
+    cs_ctx_drop(context);
+    this->context=NULL;
+  }
+
+  SYB_UNLOCK(mainlock);
+  THREADS_DISALLOW();
+  
+  if (err) error(err); /* throw the exception if appropriate */
+
+  /* now connect */
+  if (args)
+    f_connect(args);
+
+  sybdebug((stderr,"sybase::create exiting\n"));
+  /* the args are popped in f_connect */
+}
+
+/* TODO: should I cancel a pending query instead of throwing errors?
+ *
+ * object(sybase.sybase_result)|string big_query(string q)
+ *
+ * NOTICE: the string return value is an extension to the standard API.
+ *  It will returned ONLY if it is the return status from a stored procedure
+ */
+
+#define PS_NORESULT 1
+#define PS_RESULT_THIS 2
+#define PS_RESULT_STATUS 3 
+/* if function_result == PS_RESULT_STATUS, the value to be returned
+   is *function_result */
+
+static void f_big_query(INT32 args) {
+  pike_sybase_connection *this=THIS;
+  char *query, *err=NULL, *function_result=NULL;
+  int querylen, numcols,j, done=0;
+  CS_COMMAND *cmd=this->cmd;
+  CS_RETCODE ret;
+  CS_DATAFMT description, got_desc;
+  CS_INT rtype;
+  char ** results;
+  CS_INT *results_lengths;
+  CS_SMALLINT *nulls;
+  int toreturn=PS_NORESULT; /* one of the #defines here above */
+  
+  
+  
+  /* check, get and pop args */
+  check_all_args("sybase->big_query",args,BIT_STRING,0);
+  query=sp[-args].u.string->str;
+  querylen=sp[-args].u.string->len;
+  sybdebug((stderr,"query: '%s'\n",query));
+  /* pop_n_elems(args); Let's try moving it later */
+
+  sybdebug((stderr,"Busy status: %d\n",this->busy));
+
+  /* verify that we're not busy handing out results */
+  if (this->busy > 0)
+    error("Busy\n");
+
+  THREADS_ALLOW();
+  SYB_LOCK(this->lock);
+  
+  if (cmd==NULL) {
+    /* no sense in alloc-ing everytime */
+    sybdebug((stderr,"Allocating command structure\n"));
+    ret=ct_cmd_alloc(this->connection, &cmd);
+    show_status(ret);
+    
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tUh oh... problems\n"));
+      err="Error allocating command\n";
+    }
+
+    this->cmd=cmd;
+  }
+
+  if (this->results) {
+    sybdebug((stderr,"Freeing previous results DMA memory\n"));
+    for (j=0;j<(this->numcols);j++) {
+      sybdebug((stderr,"Column %d (%p)\n",j,this->results[j]));
+      free(this->results[j]);
+    }
+    sybdebug((stderr,"Freeing main results array\n"));
+    free(this->results);
+    free(this->results_lengths);
+    free(this->nulls);
+    this->results=NULL;
+    this->results_lengths=NULL;
+    this->nulls=NULL;
+    this->numcols=0;
+  }
+
+  if (!err) { /* we can't be done yet */
+    sybdebug((stderr,"issung command\n"));
+    ret=ct_command(cmd,CS_LANG_CMD,query,querylen,CS_UNUSED);
+    show_status(ret);
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\tUh oh... something wrong here\n"));
+      err="Error while setting command\n";
+    }
+  }
+
+  if (!err) { /* we can't be done yet */
+    sybdebug((stderr,"Sending command\n"));
+    ret=ct_send(cmd);
+    show_status(ret);
+    this->busy++;
+    sybdebug((stderr,"Busy status: %d\n",this->busy));
+    if (FAILED(ret)) {
+      sybdebug((stderr,"\thm... I don't like what I'm seeing\n"));
+      err="Error while sending command\n";
+    }
+  }
+  
+  if (err) { /* we can't be done yet */
+    sybdebug((stderr,"Problems. Dropping command\n"));
+    ct_cmd_drop(cmd);
+    this->cmd=NULL;
+/*     this->busy--; */
+/*     sybdebug((stderr,"Busy status: %d\n",this->busy)); */
+  }
+
+
+  /* let's move the first part of a process here.
+   */
+  
+  while (!err && !done) {
+    /* okay, let's see what we got */
+    sybdebug((stderr,"Issuing results\n"));
+    ret=ct_results(cmd,&rtype);
+    show_status(ret);
+    show_results_type(rtype);
+    
+    switch(ret) {
+    case CS_PENDING:
+    case CS_BUSY:
+      sybdebug((stderr,"Got strange stuff\n"));
+      err="Async operations are not supported\n";
+      break;
+    case CS_END_RESULTS:
+      sybdebug((stderr,"No more results\n"));
+/*       this->busy--; */
+/*       sybdebug((stderr,"Busy status: %d\n",this->busy)); */
+      done=1;/* we return 0 from a query. Sounds good.. */
+      break;
+    case CS_FAIL: /* uh oh, something's terribly wrong */
+      sybdebug((stderr,"Command failed. Canceling\n"));
+      ct_cancel(this->connection,NULL,CS_CANCEL_ALL);/* try to cancel result */
+/*       this->busy--; */
+/*       sybdebug((stderr,"Busy status: %d\n",this->busy)); */
+      /* we should really check the return code here.. */
+      err="Canceled\n";
+      flush_results_queue(this);
+      break;
+    case CS_CANCELED:
+      sybdebug((stderr,"Results canceled\n"));
+/*       this->busy--; */
+      flush_results_queue(this);
+      break;
+    default: /* Ok */
+    }
+
+    if (err||done) break; /* get out of the while cycle */
+
+  /* We should probably put this in a do..while cycle, so that we can
+   * ignore STATUS results and such: only the first three carry useful
+   * information..
+   */
+  /* we need to set the fetch-cycle up. It really looks like DMA to me.. */
+    switch (rtype) {
+    case CS_ROW_RESULT:
+    case CS_CURSOR_RESULT:
+    case CS_PARAM_RESULT:
+      sybdebug((stderr,"Got useful results\n"));
+      /* we're getting rows of data here. Also, I'm skipping a few command
+         results, for command which should be pretty safe */
+      ct_res_info(cmd,CS_NUMDATA,&numcols,CS_UNUSED,NULL);
+      this->numcols=numcols;
+      sybdebug((stderr,"I have %d columns' worth of data\n",numcols));
+      sybdebug((stderr,"Allocating results**\n"));
+      /* it looks like xalloc can't be used here. Hubbe? */
+      results=(char**)malloc(numcols*sizeof(char*));
+      results_lengths=(CS_INT*)malloc(numcols*sizeof(CS_INT));
+      nulls=(CS_SMALLINT*)malloc(numcols*sizeof(CS_SMALLINT));
+      this->results=results;
+      this->results_lengths=results_lengths;
+      this->nulls=nulls;
+      
+      /* these values are set for each column, since we're fetching
+       * one row per cycle in fetch_row */
+/*       description.datatype=CS_TEXT_TYPE; */
+      description.datatype=CS_CHAR_TYPE; /* let's see if this works better */
+      description.format=CS_FMT_NULLTERM;
+      description.maxlength=MAX_RESULTS_SIZE-1;
+      description.scale=CS_SRC_VALUE;
+      description.precision=CS_SRC_VALUE;
+      description.count=1;
+      description.locale=NULL;
+
+      for(j=0;j<numcols;j++) {
+        INT32 length=guess_column_length(cmd,j+1);
+        sybdebug((stderr,"beginning work on column %d (index %d)\n",j+1,j));
+        sybdebug((stderr,"Allocating %ld+%d+1 bytes\n",
+                 length,EXTRA_COLUMN_SPACE));
+        /* it looks like xalloc can't be used here. Hubbe? */
+        results[j]=(char*)malloc((length+EXTRA_COLUMN_SPACE+1)*
+                                 sizeof(char));
+        if (results[j]==NULL) {
+          sybdebug((stderr,"PANIC! COULDN'T ALLOCATE MEMORY!"));
+        }
+
+        sybdebug((stderr,"Binding column %d\n",j+1));
+        description.maxlength=length+EXTRA_COLUMN_SPACE; 
+        /* maxlength used to be MAX_RESULTS_SIZE-1. Let's make sure we don't
+         * goof*/
+        ret=ct_bind(cmd,j+1,&description,results[j],
+                    &(results_lengths[j]),&(nulls[j]));
+        /* TODO: replace the first NULL with the length indicator */
+        /* TODO: replace the second NULL with (SQL)NULL indicators */
+        /* see page 3-10 */
+        show_status(ret);
+        if (FAILED(ret)) {
+          err="Failed to bind column\n";
+          break;
+        }
+      }
+      toreturn=PS_RESULT_THIS;
+      done=1;
+      break;
+    case CS_STATUS_RESULT:
+      sybdebug((stderr,"Got status result. Retrieving\n"));
+      do {
+        INT32 length;
+        CS_DATAFMT description;
+        CS_INT rows_read;
+
+        length=guess_column_length(cmd,1);
+
+        sybdebug((stderr,"Guessed length of %d\n",length));
+        function_result=(char*)malloc((length+EXTRA_COLUMN_SPACE+1)*
+                                         sizeof(char));
+
+        description.datatype=CS_CHAR_TYPE;
+        description.format=CS_FMT_NULLTERM;
+        description.maxlength=length+EXTRA_COLUMN_SPACE;
+        description.scale=CS_SRC_VALUE;
+        description.precision=CS_SRC_VALUE;
+        description.count=1;
+        description.locale=NULL;
+        
+        
+        sybdebug((stderr,"Binding...\n"));
+        ret=ct_bind(cmd,1,&description,function_result,NULL,NULL); 
+        /* TODO: use binary strings */
+
+        do {
+          sybdebug((stderr,"Fetching\n"));
+          ret=ct_fetch(cmd,CS_UNUSED,CS_UNUSED,CS_UNUSED,&rows_read);
+          show_status(ret);
+          sybdebug((stderr,"Got: %s\n",function_result));
+        } while (OK(ret));
+        
+        
+        
+        toreturn=PS_RESULT_STATUS;
+        
+      } while (0);
+      /* flush_results_queue(this); No need to. We'll loop in the while
+       * cycle, and if there's some result we'll catch it elsewhere.
+       */
+      break;
+    case CS_COMPUTE_RESULT:
+/*       this->busy--; */
+      err="Compute result is not supported\n";
+      flush_results_queue(this);
+      break;
+    case CS_MSG_RESULT:
+/*       this->busy--; */
+      err="Message result is not supported\n";
+      flush_results_queue(this);
+      break;
+    case CS_DESCRIBE_RESULT:
+/*       this->busy--; */
+      err="Describe result is not supported\n";
+      flush_results_queue(this);
+      break;
+    case CS_ROWFMT_RESULT:
+/*       this->busy--; */
+      err="Row Format result is not supported\n";
+      flush_results_queue(this);
+      break;
+    case CS_COMPUTEFMT_RESULT:
+/*       this->busy--; */
+      err="Compute Format result is not supported\n";
+      flush_results_queue(this);
+      break;
+    case CS_CMD_DONE:
+/*       this->busy--; */
+      flush_results_queue(this);
+      done=1;
+      break;
+    case CS_CMD_SUCCEED:
+/*       this->busy--; */
+      flush_results_queue(this);
+      done=1;
+      break;
+    case CS_CMD_FAIL:
+/*       this->busy--; */
+      err="Command failed\n";
+      flush_results_queue(this);
+      break;
+    }
+  }
+        
+  sybdebug((stderr,"Busy status: %d\n",this->busy));
+
+  SYB_UNLOCK(this->lock);
+  THREADS_DISALLOW();
+
+  if (err) {
+    handle_errors(this);
+    this->busy--;
+    error(err);
+  }
+
+  pop_n_elems(args); /* moved from earlier. */
+
+  /* we're surely done */
+  switch (toreturn) {
+  case PS_NORESULT:
+    this->busy--;
+    push_int(0);
+    break;
+  case PS_RESULT_THIS:
+    ref_push_object(fp->current_object);
+    break;
+  case PS_RESULT_STATUS:
+    this->busy--;
+    push_text(function_result);
+    free(function_result);
+    function_result=NULL;
+    break;
+  default:
+    fatal("Internal error! Wrong result in big_query\n");
+    break;
+  }
+  /* extra safety check. Paranoia. */
+  if (function_result) {
+    sybdebug((stderr,"Internal error! Function_result!=NULL"));
+    free(function_result);
+  }
+}
+
+
+/*
+ * The while cycle is supposed to be outside this function call, in the pike
+ * code.
+ */
+/* void|array(mix) fetch_row() */
+static void f_fetch_row(INT32 args) {
+  pike_sybase_connection *this=THIS;
+  CS_RETCODE ret;
+  CS_COMMAND *cmd=this->cmd;
+  CS_INT numread,j;
+  char **results=this->results;
+  CS_INT *results_lengths=this->results_lengths;
+  CS_SMALLINT *nulls=this->nulls;
+  int numcols=this->numcols;
+
+  pop_n_elems(args);
+
+  if (this->busy<=0)
+    error("No pending results\n");
+
+  THREADS_ALLOW();
+  SYB_LOCK(this->lock);
+
+  sybdebug((stderr,"Fetching row\n"));
+  ret=ct_fetch(cmd,CS_UNUSED,CS_UNUSED,CS_UNUSED,&numread);
+  show_status(ret);
+
+  SYB_UNLOCK(this->lock);
+  THREADS_DISALLOW();
+
+  switch(ret) {
+  case CS_SUCCEED:
+    sybdebug((stderr,"Got row\n"));
+    for(j=0;j<numcols;j++) {
+      if (nulls[j] != -1) {
+        /* !null */
+        push_string(make_shared_binary_string(results[j],
+                                              results_lengths[j]-1));
+      } else {
+        /* null */
+        push_int(0);
+      }
+    }
+    f_aggregate(numcols);
+    return;
+  case CS_END_DATA:
+    sybdebug((stderr,"Got end of data\n"));
+    flush_results_queue(this);
+    push_int(0);
+    this->busy--;
+    sybdebug((stderr,"Busy status: %d\n",this->busy));
+    return;
+  case CS_ROW_FAIL:
+    handle_errors(this);
+    error("Recoverable error while fetching row\n");
+    break;
+  case CS_FAIL:
+    handle_errors(this);
+    ct_cancel(this->connection,cmd,CS_CANCEL_ALL);
+    this->busy--;
+    sybdebug((stderr,"Busy status: %d\n",this->busy));
+    error("Unrecoverable error while fetching row\n");
+    break;
+  case CS_CANCELED:
+    sybdebug((stderr,"Canceled\n"));
+    push_int(0);
+    this->busy--;
+    sybdebug((stderr,"Busy status: %d\n",this->busy));
+    return;
+  case CS_PENDING:
+  case CS_BUSY:
+    error("Asynchronous operations are not supported\n");
+    break;
+  }
+  error("Internal error. We shouldn't get here\n");
+}
+
+/* int num_fields() */
+static void f_num_fields(INT32 args) {
+  CS_INT cols;
+  CS_RETCODE ret;
+  char* err=NULL;
+  pike_sybase_connection *this=THIS;
+
+  pop_n_elems(args);
+  if (this->busy<=0) {
+    error("Issue a command first!\n");
+  }
+
+  THREADS_ALLOW();
+  SYB_LOCK(this->lock);
+
+  ret=ct_res_info(this->cmd,CS_NUMDATA,&cols,CS_UNUSED,NULL);
+  if (FAILED(ret)) {
+    err="Can't retrieve columns number information\n";
+    handle_errors(this);
+  }
+
+  SYB_UNLOCK(this->lock);
+  THREADS_DISALLOW();
+  
+  if (err) error(err);
+  
+  push_int(cols);
+}
+
+/* int affected_rows() */
+static void f_affected_rows(INT32 args) {
+  CS_INT rows;
+  CS_RETCODE ret;
+  char *err=NULL;
+  pike_sybase_connection *this=THIS;
+
+  pop_n_elems(args);
+  
+  THREADS_ALLOW();
+  SYB_LOCK(this->lock);
+
+  ret=ct_res_info(this->cmd,CS_ROW_COUNT,&rows,CS_UNUSED,NULL);
+  if (FAILED(ret)) {
+    err="Can't retrieve affected rows information\n";
+    handle_errors(this);
+  }
+
+  SYB_UNLOCK(this->lock);
+  THREADS_DISALLOW();
+  
+  if (err) error(err);
+  
+  push_int(rows);
+}
+
+
+#define desc_type(X,Y) case X: push_text(#Y);sybdebug((stderr,"type is %s\n",#Y)); break
+static void f_fetch_fields(INT32 args) {
+  pike_sybase_connection *this=THIS;
+  int j, nflags, numcols=this->numcols;
+  CS_DATAFMT descs[numcols], *desc;
+  CS_RETCODE ret;
+  char* err=NULL;
+
+  if (this->busy<=0)
+    error("You must issue a command first\n");
+
+  pop_n_elems(args);
+
+  THREADS_ALLOW();
+  SYB_LOCK(this->lock);
+  
+  for (j=0;j<numcols;j++) {
+    sybdebug((stderr,"Describing column %d\n",j+1));
+    ret=ct_describe(this->cmd,j+1,&descs[j]);
+    show_status(ret);
+    if (FAILED(ret)) {
+      err="Error while fetching descriptions\n";
+      break;
+    }
+  }
+
+  SYB_UNLOCK(this->lock);
+  THREADS_DISALLOW();
+  
+  for(j=0;j<numcols;j++) {
+    nflags=0;
+    desc=&descs[j];
+    push_text("name");
+    push_text(desc->name);
+    sybdebug((stderr,"name is %s\n",desc->name));
+    push_text("type");
+    switch(desc->datatype) {
+      desc_type(CS_ILLEGAL_TYPE,illegal);
+      desc_type(CS_CHAR_TYPE,char);
+      desc_type(CS_BINARY_TYPE,bynary);
+      desc_type(CS_LONGCHAR_TYPE,longchar);
+      desc_type(CS_LONGBINARY_TYPE,longbinary);
+      desc_type(CS_TEXT_TYPE,text);
+      desc_type(CS_IMAGE_TYPE,image);
+      desc_type(CS_TINYINT_TYPE,tinyint);
+      desc_type(CS_SMALLINT_TYPE,smallint);
+      desc_type(CS_INT_TYPE,integer);
+      desc_type(CS_REAL_TYPE,real);
+      desc_type(CS_FLOAT_TYPE,float);
+      desc_type(CS_BIT_TYPE,bit);
+      desc_type(CS_DATETIME_TYPE,datetime);
+      desc_type(CS_DATETIME4_TYPE,datetime4);
+      desc_type(CS_MONEY_TYPE,money);
+      desc_type(CS_MONEY4_TYPE,money4);
+      desc_type(CS_NUMERIC_TYPE,numeric);
+      desc_type(CS_DECIMAL_TYPE,decimal);
+      desc_type(CS_VARCHAR_TYPE,varchar);
+      desc_type(CS_VARBINARY_TYPE,varbinary);
+      desc_type(CS_LONG_TYPE,long);
+      desc_type(CS_SENSITIVITY_TYPE,sensitivity);
+      desc_type(CS_BOUNDARY_TYPE,boundary);
+      desc_type(CS_VOID_TYPE,void);
+      desc_type(CS_USHORT_TYPE,ushort);
+    default:
+      push_text("unknown");
+    }
+    push_text("max_length");
+    push_int(desc->maxlength);
+    sybdebug((stderr,"max_length is %d\n",desc->maxlength));
+
+    push_text("flags");
+    if (!(desc->status & CS_CANBENULL)) {
+      sybdebug((stderr,"Flag: not null\n"));
+      push_text("not_null");
+      nflags++;
+    }
+    if (desc->status & CS_HIDDEN) {
+      sybdebug((stderr,"Flag: hidden\n"));
+      push_text("hidden");
+      nflags++;
+    }
+    if (desc->status & CS_IDENTITY) {
+      sybdebug((stderr,"Flag: identity\n"));
+      push_text("identity");
+      nflags++;
+    }
+    if (desc->status & CS_KEY) {
+      sybdebug((stderr,"Flag: key\n"));
+      push_text("key");
+      nflags++;
+    }
+    if (desc->status & CS_VERSION_KEY) {
+      sybdebug((stderr,"Flag: version_key\n"));
+      push_text("version_key");
+      nflags++;
+    }
+    if (desc->status & CS_TIMESTAMP) {
+      sybdebug((stderr,"Flag: timestamp\n"));
+      push_text("timestamp");
+      nflags++;
+    }
+    if (desc->status & CS_UPDATABLE) {
+      sybdebug((stderr,"Flag: updatable\n"));
+      push_text("updatable");
+      nflags++;
+    }
+    if (nflags) {
+      sybdebug((stderr,"Aggregating flags: %d members\n",nflags));
+      f_aggregate_multiset(nflags);
+    } else {
+      sybdebug((stderr,"No flags"));
+      push_int(0);
+    }
+    
+    f_aggregate_mapping(2*4);
+  }
+
+  sybdebug((stderr,"Aggregating columns: %d members\n",numcols));
+  f_aggregate(numcols);
+
+}
+
+
+/********/
+/* Glue */
+/********/
+void pike_module_exit (void) {
+  SYB_MT_EXIT(mainlock);
+}
+
+void pike_module_init (void) {
+  struct program* sybase_program;
+
+  sybdebug((stderr,"sybase driver release " SYBASE_DRIVER_VERSION "\n"));
+
+  start_new_program();
+  ADD_STORAGE(pike_sybase_connection);
+  set_init_callback(sybase_create);
+  set_exit_callback(sybase_destroy);
+  
+#ifdef ADD_FUNCTION /* pike_0.7 */
+  /* function(void|string,void|string,void|string,void|string,int|void:void) */
+  ADD_FUNCTION("create",f_create,tFunc(tOr(tVoid,tStr) tOr(tVoid,tStr) 
+                                       tOr(tVoid,tStr) tOr(tVoid,tStr)
+                                       tOr(tInt,tVoid), tVoid),
+               0);
+  ADD_FUNCTION("connect",f_connect,tFunc(tOr(tVoid,tStr) tOr(tVoid,tStr) 
+                                         tOr(tVoid,tStr) tOr(tVoid,tStr)
+                                         tOr(tInt,tVoid), tVoid),
+               0);
+  ADD_FUNCTION("error",f_error,tFunc(tVoid,tOr(tVoid,tStr)),
+               OPT_RETURN);
+
+  ADD_FUNCTION("big_query",f_big_query,tFunc(tString,tOr(tInt,tObj)),
+               OPT_RETURN);
+
+  ADD_FUNCTION("fetch_row", f_fetch_row,
+               tFunc(tVoid,tOr(tVoid,tArr(tMix))), 0);
+
+  ADD_FUNCTION("num_fields", f_num_fields,
+               tFunc(tVoid,tInt), 0);
+
+  ADD_FUNCTION("affected_rows", f_affected_rows,
+               tFunc(tVoid,tInt), 0);
+
+  ADD_FUNCTION("fetch_fields", f_fetch_fields,
+               tFunc(tVoid,tArr(tOr(tInt,tMap(tStr,tMix)))), ID_PUBLIC);
+#else
+  add_function("create",f_create,
+               "function(void|string,void|string,void|string,void|string,int|void:void)",
+               0);
+  add_function("connect",f_connect,
+               "function(void|string,void|string,void|string,void|string,int|void:void)",
+               0);
+  add_function("error",f_error,"function(void:void|string)",OPT_RETURN);
+  add_function("big_query",f_big_query,"function(string:void|object)",
+               OPT_RETURN);
+  add_function("fetch_row", f_fetch_row,"function(void:void|array(mixed))",
+               OPT_RETURN);
+  add_function("num_fields", f_num_fields, "function(void:int)",0);
+  add_function("affected_rows",f_affected_rows,"function(void:int)",0);
+  add_function("fetch_fields",f_fetch_fields,
+               "function(void:array(int|mapping(string:mixed)))",
+               0);q
+#endif
+
+  /* TODO */
+  /* int num_rows() */ /* looks like sybase doesn't support this one.. */
+  /* void seek(int skip) */ /*implemented in pike. Inefficient but simple */
+  /* mapping* fetch_fields() */
+
+  sybase_program=end_program();
+  add_program_constant("sybase",sybase_program,0);
+  
+  SYB_MT_INIT(mainlock);
+}
+
+
+#else /* HAVE_SYBASE */
+void pike_module_init (void) {}
+void pike_module_exit (void) {}
+#endif /* HAVE_SYBASE */
diff --git a/src/modules/sybase/sybase.h b/src/modules/sybase/sybase.h
new file mode 100644
index 0000000000..d80e1201bb
--- /dev/null
+++ b/src/modules/sybase/sybase.h
@@ -0,0 +1,44 @@
+/*
+ * Sybase driver for the Pike programming language.
+ *
+ * By Francesco Chemolli <kinkie@roxen.com> 10/12/1999
+ * (C) Roxen IS
+ *
+ */
+
+#include "sybase_config.h"
+
+#ifndef __PIKE_SYBASE_SYBASE_H
+#define __PIKE_SYBASE_SYBASE_H
+#ifdef HAVE_SYBASE
+
+#include "threads.h"
+#include <ctpublic.h>
+
+#define SYBASE_DRIVER_VERSION "9"
+
+typedef struct {
+  CS_CONTEXT *context;
+  CS_CONNECTION *connection;
+  CS_COMMAND *cmd;
+  char busy; /* only one pending command per connection */
+  
+  char had_error; /* non-zero if had error */
+  char error[256]; /* The last error string. The size is determined by the */
+                    /* sybase API */
+
+  char **results;
+  CS_INT *results_lengths;
+  CS_SMALLINT *nulls;
+
+  int numcols; /* the number of columns */
+
+#ifdef _REENTRANT
+  MUTEX_T lock;
+#endif
+
+} pike_sybase_connection;
+
+
+#endif /* HAVE_SYBASE */
+#endif /* __PIKE_SYBASE_SYBASE_H */
-- 
GitLab