Ejemplo n.º 1
0
void CSyncSource::processServerCmd_Ver3(const String& strCmd, const String& strObject, const String& strAttriba, const String& strValuea)//throws Exception
{
    CAttrValue oAttrValue(strAttriba,strValuea);

    if ( strCmd.compare("insert") == 0 )
    {
        if ( !processBlob(strCmd,strObject,oAttrValue) )
            return;

        DBResult(resInsert, getDB().executeSQLReportNonUnique("INSERT INTO object_values \
            (attrib, source_id, object, value) VALUES(?,?,?,?)", 
            oAttrValue.m_strAttrib, getID(), strObject, oAttrValue.m_strValue ) );
        if ( resInsert.isNonUnique() )
        {
            getDB().executeSQL("UPDATE object_values \
                SET value=? WHERE object=? and attrib=? and source_id=?", 
                oAttrValue.m_strValue, strObject, oAttrValue.m_strAttrib, getID() );

            if ( getSyncType().compare("none") != 0 )
            {
                // oo conflicts
                getDB().executeSQL("UPDATE changed_values SET sent=4 where object=? and attrib=? and source_id=? and sent>1", 
                    strObject, oAttrValue.m_strAttrib, getID() );
                //
            }
        }

        if ( getSyncType().compare("none") != 0 )
           getNotify().onObjectChanged(getID(),strObject, CSyncNotify::enUpdate);

        m_nInserted++;
    }else if (strCmd.compare("delete") == 0)
/*
 * getCopyDataMessage - fetch next CopyData message, process async messages
 *
 * Returns length word of CopyData message (> 0), or 0 if no complete
 * message available, -1 if end of copy, -2 if error.
 */
static int
getCopyDataMessage(PGconn *conn)
{
	char		id;
	int			msgLength;
	int			avail;

	for (;;)
	{
		/*
		 * Do we have the next input message?  To make life simpler for async
		 * callers, we keep returning 0 until the next message is fully
		 * available, even if it is not Copy Data.
		 */
		conn->inCursor = conn->inStart;
		if (pqGetc(&id, conn))
			return 0;
		if (pqGetInt(&msgLength, 4, conn))
			return 0;
		if (msgLength < 4)
		{
			handleSyncLoss(conn, id, msgLength);
			return -2;
		}
		avail = conn->inEnd - conn->inCursor;
		if (avail < msgLength - 4)
			return 0;

		/*
		 * If it's a legitimate async message type, process it.  (NOTIFY
		 * messages are not currently possible here, but we handle them for
		 * completeness.  NOTICE is definitely possible, and ParameterStatus
		 * could probably be made to happen.)  Otherwise, if it's anything
		 * except Copy Data, report end-of-copy.
		 */
		switch (id)
		{
			case 'A':			/* NOTIFY */
				if (getNotify(conn))
					return 0;
				break;
			case 'N':			/* NOTICE */
				if (pqGetErrorNotice3(conn, false))
					return 0;
				break;
			case 'S':			/* ParameterStatus */
				if (getParameterStatus(conn))
					return 0;
				break;
			case 'd':			/* Copy Data, pass it back to caller */
				return msgLength;
			default:			/* treat as end of copy */
				return -1;
		}

		/* Drop the processed message and loop around for another */
		conn->inStart = conn->inCursor;
	}
}
Ejemplo n.º 3
0
void CSyncSource::processSyncCommand(const String& strCmd, CJSONEntry oCmdEntry)
{
    CJSONStructIterator objIter(oCmdEntry);

    for( ; !objIter.isEnd() && getSync().isContinueSync(); objIter.next() )
    {
        String strObject = objIter.getCurKey();
        CJSONStructIterator attrIter( objIter.getCurValue() );
        if ( m_bSchemaSource )
            processServerCmd_Ver3_Schema(strCmd,strObject,attrIter);
        else
        {
            for( ; !attrIter.isEnd() && getSync().isContinueSync(); attrIter.next() )
            {
                String strAttrib = attrIter.getCurKey();
                String strValue = attrIter.getCurString();

                processServerCmd_Ver3(strCmd,strObject,strAttrib,strValue);
            }
        }

        if ( getSyncType().compare("none") == 0 )
            continue;

        int nSyncObjectCount  = getNotify().incLastSyncObjectCount(getID());
        if ( getProgressStep() > 0 && (nSyncObjectCount%getProgressStep() == 0) )
            getNotify().fireSyncNotification(this, false, RhoAppAdapter.ERR_NONE, "");

        if ( getDB().isUIWaitDB() )
        {
	        LOG(INFO) + "Commit transaction because of UI request.";
            getDB().endTransaction();
            CSyncThread::getInstance()->sleep(1000);
            getDB().startTransaction();
        }

    }
}
Ejemplo n.º 4
0
/*
 * parseInput: if appropriate, parse input data from backend
 * until input is exhausted or a stopping state is reached.
 * Note that this function will NOT attempt to read more data from the backend.
 */
void
pqParseInput2(PGconn *conn)
{
	char		id;

	/*
	 * Loop to parse successive complete messages available in the buffer.
	 */
	for (;;)
	{
		/*
		 * Quit if in COPY_OUT state: we expect raw data from the server until
		 * PQendcopy is called.  Don't try to parse it according to the normal
		 * protocol.  (This is bogus.  The data lines ought to be part of the
		 * protocol and have identifying leading characters.)
		 */
		if (conn->asyncStatus == PGASYNC_COPY_OUT)
			return;

		/*
		 * OK to try to read a message type code.
		 */
		conn->inCursor = conn->inStart;
		if (pqGetc(&id, conn))
			return;

		/*
		 * NOTIFY and NOTICE messages can happen in any state besides COPY
		 * OUT; always process them right away.
		 *
		 * Most other messages should only be processed while in BUSY state.
		 * (In particular, in READY state we hold off further parsing until
		 * the application collects the current PGresult.)
		 *
		 * However, if the state is IDLE then we got trouble; we need to deal
		 * with the unexpected message somehow.
		 */
		if (id == 'A')
		{
			if (getNotify(conn))
				return;
		}
		else if (id == 'N')
		{
			if (pqGetErrorNotice2(conn, false))
				return;
		}
		else if (conn->asyncStatus != PGASYNC_BUSY)
		{
			/* If not IDLE state, just wait ... */
			if (conn->asyncStatus != PGASYNC_IDLE)
				return;

			/*
			 * Unexpected message in IDLE state; need to recover somehow.
			 * ERROR messages are displayed using the notice processor;
			 * anything else is just dropped on the floor after displaying a
			 * suitable warning notice.  (An ERROR is very possibly the
			 * backend telling us why it is about to close the connection, so
			 * we don't want to just discard it...)
			 */
			if (id == 'E')
			{
				if (pqGetErrorNotice2(conn, false /* treat as notice */ ))
					return;
			}
			else
			{
				pqInternalNotice(&conn->noticeHooks,
						"message type 0x%02x arrived from server while idle",
								 id);
				/* Discard the unexpected message; good idea?? */
				conn->inStart = conn->inEnd;
				break;
			}
		}
		else
		{
			/*
			 * In BUSY state, we can process everything.
			 */
			switch (id)
			{
				case 'C':		/* command complete */
					if (pqGets(&conn->workBuffer, conn))
						return;
					if (conn->result == NULL)
					{
						conn->result = PQmakeEmptyPGresult(conn,
														   PGRES_COMMAND_OK);
						if (!conn->result)
						{
							printfPQExpBuffer(&conn->errorMessage,
											  libpq_gettext("out of memory"));
							pqSaveErrorResult(conn);
						}
					}
					if (conn->result)
					{
						strlcpy(conn->result->cmdStatus, conn->workBuffer.data,
								CMDSTATUS_LEN);
					}
					checkXactStatus(conn, conn->workBuffer.data);
					conn->asyncStatus = PGASYNC_READY;
					break;
				case 'E':		/* error return */
					if (pqGetErrorNotice2(conn, true))
						return;
					conn->asyncStatus = PGASYNC_READY;
					break;
				case 'Z':		/* backend is ready for new query */
					conn->asyncStatus = PGASYNC_IDLE;
					break;
				case 'I':		/* empty query */
					/* read and throw away the closing '\0' */
					if (pqGetc(&id, conn))
						return;
					if (id != '\0')
						pqInternalNotice(&conn->noticeHooks,
										 "unexpected character %c following empty query response (\"I\" message)",
										 id);
					if (conn->result == NULL)
					{
						conn->result = PQmakeEmptyPGresult(conn,
														   PGRES_EMPTY_QUERY);
						if (!conn->result)
						{
							printfPQExpBuffer(&conn->errorMessage,
											  libpq_gettext("out of memory"));
							pqSaveErrorResult(conn);
						}
					}
					conn->asyncStatus = PGASYNC_READY;
					break;
				case 'K':		/* secret key data from the backend */

					/*
					 * This is expected only during backend startup, but it's
					 * just as easy to handle it as part of the main loop.
					 * Save the data and continue processing.
					 */
					if (pqGetInt(&(conn->be_pid), 4, conn))
						return;
					if (pqGetInt(&(conn->be_key), 4, conn))
						return;
					break;
				case 'P':		/* synchronous (normal) portal */
					if (pqGets(&conn->workBuffer, conn))
						return;
					/* We pretty much ignore this message type... */
					break;
				case 'T':		/* row descriptions (start of query results) */
					if (conn->result == NULL)
					{
						/* First 'T' in a query sequence */
						if (getRowDescriptions(conn))
							return;
						/* getRowDescriptions() moves inStart itself */
						continue;
					}
					else
					{
						/*
						 * A new 'T' message is treated as the start of
						 * another PGresult.  (It is not clear that this is
						 * really possible with the current backend.) We stop
						 * parsing until the application accepts the current
						 * result.
						 */
						conn->asyncStatus = PGASYNC_READY;
						return;
					}
					break;
				case 'D':		/* ASCII data tuple */
					if (conn->result != NULL)
					{
						/* Read another tuple of a normal query response */
						if (getAnotherTuple(conn, FALSE))
							return;
						/* getAnotherTuple() moves inStart itself */
						continue;
					}
					else
					{
						pqInternalNotice(&conn->noticeHooks,
										 "server sent data (\"D\" message) without prior row description (\"T\" message)");
						/* Discard the unexpected message; good idea?? */
						conn->inStart = conn->inEnd;
						return;
					}
					break;
				case 'B':		/* Binary data tuple */
					if (conn->result != NULL)
					{
						/* Read another tuple of a normal query response */
						if (getAnotherTuple(conn, TRUE))
							return;
						/* getAnotherTuple() moves inStart itself */
						continue;
					}
					else
					{
						pqInternalNotice(&conn->noticeHooks,
										 "server sent binary data (\"B\" message) without prior row description (\"T\" message)");
						/* Discard the unexpected message; good idea?? */
						conn->inStart = conn->inEnd;
						return;
					}
					break;
				case 'G':		/* Start Copy In */
					conn->asyncStatus = PGASYNC_COPY_IN;
					break;
				case 'H':		/* Start Copy Out */
					conn->asyncStatus = PGASYNC_COPY_OUT;
					break;

					/*
					 * Don't need to process CopyBothResponse here because it
					 * never arrives from the server during protocol 2.0.
					 */
				default:
					printfPQExpBuffer(&conn->errorMessage,
									  libpq_gettext(
													"unexpected response from server; first received character was \"%c\"\n"),
									  id);
					/* build an error result holding the error message */
					pqSaveErrorResult(conn);
					/* Discard the unexpected message; good idea?? */
					conn->inStart = conn->inEnd;
					conn->asyncStatus = PGASYNC_READY;
					return;
			}					/* switch on protocol character */
		}
		/* Successfully consumed this message */
		conn->inStart = conn->inCursor;
	}
}
Ejemplo n.º 5
0
/*
 * PQfn - Send a function call to the POSTGRES backend.
 *
 * See fe-exec.c for documentation.
 */
PGresult *
pqFunctionCall2(PGconn *conn, Oid fnid,
				int *result_buf, int *actual_result_len,
				int result_is_int,
				const PQArgBlock *args, int nargs)
{
	bool		needInput = false;
	ExecStatusType status = PGRES_FATAL_ERROR;
	char		id;
	int			i;

	/* PQfn already validated connection state */

	if (pqPutMsgStart('F', false, conn) < 0 ||	/* function call msg */
		pqPuts(" ", conn) < 0 ||	/* dummy string */
		pqPutInt(fnid, 4, conn) != 0 || /* function id */
		pqPutInt(nargs, 4, conn) != 0)	/* # of args */
	{
		pqHandleSendFailure(conn);
		return NULL;
	}

	for (i = 0; i < nargs; ++i)
	{							/* len.int4 + contents	   */
		if (pqPutInt(args[i].len, 4, conn))
		{
			pqHandleSendFailure(conn);
			return NULL;
		}

		if (args[i].isint)
		{
			if (pqPutInt(args[i].u.integer, 4, conn))
			{
				pqHandleSendFailure(conn);
				return NULL;
			}
		}
		else
		{
			if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
			{
				pqHandleSendFailure(conn);
				return NULL;
			}
		}
	}

	if (pqPutMsgEnd(conn) < 0 ||
		pqFlush(conn))
	{
		pqHandleSendFailure(conn);
		return NULL;
	}

	for (;;)
	{
		if (needInput)
		{
			/* Wait for some data to arrive (or for the channel to close) */
			if (pqWait(TRUE, FALSE, conn) ||
				pqReadData(conn) < 0)
				break;
		}

		/*
		 * Scan the message. If we run out of data, loop around to try again.
		 */
		conn->inCursor = conn->inStart;
		needInput = true;

		if (pqGetc(&id, conn))
			continue;

		/*
		 * We should see V or E response to the command, but might get N
		 * and/or A notices first. We also need to swallow the final Z before
		 * returning.
		 */
		switch (id)
		{
			case 'V':			/* function result */
				if (pqGetc(&id, conn))
					continue;
				if (id == 'G')
				{
					/* function returned nonempty value */
					if (pqGetInt(actual_result_len, 4, conn))
						continue;
					if (result_is_int)
					{
						if (pqGetInt(result_buf, 4, conn))
							continue;
					}
					else
					{
						if (pqGetnchar((char *) result_buf,
									   *actual_result_len,
									   conn))
							continue;
					}
					if (pqGetc(&id, conn))		/* get the last '0' */
						continue;
				}
				if (id == '0')
				{
					/* correctly finished function result message */
					status = PGRES_COMMAND_OK;
				}
				else
				{
					/* The backend violates the protocol. */
					printfPQExpBuffer(&conn->errorMessage,
								  libpq_gettext("protocol error: id=0x%x\n"),
									  id);
					pqSaveErrorResult(conn);
					conn->inStart = conn->inCursor;
					return pqPrepareAsyncResult(conn);
				}
				break;
			case 'E':			/* error return */
				if (pqGetErrorNotice2(conn, true))
					continue;
				status = PGRES_FATAL_ERROR;
				break;
			case 'A':			/* notify message */
				/* handle notify and go back to processing return values */
				if (getNotify(conn))
					continue;
				break;
			case 'N':			/* notice */
				/* handle notice and go back to processing return values */
				if (pqGetErrorNotice2(conn, false))
					continue;
				break;
			case 'Z':			/* backend is ready for new query */
				/* consume the message and exit */
				conn->inStart = conn->inCursor;
				/* if we saved a result object (probably an error), use it */
				if (conn->result)
					return pqPrepareAsyncResult(conn);
				return PQmakeEmptyPGresult(conn, status);
			default:
				/* The backend violates the protocol. */
				printfPQExpBuffer(&conn->errorMessage,
								  libpq_gettext("protocol error: id=0x%x\n"),
								  id);
				pqSaveErrorResult(conn);
				conn->inStart = conn->inCursor;
				return pqPrepareAsyncResult(conn);
		}
		/* Completed this message, keep going */
		conn->inStart = conn->inCursor;
		needInput = false;
	}

	/*
	 * We fall out of the loop only upon failing to read data.
	 * conn->errorMessage has been set by pqWait or pqReadData. We want to
	 * append it to any already-received error message.
	 */
	pqSaveErrorResult(conn);
	return pqPrepareAsyncResult(conn);
}
Ejemplo n.º 6
0
boolean CSyncSource::processSyncObject_ver1(CJSONEntry oJsonObject, int nSrcID)//throws Exception
{
    const char* strOldObject = oJsonObject.getString("oo");
    if ( isDeleteObjectsPass() != (nSrcID < 0) )
        return true;

    if ( oJsonObject.hasName("e") )
    {
        const char* strError = oJsonObject.getString("e");
        getNotify().addCreateObjectError(nSrcID,strOldObject,strError);
        return true;
    }

	const char* strObject = oJsonObject.getString("o");
	CJSONArrayIterator oJsonArr(oJsonObject, "av");
    //oo conflicts
    boolean bUpdatedOO = false;
    //
    for( ; !oJsonArr.isEnd() && getSync().isContinueSync(); oJsonArr.next() )
	{
		CJSONEntry oJsonEntry = oJsonArr.getCurItem();
        if ( oJsonEntry.isEmpty() )
        	continue;

        if ( nSrcID >= 0 ) //insert
        {
    	    CValue value(oJsonEntry,1);
    	    if ( !downloadBlob(value) )
    		    return false;

            String strAttrib = oJsonEntry.getString("a");
            //oo conflicts
            if ( strOldObject != null && !bUpdatedOO )
            {
                getDB().executeSQL("UPDATE object_values SET object=? where object=? and source_id=?", strObject, strOldObject, nSrcID );
                getDB().executeSQL("UPDATE changed_values SET object=? where object=? and source_id=?", strObject, strOldObject, nSrcID );

                getNotify().onObjectChanged(nSrcID,strOldObject, CSyncNotify::enCreate);

                bUpdatedOO = true;
            }

            DBResult(resInsert, getDB().executeSQLReportNonUnique("INSERT INTO object_values \
                (id, attrib, source_id, object, value, attrib_type) VALUES(?,?,?,?,?,?)", 
                value.m_nID, strAttrib, nSrcID, strObject,
                value.m_strValue, value.m_strAttrType ) );
            if ( resInsert.isNonUnique() )
            {
                getDB().executeSQL("UPDATE object_values \
                    SET id=?, value=?, attrib_type=? WHERE object=? and attrib=? and source_id=?", 
                    value.m_nID, value.m_strValue, value.m_strAttrType,
                    strObject, strAttrib, nSrcID );

                // oo conflicts
                getDB().executeSQL("UPDATE changed_values SET main_id=? where object=? and attrib=? and source_id=? and sent<=1", value.m_nID, strObject, strAttrib, nSrcID );
                getDB().executeSQL("UPDATE changed_values SET sent=4 where object=? and attrib=? and source_id=? and sent>1", strObject, strAttrib, nSrcID );
                //
            }

            getNotify().onObjectChanged(nSrcID,strObject, CSyncNotify::enUpdate);

            m_nInserted++;
        }else
Ejemplo n.º 7
0
void CSyncSource::processServerData(const char* szData)
{
    PROF_START("Parse");
    CJSONArrayIterator oJsonArr(szData);
    PROF_STOP("Parse");
    PROF_START("Data1");
    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("error") )
    {
        m_strError = oJsonArr.getCurItem().getString("error");
        m_nErrCode = RhoRuby.ERR_CUSTOMSYNCSERVER;
        getSync().stopSync();
        return;
    }

    if ( !oJsonArr.isEnd() )
    {
        setCurPageCount(oJsonArr.getCurItem().getInt("count"));
        oJsonArr.next();
    }
    int nVersion = 0;
    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("version") )
    {
        nVersion = oJsonArr.getCurItem().getInt("version");
        oJsonArr.next();
    }

    if ( nVersion != getSync().SYNC_VERSION() )
    {
        LOG(ERROR) + "Sync server send data with incompatible version. Client version: " + convertToStringA(getSync().SYNC_VERSION()) +
            "; Server response version: " + convertToStringA(nVersion) + ". Source name: " + getName();
        getSync().stopSync();
        m_nErrCode = RhoRuby.ERR_SYNCVERSION;
        return;
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("rt") )
    {
        setRefreshTime(oJsonArr.getCurItem().getInt("rt"));
        oJsonArr.next();
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("total_count") )
    {
        setTotalCount(oJsonArr.getCurItem().getInt("total_count"));
        oJsonArr.next();
    }
    //if ( getServerObjectsCount() == 0 )
    //    getNotify().fireSyncNotification(this, false, RhoRuby.ERR_NONE, "");

    if ( !oJsonArr.isEnd() )
    {
        processToken(oJsonArr.getCurItem().getUInt64("token"));
        oJsonArr.next();
    }else if ( getCurPageCount() == 0 )
    {
        //oo conflicts
        getDB().executeSQL("DELETE FROM changed_values where source_id=? and sent>=3", getID() );
        //
        processToken(0);
    }

	LOG(INFO) + "Got " + getCurPageCount() + "(Processed: " +  getServerObjectsCount() + ") records of " + getTotalCount() + " from server. Source: " + getName()
         + ". Version: " + nVersion;

    PROF_STOP("Data1");
    if ( !oJsonArr.isEnd() && getSync().isContinueSync() )
    {
        PROF_START("Data");
        //TODO: support DBExceptions
        getDB().startTransaction();

        int nSavedPos = oJsonArr.getCurPos();
        setSyncServerDataPass(edpNone);
        processServerData_Ver1(oJsonArr);

        setSyncServerDataPass(edpDeleteObjects);
        oJsonArr.reset(nSavedPos);
        processServerData_Ver1(oJsonArr);

	    PROF_STOP("Data");		    
    	PROF_START("DB");
        getDB().endTransaction();
	    PROF_STOP("DB");

        getNotify().fireObjectsNotification();
    }

	PROF_START("Data1");
    if ( getCurPageCount() > 0 )
        getNotify().fireSyncNotification(this, false, RhoRuby.ERR_NONE, "");
	PROF_STOP("Data1");
}
/*
 * parseInput: if appropriate, parse input data from backend
 * until input is exhausted or a stopping state is reached.
 * Note that this function will NOT attempt to read more data from the backend.
 */
void
pqParseInput3(PGconn *conn)
{
	char		id;
	int			msgLength;
	int			avail;

	/*
	 * Loop to parse successive complete messages available in the buffer.
	 */
	for (;;)
	{
		/*
		 * Try to read a message.  First get the type code and length. Return
		 * if not enough data.
		 */
		conn->inCursor = conn->inStart;
		if (pqGetc(&id, conn))
			return;
		if (pqGetInt(&msgLength, 4, conn))
			return;

		/*
		 * Try to validate message type/length here.  A length less than 4 is
		 * definitely broken.  Large lengths should only be believed for a few
		 * message types.
		 */
		if (msgLength < 4)
		{
			handleSyncLoss(conn, id, msgLength);
			return;
		}
		if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id))
		{
			handleSyncLoss(conn, id, msgLength);
			return;
		}

		/*
		 * Can't process if message body isn't all here yet.
		 */
		msgLength -= 4;
		avail = conn->inEnd - conn->inCursor;
		if (avail < msgLength)
		{
			/*
			 * Before returning, enlarge the input buffer if needed to hold
			 * the whole message.  This is better than leaving it to
			 * pqReadData because we can avoid multiple cycles of realloc()
			 * when the message is large; also, we can implement a reasonable
			 * recovery strategy if we are unable to make the buffer big
			 * enough.
			 */
			if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
			{
				/*
				 * XXX add some better recovery code... plan is to skip over
				 * the message using its length, then report an error. For the
				 * moment, just treat this like loss of sync (which indeed it
				 * might be!)
				 */
				handleSyncLoss(conn, id, msgLength);
			}
			return;
		}

		/*
		 * NOTIFY and NOTICE messages can happen in any state; always process
		 * them right away.
		 *
		 * Most other messages should only be processed while in BUSY state.
		 * (In particular, in READY state we hold off further parsing until
		 * the application collects the current PGresult.)
		 *
		 * However, if the state is IDLE then we got trouble; we need to deal
		 * with the unexpected message somehow.
		 *
		 * ParameterStatus ('S') messages are a special case: in IDLE state we
		 * must process 'em (this case could happen if a new value was adopted
		 * from config file due to SIGHUP), but otherwise we hold off until
		 * BUSY state.
		 */
		if (id == 'A')
		{
			if (getNotify(conn))
				return;
		}
		else if (id == 'N')
		{
			if (pqGetErrorNotice3(conn, false))
				return;
		}
		else if (conn->asyncStatus != PGASYNC_BUSY)
		{
			/* If not IDLE state, just wait ... */
			if (conn->asyncStatus != PGASYNC_IDLE)
				return;

			/*
			 * Unexpected message in IDLE state; need to recover somehow.
			 * ERROR messages are displayed using the notice processor;
			 * ParameterStatus is handled normally; anything else is just
			 * dropped on the floor after displaying a suitable warning
			 * notice.	(An ERROR is very possibly the backend telling us why
			 * it is about to close the connection, so we don't want to just
			 * discard it...)
			 */
			if (id == 'E')
			{
				if (pqGetErrorNotice3(conn, false /* treat as notice */ ))
					return;
			}
			else if (id == 'S')
			{
				if (getParameterStatus(conn))
					return;
			}
			else
			{
				pqInternalNotice(&conn->noticeHooks,
						"message type 0x%02x arrived from server while idle",
								 id);
				/* Discard the unexpected message */
				conn->inCursor += msgLength;
			}
		}
		else
		{
			/*
			 * In BUSY state, we can process everything.
			 */
			switch (id)
			{
				case 'C':		/* command complete */
					if (pqGets(&conn->workBuffer, conn))
						return;
					if (conn->result == NULL)
					{
						conn->result = PQmakeEmptyPGresult(conn,
														   PGRES_COMMAND_OK);
						if (!conn->result)
							return;
					}
					strncpy(conn->result->cmdStatus, conn->workBuffer.data,
							CMDSTATUS_LEN);
					conn->asyncStatus = PGASYNC_READY;
					break;
				case 'E':		/* error return */
					if (pqGetErrorNotice3(conn, true))
						return;
					conn->asyncStatus = PGASYNC_READY;
					break;
				case 'Z':		/* backend is ready for new query */
					if (getReadyForQuery(conn))
						return;
					conn->asyncStatus = PGASYNC_IDLE;
					break;
				case 'I':		/* empty query */
					if (conn->result == NULL)
					{
						conn->result = PQmakeEmptyPGresult(conn,
														   PGRES_EMPTY_QUERY);
						if (!conn->result)
							return;
					}
					conn->asyncStatus = PGASYNC_READY;
					break;
				case '1':		/* Parse Complete */
					/* If we're doing PQprepare, we're done; else ignore */
					if (conn->queryclass == PGQUERY_PREPARE)
					{
						if (conn->result == NULL)
						{
							conn->result = PQmakeEmptyPGresult(conn,
														   PGRES_COMMAND_OK);
							if (!conn->result)
								return;
						}
						conn->asyncStatus = PGASYNC_READY;
					}
					break;
				case '2':		/* Bind Complete */
				case '3':		/* Close Complete */
					/* Nothing to do for these message types */
					break;
				case 'S':		/* parameter status */
					if (getParameterStatus(conn))
						return;
					break;
				case 'K':		/* secret key data from the backend */

					/*
					 * This is expected only during backend startup, but it's
					 * just as easy to handle it as part of the main loop.
					 * Save the data and continue processing.
					 */
					if (pqGetInt(&(conn->be_pid), 4, conn))
						return;
					if (pqGetInt(&(conn->be_key), 4, conn))
						return;
					break;
				case 'T':		/* Row Description */
					if (conn->result == NULL ||
						conn->queryclass == PGQUERY_DESCRIBE)
					{
						/* First 'T' in a query sequence */
						if (getRowDescriptions(conn))
							return;

						/*
						 * If we're doing a Describe, we're ready to pass the
						 * result back to the client.
						 */
						if (conn->queryclass == PGQUERY_DESCRIBE)
							conn->asyncStatus = PGASYNC_READY;
					}
					else
					{
						/*
						 * A new 'T' message is treated as the start of
						 * another PGresult.  (It is not clear that this is
						 * really possible with the current backend.) We stop
						 * parsing until the application accepts the current
						 * result.
						 */
						conn->asyncStatus = PGASYNC_READY;
						return;
					}
					break;
				case 'n':		/* No Data */

					/*
					 * NoData indicates that we will not be seeing a
					 * RowDescription message because the statement or portal
					 * inquired about doesn't return rows. Set up a COMMAND_OK
					 * result, instead of TUPLES_OK.
					 */
					if (conn->result == NULL)
						conn->result = PQmakeEmptyPGresult(conn,
														   PGRES_COMMAND_OK);

					/*
					 * If we're doing a Describe, we're ready to pass the
					 * result back to the client.
					 */
					if (conn->queryclass == PGQUERY_DESCRIBE)
						conn->asyncStatus = PGASYNC_READY;
					break;
				case 't':		/* Parameter Description */
					if (getParamDescriptions(conn))
						return;
					break;
				case 'D':		/* Data Row */
					if (conn->result != NULL &&
						conn->result->resultStatus == PGRES_TUPLES_OK)
					{
						/* Read another tuple of a normal query response */
						if (getAnotherTuple(conn, msgLength))
							return;
					}
					else if (conn->result != NULL &&
							 conn->result->resultStatus == PGRES_FATAL_ERROR)
					{
						/*
						 * We've already choked for some reason.  Just discard
						 * tuples till we get to the end of the query.
						 */
						conn->inCursor += msgLength;
					}
					else
					{
						/* Set up to report error at end of query */
						printfPQExpBuffer(&conn->errorMessage,
										  libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
						pqSaveErrorResult(conn);
						/* Discard the unexpected message */
						conn->inCursor += msgLength;
					}
					break;
				case 'G':		/* Start Copy In */
					if (getCopyStart(conn, PGRES_COPY_IN))
						return;
					conn->asyncStatus = PGASYNC_COPY_IN;
					break;
				case 'H':		/* Start Copy Out */
					if (getCopyStart(conn, PGRES_COPY_OUT))
						return;
					conn->asyncStatus = PGASYNC_COPY_OUT;
					conn->copy_already_done = 0;
					break;
				case 'd':		/* Copy Data */

					/*
					 * If we see Copy Data, just silently drop it.	This would
					 * only occur if application exits COPY OUT mode too
					 * early.
					 */
					conn->inCursor += msgLength;
					break;
				case 'c':		/* Copy Done */

					/*
					 * If we see Copy Done, just silently drop it.	This is
					 * the normal case during PQendcopy.  We will keep
					 * swallowing data, expecting to see command-complete for
					 * the COPY command.
					 */
					break;
				default:
					printfPQExpBuffer(&conn->errorMessage,
									  libpq_gettext(
													"unexpected response from server; first received character was \"%c\"\n"),
									  id);
					/* build an error result holding the error message */
					pqSaveErrorResult(conn);
					/* not sure if we will see more, so go to ready state */
					conn->asyncStatus = PGASYNC_READY;
					/* Discard the unexpected message */
					conn->inCursor += msgLength;
					break;
			}					/* switch on protocol character */
		}
		/* Successfully consumed this message */
		if (conn->inCursor == conn->inStart + 5 + msgLength)
		{
			/* Normal case: parsing agrees with specified length */
			conn->inStart = conn->inCursor;
		}
		else
		{
			/* Trouble --- report it */
			printfPQExpBuffer(&conn->errorMessage,
							  libpq_gettext("message contents do not agree with length in message type \"%c\"\n"),
							  id);
			/* build an error result holding the error message */
			pqSaveErrorResult(conn);
			conn->asyncStatus = PGASYNC_READY;
			/* trust the specified message length as what to skip */
			conn->inStart += 5 + msgLength;
		}
	}
}
/*
 * PQfn - Send a function call to the POSTGRES backend.
 *
 * See fe-exec.c for documentation.
 */
