Added support for File objects for PQprint()/PGResult.print() and
authorMatthew Mondor <mmondor@pulsar-zone.net>
Tue, 3 Oct 2006 19:12:01 +0000 (19:12 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Tue, 3 Oct 2006 19:12:01 +0000 (19:12 +0000)
PQtrace()/PGConn.trace()

mmsoftware/js/classes/js_pgsql.c

index 12769bc..3b6cd5e 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: js_pgsql.c,v 1.2 2006/08/05 20:26:25 mmondor Exp $ */
+/* $Id: js_pgsql.c,v 1.3 2006/10/03 19:12:01 mmondor Exp $ */
 
 /*
  * Copyright (c) 2006, Matthew Mondor
  *   generated objects instances's private data (using structures as necessary
  *   instead of simply wrapping around the native object's pointer
  *   (actually PGconn object).
- * - 28.10. Notice Processing
+ * - 28.10. Notice Processing XXX
  *   Either place one(s) that use syslog(3) transparently, or somehow allow
  *   the user to set a custom handler
  * - Large objects API
  * - Compare to PHP library to verify if missing any nice functions ideas
  * - See what to do about the following functions:
  *   - PQgetssl() (returns an SSL object!)
- *   - PQprint() (writes to a supplied FILE *)
+ *   - PQprint() (writes to a supplied FILE *) XXX
  */
 
 
@@ -42,6 +42,7 @@
 #include <libpq-fe.h>
 
 #include <js_pgsql.h>
+#include <js_file.h>
 
 
 
@@ -83,6 +84,7 @@ static int    param_grow(int);
 
 static JSBool  pg_constructor(JSContext *, JSObject *, uintN, jsval *,
                    jsval *);
+static void    pg_finalize(JSContext *, JSObject *);
 
 static JSBool  pg_sm_PQconndefaults(JSContext *, JSObject *, uintN, jsval *,
                    jsval *);
@@ -244,6 +246,8 @@ static JSBool       pgresult_m_PQcmdTuples(JSContext *, JSObject *, uintN, jsval *,
                    jsval *);
 static JSBool  pgresult_m_PQoidValue(JSContext *, JSObject *, uintN, jsval *,
                    jsval *);
+static JSBool  pgresult_m_PQprint(JSContext *, JSObject *, uintN, jsval *,
+                   jsval *);
 
 static JSObject        *js_InitPGcancelClass(JSContext *, JSObject *);
 static JSBool  pgcancel_constructor(JSContext *, JSObject *, uintN, jsval *,
@@ -255,6 +259,10 @@ static JSBool      pgcancel_m_PQfreeCancel(JSContext *, JSObject *, uintN,
 static JSBool  pgcancel_m_PQcancel(JSContext *, JSObject *, uintN, jsval *,
                    jsval *);
 
+static JSObject        *js_InitPGPrintOptClass(JSContext *, JSObject *);
+static JSBool  pgprintopt_constructor(JSContext *, JSObject *, uintN, jsval *,
+                   jsval *);
+
 
 
 /*
@@ -264,6 +272,9 @@ static JSBool       pgcancel_m_PQcancel(JSContext *, JSObject *, uintN, jsval *,
 /*
  * General purpose string buffer (note that this is not thread-safe).
  * Allows to optimize functions such as PQescapeStringConn().
+ * XXX To be thread-safe, these would need to be tied to objects rather than
+ * being shared globals.  This obviously would require more memory and add
+ * additional object creation overhead.
  */
 static char    *buffer = NULL;
 static size_t  buffer_size = 0;
@@ -273,11 +284,19 @@ static int        *param_lengths = NULL;
 static int     *param_formats = NULL;
 static int     param_entries = 0;
 
+/*
+ * This object is crated by js_InitPGClass() and rooted, so that we may easily
+ * root objects by adding them as properties into it.  To be thread-safe, a
+ * critical section or mutex would be needed when accessing this object.
+ */
+static JSObject        *root_object = NULL;
+
+
 /* PG class */
 static JSClass pg_class = {
        "PG", 0, JS_PropertyStub, JS_PropertyStub,
        JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub,
-       JS_ConvertStub, JS_FinalizeStub
+       JS_ConvertStub, pg_finalize
 };
 
 /* Provided static methods */
@@ -392,7 +411,7 @@ static JSFunctionSpec pgconn_methods[] = {
        { "isNonBlocking", pgconn_m_PQisnonblocking, 0, 0, 0 },
        { "flush", pgconn_m_PQflush, 0, 0, 0 },
        { "setErrorVerbosity", pgconn_m_PQsetErrorVerbosity, 1, 0, 0 },
-       { "trace", pgconn_m_PQtrace, 0, 0, 0 },
+       { "trace", pgconn_m_PQtrace, 1, 0, 0 },
        { "untrace", pgconn_m_PQuntrace, 0, 0, 0 },
        { "putCopyData", pgconn_m_PQputCopyData, 1, 0, 0 },
        { "putCopyEnd", pgconn_m_PQputCopyEnd, 1, 0, 0 },
@@ -431,6 +450,7 @@ static JSFunctionSpec pgresult_methods[] = {
        { "cmdStatus", pgresult_m_PQcmdStatus, 0, 0, 0 },
        { "cmdTuples", pgresult_m_PQcmdTuples, 0, 0, 0 },
        { "oidValue", pgresult_m_PQoidValue, 0, 0, 0 },
+       { "print", pgresult_m_PQprint, 2, 0, 0 },
        { NULL, NULL, 0, 0, 0 }
 };
 
@@ -450,6 +470,14 @@ static JSFunctionSpec pgcancel_methods[] = {
 };
 
 
+/* PGPrintOpt class */
+static JSClass pgprintopt_class = {
+       "PGPrintOpt", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+       JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
+       JS_FinalizeStub
+};
+
+
 
 static int
 buffer_grow(size_t required)
@@ -477,7 +505,8 @@ param_grow(int required)
        int     new;
        void    *types, *values, *lengths, *formats;
 
-       if (required <= param_entries)
+       /* Account space for NULL */
+       if (++required <= param_entries)
                return 0;
 
        for (new = param_entries; new < required; new *= 2) ;
@@ -529,21 +558,26 @@ js_InitPGClass(JSContext *cx, JSObject *obj)
                }
        }
 
-       /* Initialize PGconn class since we'll need to instanciate it from C */
+       /*
+        * Initialize object/classes which we'll need to instanciate objects
+        * from
+        */
        if (js_InitPGconnClass(cx, obj) == NULL) {
                (void) fprintf(stderr, "PG: InitPGconnClass()\n");
                goto err;
        }
-       /* Same for PGresult class */
        if (js_InitPGresultClass(cx, obj) == NULL) {
                (void) fprintf(stderr, "PG: InitPGresultClass()\n");
                goto err;
        }
-       /* And PGcancel class */
        if (js_InitPGcancelClass(cx, obj) == NULL) {
                (void) fprintf(stderr, "PG: InitPGcancelClass()\n");
                goto err;
        }
+       if (js_InitPGPrintOptClass(cx, obj) == NULL) {
+               (void) fprintf(stderr, "PG: InitPGPrintOptClass()\n");
+               goto err;
+       }
 
        /*
         * Note that the following buffers, although allowing optimizations,
@@ -571,6 +605,16 @@ js_InitPGClass(JSContext *cx, JSObject *obj)
        }
        param_entries = 16;
 
+       /* Create our rooted object */
+       if ((root_object = JS_NewObject(cx, NULL, NULL, NULL)) == NULL) {
+               (void) fprintf(stderr, "PG: JS_NewObject(root_object)\n");
+               goto err;
+       }
+       if (!JS_AddRoot(cx, &root_object)) { /* Pointer to pointer */
+               (void) fprintf(stderr, "PG: JS_AddRoot(root_object)\n");
+               goto err;
+       }
+
        return proto;
 
 err:
@@ -599,6 +643,14 @@ pg_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
        return JS_FALSE;
 }
 
+static void
+pg_finalize(JSContext *cx, JSObject *obj)
+{
+
+       if (root_object != NULL)
+               (void) JS_RemoveRoot(cx, &root_object); /* Ptr to ptr */
+}
+
 
 /*
  * PG object static methods
@@ -2562,43 +2614,72 @@ err:
        return JS_FALSE;
 }
 
+/*
+ * Requires a File object, which we root to ensure that it doesn't get
+ * unexpectedly finalized.  We unroot any previously provided File object when
+ * supplied with a new one.
+ */
 static JSBool
 pgconn_m_PQtrace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
     jsval *rval)
 {
        PGconn          *pgc;
+       FILE            *fh;
 
-       if (argc != 0) {
-               QUEUE_EXCEPTION("Function allows no arguments");
-               *rval = OBJECT_TO_JSVAL(NULL);
+       *rval = OBJECT_TO_JSVAL(NULL);
+
+       if (argc != 1) {
+               QUEUE_EXCEPTION("Wrong number of arguments");
+               return JS_FALSE;
+       }
+       if ((fh = file_fh(cx, argv[0])) == NULL) {
+               QUEUE_EXCEPTION("Argument 1 not a File");
                return JS_FALSE;
        }
 
        pgc = JS_GetInstancePrivate(cx, obj, &pgconn_class, NULL);
        assert(pgc != NULL);
 
-       *rval = OBJECT_TO_JSVAL(NULL);
-       PQtrace(pgc, stderr);
+       /*
+        * Unroot previously rooted File object, if any, then root newly
+        * provided File object.
+        */
+       (void) JS_DeleteProperty(cx, root_object, "trace");
+       if (!JS_DefineProperty(cx, root_object, "trace", argv[0],
+           NULL, NULL, 0)) {
+               QUEUE_EXCEPTION("Internal error!");
+               return JS_FALSE;
+       }
+
+       PQtrace(pgc, fh);
 
        return JS_TRUE;
 }
 
+/*
+ * We unroot any previously rooted File object supplied to trace().
+ */
 static JSBool
 pgconn_m_PQuntrace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
     jsval *rval)
 {
        PGconn          *pgc;
 
+       *rval = OBJECT_TO_JSVAL(NULL);
+
        if (argc != 0) {
                QUEUE_EXCEPTION("Function allows no arguments");
-               *rval = OBJECT_TO_JSVAL(NULL);
                return JS_FALSE;
        }
 
        pgc = JS_GetInstancePrivate(cx, obj, &pgconn_class, NULL);
        assert(pgc != NULL);
 
-       *rval = OBJECT_TO_JSVAL(NULL);
+       /*
+        * Unroot previously rooted File object
+        */
+       (void) JS_DeleteProperty(cx, root_object, "trace");
+
        PQuntrace(pgc);
 
        return JS_TRUE;
@@ -3392,6 +3473,181 @@ err:
        return JS_FALSE;
 }
 
