/* * create and populate the crosstab tuplestore using the provided source query */ static Tuplestorestate * get_crosstab_tuplestore(char *sql, HTAB *crosstab_hash, TupleDesc tupdesc, MemoryContext per_query_ctx, bool randomAccess) { Tuplestorestate *tupstore; int num_categories = hash_get_num_entries(crosstab_hash); AttInMetadata *attinmeta = TupleDescGetAttInMetadata(tupdesc); char **values; HeapTuple tuple; int ret; int proc; /* initialize our tuplestore (while still in query context!) */ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) /* internal error */ elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret); /* Now retrieve the crosstab source rows */ ret = SPI_execute(sql, true, 0); proc = SPI_processed; /* Check for qualifying tuples */ if ((ret == SPI_OK_SELECT) && (proc > 0)) { SPITupleTable *spi_tuptable = SPI_tuptable; TupleDesc spi_tupdesc = spi_tuptable->tupdesc; int ncols = spi_tupdesc->natts; char *rowid; char *lastrowid = NULL; bool firstpass = true; int i, j; int result_ncols; if (num_categories == 0) { /* no qualifying category tuples */ ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("provided \"categories\" SQL must " \ "return 1 column of at least one row"))); } /* * The provided SQL query must always return at least three columns: * * 1. rowname the label for each row - column 1 in the final result * 2. category the label for each value-column in the final result 3. * value the values used to populate the value-columns * * If there are more than three columns, the last two are taken as * "category" and "values". The first column is taken as "rowname". * Additional columns (2 thru N-2) are assumed the same for the same * "rowname", and are copied into the result tuple from the first time * we encounter a particular rowname. */ if (ncols < 3) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid source data SQL statement"), errdetail("The provided SQL must return 3 " \ " columns; rowid, category, and values."))); result_ncols = (ncols - 2) + num_categories; /* Recheck to make sure we tuple descriptor still looks reasonable */ if (tupdesc->natts != result_ncols) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid return type"), errdetail("Query-specified return " \ "tuple has %d columns but crosstab " \ "returns %d.", tupdesc->natts, result_ncols))); /* allocate space */ values = (char **) palloc(result_ncols * sizeof(char *)); /* and make sure it's clear */ memset(values, '\0', result_ncols * sizeof(char *)); for (i = 0; i < proc; i++) { HeapTuple spi_tuple; crosstab_cat_desc *catdesc; char *catname; /* get the next sql result tuple */ spi_tuple = spi_tuptable->vals[i]; /* get the rowid from the current sql result tuple */ rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1); /* * if we're on a new output row, grab the column values up to * column N-2 now */ if (firstpass || !xstreq(lastrowid, rowid)) { /* * a new row means we need to flush the old one first, unless * we're on the very first row */ if (!firstpass) { /* rowid changed, flush the previous output row */ tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); for (j = 0; j < result_ncols; j++) xpfree(values[j]); } values[0] = rowid; for (j = 1; j < ncols - 2; j++) values[j] = SPI_getvalue(spi_tuple, spi_tupdesc, j + 1); /* we're no longer on the first pass */ firstpass = false; } /* look up the category and fill in the appropriate column */ catname = SPI_getvalue(spi_tuple, spi_tupdesc, ncols - 1); if (catname != NULL) { crosstab_HashTableLookup(crosstab_hash, catname, catdesc); if (catdesc) values[catdesc->attidx + ncols - 2] = SPI_getvalue(spi_tuple, spi_tupdesc, ncols); } xpfree(lastrowid); xpstrdup(lastrowid, rowid); } /* flush the last output row */ tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); } if (SPI_finish() != SPI_OK_FINISH) /* internal error */ elog(ERROR, "get_crosstab_tuplestore: SPI_finish() failed"); tuplestore_donestoring(tupstore); return tupstore; }
Datum crosstab(PG_FUNCTION_ARGS) { char *sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; Tuplestorestate *tupstore; TupleDesc tupdesc; int call_cntr; int max_calls; AttInMetadata *attinmeta; SPITupleTable *spi_tuptable; TupleDesc spi_tupdesc; bool firstpass; char *lastrowid; int i; int num_categories; MemoryContext per_query_ctx; MemoryContext oldcontext; int ret; int proc; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (!(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialize mode required, but it is not " \ "allowed in this context"))); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; /* Connect to SPI manager */ if ((ret = SPI_connect()) < 0) /* internal error */ elog(ERROR, "crosstab: SPI_connect returned %d", ret); /* Retrieve the desired rows */ ret = SPI_execute(sql, true, 0); proc = SPI_processed; /* If no qualifying tuples, fall out early */ if (ret != SPI_OK_SELECT || proc <= 0) { SPI_finish(); rsinfo->isDone = ExprEndResult; PG_RETURN_NULL(); } spi_tuptable = SPI_tuptable; spi_tupdesc = spi_tuptable->tupdesc; /*---------- * The provided SQL query must always return three columns. * * 1. rowname * the label or identifier for each row in the final result * 2. category * the label or identifier for each column in the final result * 3. values * the value for each column in the final result *---------- */ if (spi_tupdesc->natts != 3) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid source data SQL statement"), errdetail("The provided SQL must return 3 " "columns: rowid, category, and values."))); /* get a tuple descriptor for our result type */ switch (get_call_result_type(fcinfo, NULL, &tupdesc)) { case TYPEFUNC_COMPOSITE: /* success */ break; case TYPEFUNC_RECORD: /* failed to determine actual type of RECORD */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); break; default: /* result type isn't composite */ elog(ERROR, "return type must be a row type"); break; } /* * Check that return tupdesc is compatible with the data we got from SPI, * at least based on number and type of attributes */ if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("return and sql tuple descriptions are " \ "incompatible"))); /* * switch to long-lived memory context */ oldcontext = MemoryContextSwitchTo(per_query_ctx); /* make sure we have a persistent copy of the result tupdesc */ tupdesc = CreateTupleDescCopy(tupdesc); /* initialize our tuplestore in long-lived context */ tupstore = tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random, false, work_mem); MemoryContextSwitchTo(oldcontext); /* * Generate attribute metadata needed later to produce tuples from raw C * strings */ attinmeta = TupleDescGetAttInMetadata(tupdesc); /* total number of tuples to be examined */ max_calls = proc; /* the return tuple always must have 1 rowid + num_categories columns */ num_categories = tupdesc->natts - 1; firstpass = true; lastrowid = NULL; for (call_cntr = 0; call_cntr < max_calls; call_cntr++) { bool skip_tuple = false; char **values; /* allocate and zero space */ values = (char **) palloc0((1 + num_categories) * sizeof(char *)); /* * now loop through the sql results and assign each value in sequence * to the next category */ for (i = 0; i < num_categories; i++) { HeapTuple spi_tuple; char *rowid; /* see if we've gone too far already */ if (call_cntr >= max_calls) break; /* get the next sql result tuple */ spi_tuple = spi_tuptable->vals[call_cntr]; /* get the rowid from the current sql result tuple */ rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1); /* * If this is the first pass through the values for this rowid, * set the first column to rowid */ if (i == 0) { xpstrdup(values[0], rowid); /* * Check to see if the rowid is the same as that of the last * tuple sent -- if so, skip this tuple entirely */ if (!firstpass && xstreq(lastrowid, rowid)) { xpfree(rowid); skip_tuple = true; break; } } /* * If rowid hasn't changed on us, continue building the output * tuple. */ if (xstreq(rowid, values[0])) { /* * Get the next category item value, which is always attribute * number three. * * Be careful to assign the value to the array index based on * which category we are presently processing. */ values[1 + i] = SPI_getvalue(spi_tuple, spi_tupdesc, 3); /* * increment the counter since we consume a row for each * category, but not for last pass because the outer loop will * do that for us */ if (i < (num_categories - 1)) call_cntr++; xpfree(rowid); } else { /* * We'll fill in NULLs for the missing values, but we need to * decrement the counter since this sql result row doesn't * belong to the current output tuple. */ call_cntr--; xpfree(rowid); break; } } if (!skip_tuple) { HeapTuple tuple; /* build the tuple and store it */ tuple = BuildTupleFromCStrings(attinmeta, values); tuplestore_puttuple(tupstore, tuple); heap_freetuple(tuple); } /* Remember current rowid */ xpfree(lastrowid); xpstrdup(lastrowid, values[0]); firstpass = false; /* Clean up */ for (i = 0; i < num_categories + 1; i++) if (values[i] != NULL) pfree(values[i]); pfree(values); } /* let the caller know we're sending back a tuplestore */ rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; /* release SPI related resources (and return to caller's context) */ SPI_finish(); return (Datum) 0; }
/******************************************************************** * * fork/exec a child pdm after setting up a message pipe. */ void mgr_launch_pdm( XpPdmServiceRec *rec ) { int i; struct sigaction svec; char buf[1024]; int original_umask; char *existing_name; FILE *existing_file; Xauth *entry; char *envstr; /* * Setup message pipe. */ if ( pipe(rec->message_pipe) == -1 ) { rec->pdm_exec_errorcode = g.pdm_start_error; sprintf( buf, PDMD_MSG_8, g.prog_name ); rec->pdm_exec_errormessage = xpstrdup( buf ); return; } rec->message_xtid = XtAppAddInput( g.context, rec->message_pipe[0], (XtPointer) XtInputReadMask, message_pipe_handler, (XtPointer) NULL ); /* * See if a cookie file is needed. */ if (rec->cookie_cnt) { /* * Create new .Xauthority file. */ original_umask = umask (0077); /* disallow non-owner access */ tmpnam( rec->auth_filename ); rec->auth_file = fopen( rec->auth_filename, "w" ); if (rec->auth_file) { /* * Copy existing .Xauthority entries. */ existing_name = XauFileName (); if (existing_name) { if (access (existing_name, R_OK) == 0) { /* checks REAL id */ existing_file = fopen (existing_name, "r"); if (existing_file) { for (;;) { entry = XauReadAuth (existing_file); if (!entry) break; XauWriteAuth( rec->auth_file, entry ); XauDisposeAuth (entry); } fclose (existing_file); } } } /* * Merge in cookies recently sent. */ for ( i = 0; i < rec->cookie_cnt; i++ ) { XauWriteAuth( rec->auth_file, rec->cookies[i] ); } fclose( rec->auth_file ); } original_umask = umask (original_umask); } rec->pid = fork(); if ( rec->pid < 0 ) { rec->pdm_exec_errorcode = g.pdm_start_error; sprintf( buf, PDMD_MSG_9, g.prog_name ); rec->pdm_exec_errormessage = xpstrdup( buf ); return; } else if ( rec->pid == 0) { /* * Child process. */ /* * Hook stderr back to parent via message pipe. */ dup2(rec->message_pipe[1], 2); close(rec->message_pipe[0]); /* * The child should have default behavior for all signals. */ sigemptyset(&svec.sa_mask); svec.sa_flags = 0; svec.sa_handler = SIG_DFL; (void) sigaction(SIGCHLD, &svec, (struct sigaction *) NULL); for (i=3; i < FOPEN_MAX; i++) { if ((i != rec->message_pipe[1]) && (rec->auth_file && (i != fileno(rec->auth_file)))) { (void) fcntl (i, F_SETFD, 1); } } /* * Set the new locale for the child. * * note: the locale hint will be of the form: * * name_spec[;registry_spec[;ver_spec[;encoding_spec]]] * * for now, just pull out the name_spec (e.g. 'C') * and use it. With a little work, a more complex * syntax could be understood and the appropriate * actions taken here rather than just wedging * name_spec into setlocale() and hoping. */ if ( !(rec->locale_hint) ) { /* * Leave current locale alone. */ } else if ( strcmp( rec->locale_hint, "" ) ) { /* * Leave current locale alone. Note that "" into * setlocale says to go with default vs leave it alone. */ } else { char *tptr1, *tptr2; tptr1 = xpstrdup( rec->locale_hint ); tptr2 = strchr( tptr1, ';' ); if (tptr2) *tptr2 = '\0'; setlocale( LC_ALL, tptr1 ); XFree( tptr1 ); } /* * Set XAUTHORITY env var if needed. */ if ((rec->cookie_cnt) && (rec->auth_filename) && (rec->auth_file)) { envstr = Xmalloc( strlen(rec->auth_filename) + 12 ); sprintf( envstr, "XAUTHORITY=%s", rec->auth_filename ); putenv( envstr ); } /* * Start the child for real. */ (void) execvp(rec->pdm_exec_argvs[0], rec->pdm_exec_argvs); (void) fprintf (stderr, PDMD_MSG_10, g.prog_name, rec->pdm_exec_argvs[0]); /* * tomg - need to deal with failed child start. */ exit(PDM_EXIT_ERROR); } else { /* * Parent process. */ /* * Close the write end of the pipe - only the child needs it. */ close(rec->message_pipe[1]); rec->message_pipe[1] = -1; } }
/******************************************************************** * * Figure out which pdm executable to later fork/exec. */ void mgr_fetch_pdm( XpPdmServiceRec *rec ) { char tstr[1024], *tptr1, *tptr2, *tptr3; int firstTime; long now; Display *tdpy; int lxerrno; if ( g.override_pdm ) { /* * Override all defaults and other possible settings. */ tptr1 = xpstrdup(g.override_pdm); } else { /* * See if the print context says which pdm to run. */ g.xerrno = 0; /* Error Handler */ lxerrno = 0; /* XIO Error Handler */ if ( setjmp( xio_quickie_jmp_buf ) == 0 ) { XSetIOErrorHandler( xio_quickie_handler ); #if 0 && defined(PRINTING_SUPPORTED) if ( rec->seldpy_as_printdpy ) { tptr1 = XpGetOneAttribute( rec->selection_display, rec->print_context, XPPrinterAttr, "dt-pdm-command" ); } else { tdpy = XOpenDisplay( rec->print_display_str ); if (tdpy) { tptr1 = XpGetOneAttribute( tdpy, rec->print_context, XPPrinterAttr, "dt-pdm-command" ); XCloseDisplay( tdpy ); } } #endif /* PRINTING_SUPPORTED */ XSetIOErrorHandler( NULL ); } else { lxerrno = 1; XSetIOErrorHandler( NULL ); } /* * See if we got a useful pdm exec string. Use * default if not. */ if ( g.xerrno || lxerrno ) { rec->pdm_exec_errorcode = g.pdm_start_error; return; } else if (!tptr1) { tptr1 = xpstrdup(g.default_pdm); } else if (!tptr1[0]) { tptr1 = xpstrdup(g.default_pdm); } } /* * Convert pdm-command into argv[] style array. * * Note: this parsing code does NOT respect shell * quotes and other items. --tomg */ rec->pdm_exec_argvs = (char **) NULL; tptr2 = tptr1; /* retain orig pointer for freeing */ firstTime = 1; while (1) { if (firstTime) { tptr3 = xpstrtok( tptr2, " \n\t" ); firstTime = 0; if (!tptr3) { /* * There were NO useful tokens to begin with, so * we'll have to fall back on the default. */ xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup( g.default_pdm )); break; } } else { tptr3 = xpstrtok( (char *) NULL, " \n\t" ); } if (tptr3) { xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup( tptr3 ) ); } else { break; } } Xfree(tptr1); /* * Add standard command line parameters. */ xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-display") ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(rec->video_display_str) ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-window") ); sprintf( tstr, "0x%lx", rec->video_window ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(tstr) ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-pdisplay") ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(rec->print_display_str) ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-pwindow") ); sprintf( tstr, "0x%lx", rec->print_window ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(tstr) ); #if 0 && defined(PRINTING_SUPPORTED) xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup("-pcontext") ); sprintf( tstr, "0x%lx", rec->print_context ); xp_add_argv( &(rec->pdm_exec_argvs), xpstrdup(tstr) ); #endif /* PRINTING_SUPPORTED */ }
/******************************************************************** * * Setup a child service record per the selection request. */ void mgr_initialize( XEvent *report, XpPdmServiceRec *rec ) { Display *testdpy; char buf[1024]; Display *selection_display; Window requestor; Atom prop_atom; unsigned long tafter; XTextProperty text_prop; char **list; int list_cnt; /* * Grab the PDM_CLIENT_PROP from which all the juicy * information is retrieved. */ selection_display = report->xselectionrequest.display; requestor = report->xselectionrequest.requestor; prop_atom = report->xselectionrequest.property; if ( XGetWindowProperty( selection_display, requestor, prop_atom, 0, 100000, True, AnyPropertyType, &text_prop.encoding, &text_prop.format, &text_prop.nitems, &tafter, &text_prop.value ) != Success ) { /* * Error */ rec->pdm_exec_errorcode = g.pdm_start_error; sprintf( buf, PDMD_MSG_5, g.prog_name ); rec->pdm_exec_errormessage = xpstrdup( buf ); return; } if ( text_prop.format != 8 ) { /* * Error */ rec->pdm_exec_errorcode = g.pdm_start_error; sprintf( buf, PDMD_MSG_6, g.prog_name ); rec->pdm_exec_errormessage = xpstrdup( buf ); return; } if ( XmbTextPropertyToTextList( selection_display, &text_prop, &list, &list_cnt ) < 0 ) { /* * Error */ rec->pdm_exec_errorcode = g.pdm_start_error; sprintf( buf, PDMD_MSG_7, g.prog_name ); rec->pdm_exec_errormessage = xpstrdup( buf ); return; } /* * Fill in the PDM_MANAGER portion of the client record. */ rec->video_display_str = xpstrdup( list[0] ); rec->video_window = strtol(list[1], (char **)NULL, 16); rec->print_display_str = xpstrdup( list[2] ); rec->print_window = strtol(list[3], (char **)NULL, 16); #if 0 && defined(PRINTING_SUPPORTED) rec->print_context = strtol(list[4], (char **)NULL, 16); #endif /* PRINTING_SUPPORTED */ rec->locale_hint = xpstrdup( list[5] ); XFreeStringList( list ); rec->selection_display = selection_display; rec->requestor = requestor; rec->prop_atom = prop_atom; rec->selection = report->xselectionrequest.selection; rec->time = report->xselectionrequest.time; rec->mgr_flag = True; /* mgr portion of rec now valid */ /* * Optimization. The only live display connection, for which we * need to trap XIO errors, is "selection display". For the * "video" and "print" displays, we have the display strings and * can establish connections as we need them. Since they are rarely * used, and opening them up here would create XIO liability problems * and a startup performance hit, we won't establish connections now. * * One optimization however is to see if the "print" display would * just happen to be the same at the "selection display" currently * open. */ if ( !strcmp( XDisplayString(rec->selection_display), rec->print_display_str ) ) { rec->seldpy_as_printdpy = True; } else { rec->seldpy_as_printdpy = False; #ifdef OPTIONAL_PXAUTH_PRETEST /* * Verify connectability to the Print Server. * * Note: once beyond the selection phase, all communication * will be by way of the Print Server. If we cannot * connect later, then we will have no way to deliver * EXIT_PXAUTH, EXIT_VXAUTH, EXIT_ERROR, EXIT_OK or * EXIT_CANCEL. Real bad news! * * It is better to discover now that we don't have * connection authorization for the print display since * we can still let the user know of PXAUTH problems * via the selection display currently open. * * Unfortunately, this pre-test is a performance hit in the * startup phase. */ if ( ! (testdpy = XOpenDisplay(rec->print_display_str)) ) { rec->pdm_exec_errorcode = g.pdm_start_pxauth; return; } XCloseDisplay( testdpy ); #endif /* OPTIONAL_PXAUTH_PRETEST */ } #ifdef OPTIONAL_VXAUTH_PRETEST /* * Verify connectability to the Video Server. * * It is better to discover now that we don't have * connection authorization for the video display since * we can still let the user know of VXAUTH problems * via the selection display currently open. * * Unfortunately, this pre-test is a performance hit in the * startup phase. */ if ( ! (testdpy = XOpenDisplay(rec->video_display_str)) ) { rec->pdm_exec_errorcode = g.pdm_start_vxauth; return; } XCloseDisplay( testdpy ); #endif /* OPTIONAL_VXAUTH_PRETEST */ }