PGresult *
pqFunctionCall3(PGconn *conn, Oid fnid,
				int *result_buf, int *actual_result_len,
				int result_is_int,
				const PQArgBlock *args, int nargs)
{
	bool		needInput = false;
	ExecStatusType status = PGRES_FATAL_ERROR;
	char		id;
	int			msgLength;
	int			avail;
	int			i;

	/* PQfn already validated connection state */

	if (pqPutMsgStart('F', false, conn) < 0 ||	/* function call msg */
		pqPutInt(fnid, 4, conn) < 0 ||	/* function id */
		pqPutInt(1, 2, conn) < 0 ||		/* # of format codes */
		pqPutInt(1, 2, conn) < 0 ||		/* format code: BINARY */
		pqPutInt(nargs, 2, conn) < 0)	/* # of args */
	{
		pqHandleSendFailure(conn);
		return NULL;
	}

	for (i = 0; i < nargs; ++i)
	{							/* len.int4 + contents	   */
		if (pqPutInt(args[i].len, 4, conn))
		{
			pqHandleSendFailure(conn);
			return NULL;
		}
		if (args[i].len == -1)
			continue;			/* it's NULL */

		if (args[i].isint)
		{
			if (pqPutInt(args[i].u.integer, args[i].len, conn))
			{
				pqHandleSendFailure(conn);
				return NULL;
			}
		}
		else
		{
			if (pqPutnchar((char *) args[i].u.ptr, args[i].len, conn))
			{
				pqHandleSendFailure(conn);
				return NULL;
			}
		}
	}

	if (pqPutInt(1, 2, conn) < 0)		/* result format code: BINARY */
	{
		pqHandleSendFailure(conn);
		return NULL;
	}

	if (pqPutMsgEnd(conn) < 0 ||
		pqFlush(conn))
	{
		pqHandleSendFailure(conn);
		return NULL;
	}

	for (;;)
	{
		if (needInput)
		{
			/* Wait for some data to arrive (or for the channel to close) */
			if (pqWait(TRUE, FALSE, conn) ||
				pqReadData(conn) < 0)
				break;
		}

		/*
		 * Scan the message. If we run out of data, loop around to try again.
		 */
		needInput = true;

		conn->inCursor = conn->inStart;
		if (pqGetc(&id, conn))
			continue;
		if (pqGetInt(&msgLength, 4, conn))
			continue;

		/*
		 * Try to validate message type/length here.  A length less than 4 is
		 * definitely broken.  Large lengths should only be believed for a few
		 * message types.
		 */
		if (msgLength < 4)
		{
			handleSyncLoss(conn, id, msgLength);
			break;
		}
		if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id))
		{
			handleSyncLoss(conn, id, msgLength);
			break;
		}

		/*
		 * Can't process if message body isn't all here yet.
		 */
		msgLength -= 4;
		avail = conn->inEnd - conn->inCursor;
		if (avail < msgLength)
		{
			/*
			 * Before looping, enlarge the input buffer if needed to hold the
			 * whole message.  See notes in parseInput.
			 */
			if (pqCheckInBufferSpace(conn->inCursor + msgLength, conn))
			{
				/*
				 * XXX add some better recovery code... plan is to skip over
				 * the message using its length, then report an error. For the
				 * moment, just treat this like loss of sync (which indeed it
				 * might be!)
				 */
				handleSyncLoss(conn, id, msgLength);
				break;
			}
			continue;
		}

		/*
		 * We should see V or E response to the command, but might get N
		 * and/or A notices first. We also need to swallow the final Z before
		 * returning.
		 */
		switch (id)
		{
			case 'V':			/* function result */
				if (pqGetInt(actual_result_len, 4, conn))
					continue;
				if (*actual_result_len != -1)
				{
					if (result_is_int)
					{
						if (pqGetInt(result_buf, *actual_result_len, conn))
							continue;
					}
					else
					{
						if (pqGetnchar((char *) result_buf,
									   *actual_result_len,
									   conn))
							continue;
					}
				}
				/* correctly finished function result message */
				status = PGRES_COMMAND_OK;
				break;
			case 'E':			/* error return */
				if (pqGetErrorNotice3(conn, true))
					continue;
				status = PGRES_FATAL_ERROR;
				break;
			case 'A':			/* notify message */
				/* handle notify and go back to processing return values */
				if (getNotify(conn))
					continue;
				break;
			case 'N':			/* notice */
				/* handle notice and go back to processing return values */
				if (pqGetErrorNotice3(conn, false))
					continue;
				break;
			case 'Z':			/* backend is ready for new query */
				if (getReadyForQuery(conn))
					continue;
				/* consume the message and exit */
				conn->inStart += 5 + msgLength;
				/* if we saved a result object (probably an error), use it */
				if (conn->result)
					return pqPrepareAsyncResult(conn);
				return PQmakeEmptyPGresult(conn, status);
			case 'S':			/* parameter status */
				if (getParameterStatus(conn))
					continue;
				break;
			default:
				/* The backend violates the protocol. */
				printfPQExpBuffer(&conn->errorMessage,
								  libpq_gettext("protocol error: id=0x%x\n"),
								  id);
				pqSaveErrorResult(conn);
				/* trust the specified message length as what to skip */
				conn->inStart += 5 + msgLength;
				return pqPrepareAsyncResult(conn);
		}
		/* Completed this message, keep going */
		/* trust the specified message length as what to skip */
		conn->inStart += 5 + msgLength;
		needInput = false;
	}

	/*
	 * We fall out of the loop only upon failing to read data.
	 * conn->errorMessage has been set by pqWait or pqReadData. We want to
	 * append it to any already-received error message.
	 */
	pqSaveErrorResult(conn);
	return pqPrepareAsyncResult(conn);
}
Ejemplo n.º 10
0
void CSyncSource::processServerCmd_Ver3_Schema(const String& strCmd, const String& strObject, CJSONStructIterator& attrIter)//throws Exception
{
    if ( strCmd.compare("insert") == 0 )
    {
        Vector<String> vecValues, vecAttrs;
        String strCols = "", strQuest = "", strSet = "";
        for( ; !attrIter.isEnd() && getSync().isContinueSync(); attrIter.next() )
        {
            CAttrValue oAttrValue(attrIter.getCurKey(),attrIter.getCurString());
            if ( !processBlob(strCmd,strObject,oAttrValue) )
                continue;

            if ( strCols.length() > 0 )
                strCols += ",";
            if ( strQuest.length() > 0)
                strQuest += ",";
            if ( strSet.length() > 0)
                strSet += ",";

            strCols += oAttrValue.m_strAttrib;
            strQuest += "?";
            strSet += oAttrValue.m_strAttrib + "=?";
            vecAttrs.addElement(oAttrValue.m_strAttrib);
            vecValues.addElement(oAttrValue.m_strValue);
        }
        vecValues.addElement(strObject);
        if ( strCols.length() > 0 )
            strCols += ",";
        if ( strQuest.length() > 0)
            strQuest += ",";

        strCols += "object";
        strQuest += "?";

        String strSqlInsert = "INSERT INTO ";
        strSqlInsert += getName() + " (";
        strSqlInsert += strCols + ") VALUES(" + strQuest + ")";

        if ( !getSync().isContinueSync() )
            return;

        DBResult(resInsert, getDB().executeSQLReportNonUniqueEx(strSqlInsert.c_str(), vecValues ) );
        if ( resInsert.isNonUnique() )
        {
            String strSqlUpdate = "UPDATE ";
            strSqlUpdate += getName() + " SET " + strSet + " WHERE object=?";
            getDB().executeSQLEx(strSqlUpdate.c_str(), vecValues);

            if ( getSyncType().compare("none") != 0 )
            {
                // oo conflicts
                for( int i = 0; i < (int)vecAttrs.size(); i++ )
                {
                    getDB().executeSQL("UPDATE changed_values SET sent=4 where object=? and attrib=? and source_id=? and sent>1", 
                        strObject, vecAttrs.elementAt(i), getID() );
                }
                //
            }
        }

        if ( getSyncType().compare("none") != 0 )
            getNotify().onObjectChanged(getID(),strObject, CSyncNotify::enUpdate);

        m_nInserted++;
    }else if (strCmd.compare("delete") == 0)
    {
        Vector<String> vecAttrs;
        String strSet = "";
        for( ; !attrIter.isEnd() && getSync().isContinueSync(); attrIter.next() )
        {
            CAttrValue oAttrValue(attrIter.getCurKey(),attrIter.getCurString());

            if ( strSet.length() > 0 )
                strSet += ",";

            vecAttrs.addElement(oAttrValue.m_strAttrib);
            strSet += oAttrValue.m_strAttrib + "=NULL";
        }

        String strSqlUpdate = "UPDATE ";
        strSqlUpdate += getName() + " SET " + strSet + " WHERE object=?";

        if ( strSet.length() == 0 || !getSync().isContinueSync() )
            return;

        getDB().executeSQL(strSqlUpdate.c_str(), strObject);
        //Remove item if all nulls
        String strSelect = String("SELECT * FROM ") + getName() + " WHERE object=?";
        DBResult(res, getDB().executeSQL( strSelect.c_str(), strObject ) );
        if ( !res.isEnd() )
        {
            boolean bAllNulls = true;
            for( int i = 0; i < res.getColCount(); i ++)
            {
                if ( !res.isNullByIdx(i) && res.getColName(i).compare("object")!=0 )
                {
                    bAllNulls = false;
                    break;
                }
            }

            if (bAllNulls)
            {
                String strDelete = String("DELETE FROM ") + getName() + " WHERE object=?";
                getDB().executeSQL( strDelete.c_str(), strObject);
            }
        }

        if ( getSyncType().compare("none") != 0 )
        {
            getNotify().onObjectChanged(getID(), strObject, CSyncNotify::enDelete);
            // oo conflicts
            for( int i = 0; i < (int)vecAttrs.size(); i++ )
            {
                getDB().executeSQL("UPDATE changed_values SET sent=3 where object=? and attrib=? and source_id=?", 
                    strObject, vecAttrs.elementAt(i), getID() );
            }
            //
        }

        m_nDeleted++;
    }else if ( strCmd.compare("links") == 0 )
    {
        String strValue = attrIter.getCurString();
        processAssociations(strObject, strValue);

        String strSqlUpdate = "UPDATE ";
        strSqlUpdate += getName() + " SET object=? WHERE object=?";
        getDB().executeSQL(strSqlUpdate.c_str(), strValue, strObject);

        getDB().executeSQL("UPDATE changed_values SET object=?,sent=3 where object=? and source_id=?", strValue, strObject, getID() );
        getNotify().onObjectChanged(getID(), strObject, CSyncNotify::enCreate);
    }

}
Ejemplo n.º 11
0
void CSyncSource::processServerResponse_ver3(CJSONArrayIterator& oJsonArr)
{
    PROF_START("Data1");

    int nVersion = 0;
    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("version") )
    {
        nVersion = oJsonArr.getCurItem().getInt("version");
        oJsonArr.next();
    }

    if ( nVersion != getProtocol().getVersion() )
    {
        LOG(ERROR) + "Sync server send data with incompatible version. Client version: " + convertToStringA(getProtocol().getVersion()) +
            "; Server response version: " + convertToStringA(nVersion) + ". Source name: " + getName();
        getSync().stopSync();
        m_nErrCode = RhoAppAdapter.ERR_SYNCVERSION;
        return;
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("token"))
    {
        processToken(oJsonArr.getCurItem().getUInt64("token"));
        oJsonArr.next();
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("source") )
    {
        //skip it. it uses in search only
        oJsonArr.next();
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("count") )
    {
        setCurPageCount(oJsonArr.getCurItem().getInt("count"));
        oJsonArr.next();
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("refresh_time") )
    {
        setRefreshTime(oJsonArr.getCurItem().getInt("refresh_time"));
        oJsonArr.next();
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("progress_count") )
    {
        //TODO: progress_count
        //setTotalCount(oJsonArr.getCurItem().getInt("progress_count"));
        oJsonArr.next();
    }

    if ( !oJsonArr.isEnd() && oJsonArr.getCurItem().hasName("total_count") )
    {
        setTotalCount(oJsonArr.getCurItem().getInt("total_count"));
        oJsonArr.next();
    }

    //if ( getServerObjectsCount() == 0 )
    //    getNotify().fireSyncNotification(this, false, RhoAppAdapter.ERR_NONE, "");

    if ( getToken() == 0 )
    {
        //oo conflicts
        getDB().executeSQL("DELETE FROM changed_values where source_id=? and sent>=3", getID() );
        //

    }

	LOG(INFO) + "Got " + getCurPageCount() + "(Processed: " +  getServerObjectsCount() + ") records of " + getTotalCount() + " from server. Source: " + getName()
         + ". Version: " + nVersion;

    PROF_STOP("Data1");
    if ( !oJsonArr.isEnd() && getSync().isContinueSync() )
    {
        CJSONEntry oCmds = oJsonArr.getCurItem();
        PROF_START("Data");
        //TODO: use isUIWaitDB inside processSyncCommand
        //    if ( getDB().isUIWaitDB() )
        //    {
		//        LOG(INFO) + "Commit transaction because of UI request.";
        //        getDB().endTransaction();
        //        getDB().startTransaction();
        //    }

        if ( oCmds.hasName("schema-changed") )
        {
            getSync().setSchemaChanged(true);
        }else if ( oCmds.hasName("source-error") )
        {
            CJSONEntry errSrc = oCmds.getEntry("source-error");
            CJSONStructIterator errIter(errSrc);
            for( ; !errIter.isEnd(); errIter.next() )
            {
                m_nErrCode = RhoAppAdapter.ERR_CUSTOMSYNCSERVER;
                m_strError = errIter.getCurValue().getString("message");
                m_strErrorType = errIter.getCurKey();
            }
        }else if ( oCmds.hasName("search-error") )
        {
            CJSONEntry errSrc = oCmds.getEntry("search-error");
            CJSONStructIterator errIter(errSrc);
            for( ; !errIter.isEnd(); errIter.next() )
            {
                m_nErrCode = RhoAppAdapter.ERR_CUSTOMSYNCSERVER;
                m_strError = errIter.getCurValue().getString("message");
                m_strErrorType = errIter.getCurKey();
            }
        }else if ( oCmds.hasName("create-error") )
        {
            m_nErrCode = RhoAppAdapter.ERR_CUSTOMSYNCSERVER;
            m_strErrorType = "create-error";
        }else if ( oCmds.hasName("update-error") )
        {
            m_nErrCode = RhoAppAdapter.ERR_CUSTOMSYNCSERVER;
            m_strErrorType = "update-error";
        }else if ( oCmds.hasName("delete-error") )
        {
            m_nErrCode = RhoAppAdapter.ERR_CUSTOMSYNCSERVER;
            m_strErrorType = "delete-error";
        }else
        {
            getDB().startTransaction();

            if (getSync().getSourceOptions().getBoolProperty(getID(), "pass_through"))
            {
                if ( m_bSchemaSource )
                    getDB().executeSQL( (String("DELETE FROM ") + getName()).c_str() );
                else
                    getDB().executeSQL("DELETE FROM object_values WHERE source_id=?", getID() );
            }

            if ( oCmds.hasName("metadata") && getSync().isContinueSync() )
            {
                String strMetadata = oCmds.getString("metadata");
                getDB().executeSQL("UPDATE sources SET metadata=? WHERE source_id=?", strMetadata, getID() );
            }
            if ( oCmds.hasName("links") && getSync().isContinueSync() )
                processSyncCommand("links", oCmds.getEntry("links") );
            if ( oCmds.hasName("delete") && getSync().isContinueSync() )
                processSyncCommand("delete", oCmds.getEntry("delete") );
            if ( oCmds.hasName("insert") && getSync().isContinueSync() )
                processSyncCommand("insert", oCmds.getEntry("insert") );

            PROF_STOP("Data");

	        PROF_START("DB");
            getDB().endTransaction();
            PROF_STOP("DB");

            getNotify().fireObjectsNotification();
        }
    }

	PROF_START("Data1");
    if ( getCurPageCount() > 0 )
        getNotify().fireSyncNotification(this, false, RhoAppAdapter.ERR_NONE, "");
	PROF_STOP("Data1");
}
Ejemplo n.º 12
0
/*
 * getCopyDataMessage - fetch next CopyData message, process async messages
 *
 * Returns length word of CopyData message (> 0), or 0 if no complete
 * message available, -1 if end of copy, -2 if error.
 */