+/*
+ * XXX Not reentrant as it uses a global buffer.
+ * For simplicity, we currently use param_grow() and param_values.
+ */
+static JSBool
+pgresult_m_PQprint(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+    jsval *rval)
+{
+       PGresult        *pgr;
+       FILE            *fh;
+       JSObject        *o;
+       PQprintOpt      opt;
+       jsval           v;
+       JSIdArray       *a = NULL;
+
+       *rval = OBJECT_TO_JSVAL(NULL);
+
+       if (argc != 2) {
+               QUEUE_EXCEPTION("Wrong number of arguments");
+               return JS_FALSE;
+       }
+       if ((fh = file_fh(cx, argv[0])) == NULL) {
+               QUEUE_EXCEPTION("Argument 1 not a File");
+               return JS_FALSE;
+       }
+       if (!JSVAL_IS_OBJECT(argv[1])) {
+               QUEUE_EXCEPTION("Argument 2 not a PGPrintOpt");
+               return JS_FALSE;
+       }
+       o = JSVAL_TO_OBJECT(argv[1]);
+       if (!JS_InstanceOf(cx, o, &pgprintopt_class, NULL)) {
+               QUEUE_EXCEPTION("Argument 2 not a PGPrintOpt");
+               return JS_FALSE;
+       }
+
+       pgr = JS_GetInstancePrivate(cx, obj, &pgresult_class, NULL);
+       assert(pgr != NULL);
+
+       /* Fill opt according to supplied object properties */
+
+       /* Boolean properties */
+       if (!JS_GetProperty(cx, o, "header", &v) || !JSVAL_IS_BOOLEAN(v)) {
+               QUEUE_EXCEPTION("property 'header' not a Boolean");
+               return JS_FALSE;
+       }
+       opt.header = JSVAL_TO_BOOLEAN(v);
+
+       if (!JS_GetProperty(cx, o, "align", &v) || !JSVAL_IS_BOOLEAN(v)) {
+               QUEUE_EXCEPTION("property 'align' not a Boolean");
+               return JS_FALSE;
+       }
+       opt.align = JSVAL_TO_BOOLEAN(v);
+
+       if (!JS_GetProperty(cx, o, "standard", &v) || !JSVAL_IS_BOOLEAN(v)) {
+               QUEUE_EXCEPTION("property 'standard' not a Boolean");
+               return JS_FALSE;
+       }
+       opt.standard = JSVAL_TO_BOOLEAN(v);
+
+       if (!JS_GetProperty(cx, o, "html3", &v) || !JSVAL_IS_BOOLEAN(v)) {
+               QUEUE_EXCEPTION("property 'html3' not a Boolean");
+               return JS_FALSE;
+       }
+       opt.html3 = JSVAL_TO_BOOLEAN(v);
+
+       if (!JS_GetProperty(cx, o, "expanded", &v) || !JSVAL_IS_BOOLEAN(v)) {
+               QUEUE_EXCEPTION("property 'expanded' not a Boolean");
+               return JS_FALSE;
+       }
+       opt.expanded = JSVAL_TO_BOOLEAN(v);
+
+       if (!JS_GetProperty(cx, o, "pager", &v) || !JSVAL_IS_BOOLEAN(v)) {
+               QUEUE_EXCEPTION("property 'pager' not a Boolean");
+               return JS_FALSE;
+       }
+       opt.pager = JSVAL_TO_BOOLEAN(v);
+
+       /* String properties */
+       if (!JS_GetProperty(cx, o, "fieldSep", &v) || !JSVAL_IS_STRING(v)) {
+               QUEUE_EXCEPTION("property 'fieldSep' not a String");
+               return JS_FALSE;
+       }
+       if ((opt.fieldSep = JS_GetStringBytes(JSVAL_TO_STRING(v))) == NULL) {
+               QUEUE_EXCEPTION("Internal error!");
+               return JS_FALSE;
+       }
+
+       if (!JS_GetProperty(cx, o, "tableOpt", &v) || !JSVAL_IS_STRING(v)) {
+               QUEUE_EXCEPTION("property 'tableOpt' not a String");
+               return JS_FALSE;
+       }
+       if ((opt.tableOpt = JS_GetStringBytes(JSVAL_TO_STRING(v))) == NULL) {
+               QUEUE_EXCEPTION("Internal error!");
+               return JS_FALSE;
+       }
+
+       if (!JS_GetProperty(cx, o, "caption", &v) || !JSVAL_IS_STRING(v)) {
+               QUEUE_EXCEPTION("property 'caption' not a String");
+               return JS_FALSE;
+       }
+       if ((opt.caption = JS_GetStringBytes(JSVAL_TO_STRING(v))) == NULL) {
+               QUEUE_EXCEPTION("Internal error!");
+               return JS_FALSE;
+       }
+
+       /* String Array property */
+       if (!JS_GetProperty(cx, o, "fieldName", &v) || !JSVAL_IS_OBJECT(v)) {
+               QUEUE_EXCEPTION("property 'fieldName' not an Array");
+               return JS_FALSE;
+       }
+       o = JSVAL_TO_OBJECT(v);
+       if (!JS_IsArrayObject(cx, o)) {
+               QUEUE_EXCEPTION("property 'fieldName' not an Array");
+               return JS_FALSE;
+       }
+       {
+               jsint len;
+
+               if (!JS_GetArrayLength(cx, o, &len)) {
+                       QUEUE_EXCEPTION("Internal error!");
+                       return JS_FALSE;
+               }
+               if (len == 0)
+                       opt.fieldName = NULL;
+               else {
+                       if (param_grow(len) == -1) {
+                               QUEUE_EXCEPTION("Out of memory!");
+                               return JS_FALSE;
+                       }
+                       opt.fieldName = param_values;
+               }
+       }
+       if (opt.fieldName != NULL) {
+               jsval   id;
+               int     i, i2;
+               char    str[256];
+
+               if ((a = JS_Enumerate(cx, o)) == NULL) {
+                       QUEUE_EXCEPTION("Internal error!");
+                       return JS_FALSE;
+               }
+               for (i2 = i = 0; i < a->length; i++) {
+                       JS_IdToValue(cx, a->vector[i], &id);
+                       if (JS_LookupElement(cx, o, JSVAL_TO_INT(id), &v) &&
+                           !JSVAL_IS_VOID(v)) {
+                               if (JSVAL_IS_NULL(v) || !JSVAL_IS_STRING(v)) {
+                                       (void) snprintf(str, 255,
+                                           "fieldName Array element %d "
+                                           "not a String", i2);
+                                       QUEUE_EXCEPTION(str);
+                                       goto err;
+                               }
+                               if ((opt.fieldName[i2++] = JS_GetStringBytes(
+                                   JSVAL_TO_STRING(v))) == NULL) {
+                                       QUEUE_EXCEPTION("Internal error!");
+                                       goto err;
+                               }
+                       }
+               }
+               JS_DestroyIdArray(cx, a);
+               a = NULL;
+               opt.fieldName[i2] = NULL;
+       }
+
+       /* Finally call our function */
+       PQprint(fh, pgr, &opt);
+
+       return JS_TRUE;
+
+err:
+       if (a != NULL)
+               JS_DestroyIdArray(cx, a);
+
+       return JS_FALSE;
+}
 
 
 /*
@@ -3491,3 +3747,78 @@ err:
 
        return JS_FALSE;
 }
+
+
+/*
+ * PGPrintOpt object control
+ */
+static JSObject *
+js_InitPGPrintOptClass(JSContext *cx, JSObject *obj)
+{
+       JSObject        *proto;
+       JSString        *str;
+       JSObject        *array;
+
+       if ((proto = JS_InitClass(cx, obj, NULL, &pgprintopt_class,
+           pgprintopt_constructor, 0, NULL, NULL, NULL, NULL)) == NULL)
+               return NULL;
+
+       /*
+        * Add default properties to this object's prototype
+        */
+       if (!JS_DefineProperty(cx, proto, "header", BOOLEAN_TO_JSVAL(JS_TRUE),
+               NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) ||
+           !JS_DefineProperty(cx, proto, "align", BOOLEAN_TO_JSVAL(JS_TRUE),
+               NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) ||
+           !JS_DefineProperty(cx, proto, "standard",
+               BOOLEAN_TO_JSVAL(JS_FALSE), NULL, NULL,
+               JSPROP_ENUMERATE | JSPROP_PERMANENT) ||
+           !JS_DefineProperty(cx, proto, "html3", BOOLEAN_TO_JSVAL(JS_FALSE),
+               NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT) ||
+           !JS_DefineProperty(cx, proto, "expanded",
+               BOOLEAN_TO_JSVAL(JS_FALSE), NULL, NULL,
+               JSPROP_ENUMERATE | JSPROP_PERMANENT) ||
+           !JS_DefineProperty(cx, proto, "pager", BOOLEAN_TO_JSVAL(JS_FALSE),
+               NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT))
+               return NULL;
+
+       if ((str = JS_NewStringCopyZ(cx, "|")) == NULL)
+               return NULL;
+       if (!JS_DefineProperty(cx, proto, "fieldSep", STRING_TO_JSVAL(str),
+           NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT))
+               return NULL;
+
+       if ((str = JS_NewStringCopyZ(cx, "")) == NULL)
+               return NULL;
+       if (!JS_DefineProperty(cx, proto, "tableOpt", STRING_TO_JSVAL(str),
+           NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT))
+               return NULL;
+
+       if ((str = JS_NewStringCopyZ(cx, "")) == NULL)
+               return NULL;
+       if (!JS_DefineProperty(cx, proto, "caption", STRING_TO_JSVAL(str),
+           NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT))
+               return NULL;
+
+       if ((array = JS_NewArrayObject(cx, 0, NULL)) == NULL)
+               return NULL;
+       if (!JS_DefineProperty(cx, proto, "fieldName", OBJECT_TO_JSVAL(array),
+           NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT))
+               return NULL;
+
+       return proto;
+}
+
+static JSBool
+pgprintopt_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+    jsval *rval)
+{
+
+       if (!JS_IsConstructing(cx)) {
+               QUEUE_EXCEPTION("PGPrintOpt constuctor called as a function");
+               *rval = OBJECT_TO_JSVAL(NULL);
+               return JS_FALSE;
+       }
+
+       return JS_TRUE;
+}