static int
getCopyDataMessage(PGconn *conn)
{
	char		id;
	int			msgLength;
	int			avail;

	for (;;)
	{
		/*
		 * Do we have the next input message?  To make life simpler for async
		 * callers, we keep returning 0 until the next message is fully
		 * available, even if it is not Copy Data.
		 */
		conn->inCursor = conn->inStart;
		if (pqGetc(&id, conn))
			return 0;
		if (pqGetInt(&msgLength, 4, conn))
			return 0;
		if (msgLength < 4)
		{
			handleSyncLoss(conn, id, msgLength);
			return -2;
		}
		avail = conn->inEnd - conn->inCursor;
		if (avail < msgLength - 4)
		{
			/*
			 * Before returning, enlarge the input buffer if needed to hold
			 * the whole message.  See notes in parseInput.
			 */
			if (pqCheckInBufferSpace(conn->inCursor + (size_t) msgLength - 4,
									 conn))
			{
				/*
				 * XXX add some better recovery code... plan is to skip over
				 * the message using its length, then report an error. For the
				 * moment, just treat this like loss of sync (which indeed it
				 * might be!)
				 */
				handleSyncLoss(conn, id, msgLength);
				return -2;
			}
			return 0;
		}

		/*
		 * If it's a legitimate async message type, process it.  (NOTIFY
		 * messages are not currently possible here, but we handle them for
		 * completeness.)  Otherwise, if it's anything except Copy Data,
		 * report end-of-copy.
		 */
		switch (id)
		{
			case 'A':			/* NOTIFY */
				if (getNotify(conn))
					return 0;
				break;
			case 'N':			/* NOTICE */
				if (pqGetErrorNotice3(conn, false))
					return 0;
				break;
			case 'S':			/* ParameterStatus */
				if (getParameterStatus(conn))
					return 0;
				break;
			case 'd':			/* Copy Data, pass it back to caller */
				return msgLength;
			default:			/* treat as end of copy */
				return -1;
		}

		/* Drop the processed message and loop around for another */
		conn->inStart = conn->inCursor;
	}
}