/*
 * Reconcile and resolve conflicts for incoming versioning events.
 *
 * This function handles both events generated locally and remotely by other
 * backends.
 *
 * When a new versioning event is received at the Local MDVSN,
 * look up if the same object has a conflicting version locally.
 *
 * If a conflict is detected, resolve by generating a new version for
 * the "combined" object. We don't generate a new VE for the conflict.
 * This means the combined object version is only visible to the
 * current backend.
 *
 *   event: The new event to be considered
 *   local_entry: The conflicting entry in the Local MDVSN cache
 */
static void
mdver_localhandler_reconcile(mdver_event *event, mdver_entry *local_entry)
{

#ifdef MD_VERSIONING_INSTRUMENTATION
	char *mdev_str = mdver_event_str(event);
	elog(gp_mdversioning_loglevel, "Local VE Handler: Reconcile: Local entry = %d: (%d, %d). Incoming event %s",
			local_entry->key,
			(int) local_entry->ddl_version,
			(int) local_entry->dml_version,
			mdev_str);
	pfree(mdev_str);
#endif

	uint64 new_ddl_version = event->new_ddl_version;
	uint64 new_dml_version = event->new_dml_version;
	bool conflict = false;

	if (local_entry->ddl_version != event->old_ddl_version)
	{
		new_ddl_version = mdver_next_global_version();
		conflict = true;
	}

	if (local_entry->dml_version != event->old_dml_version)
	{
		new_dml_version = mdver_next_global_version();
		conflict = true;
	}

#if MD_VERSIONING_INSTRUMENTATION
	if (conflict)
	{
		elog(gp_mdversioning_loglevel, "Local VE Handler: Conflict resolved. New"
				"version generated, updated local entry to %d : (%d,%d) -> (%d, %d)",
				local_entry->key,
				(int) local_entry->ddl_version, (int) local_entry->dml_version,
				(int) new_ddl_version, (int) new_dml_version);

	}
	else
	{
		elog(gp_mdversioning_loglevel, "Local VE Handler: No conflict. Update local entry to %d : (%d,%d) -> (%d, %d)",
				local_entry->key,
				(int) event->old_ddl_version, (int) event->old_dml_version,
				(int) event->new_ddl_version, (int) event->new_dml_version);
	}
#endif

	/* Update local entry with the resolved versions */
	local_entry->ddl_version = new_ddl_version;
	local_entry->dml_version = new_dml_version;
}
/*
 * When a backend is requesting the more recent version of an object,
 * if the Local MDVSN cache doesn't have the version, and if a NUKE event
 * has been encountered in the current transaction, a new version is
 * generated and returned for the object. A new versioning event is also
 * produced.
 *
 *   key: The key of the looked-up object
 *   ddl_version: used to return the ddl version for the object
 *   dml_version: used to return the dml version for the object
 *
 */
static void
mdver_request_after_nuke(Oid key, uint64 *ddl_version, uint64 *dml_version)
{
	Assert(NULL != ddl_version);
	Assert(NULL != dml_version);

	/* Generate new version */
	*ddl_version = mdver_next_global_version();
	*dml_version = mdver_next_global_version();

	mdver_event *new_event = (mdver_event *) palloc0(sizeof(mdver_event));
	new_event->key = key;
	new_event->new_ddl_version = *ddl_version;
	new_event->new_dml_version = *dml_version;
	new_event->old_ddl_version = INVALID_MD_VERSION;
	new_event->old_dml_version = INVALID_MD_VERSION;

#ifdef MD_VERSIONING_INSTRUMENTATION
	/* Add my current process id as the originating backend pid */
	new_event->backend_pid = MyProcPid;
#endif

	/* Annotate Versioning Event with the current version from Global MDVSN if exists */
	mdver_entry *crt_entry = mdver_glob_mdvsn_find(key);
	if (NULL != crt_entry)
	{
		new_event->old_ddl_version = crt_entry->ddl_version;
		new_event->old_dml_version = crt_entry->dml_version;
	}

	CacheAddVersioningEvent(new_event);

#ifdef MD_VERSIONING_INSTRUMENTATION
	char *mdev_str = mdver_event_str(new_event);
	ereport(gp_mdversioning_loglevel,
			(errmsg("mdver_consume_after_nuke: generated new VE %s",
					mdev_str),
					errprintstack(false)));
	pfree(mdev_str);
#endif

	/* A copy of the event is added to the queue above. We can pfree our local copy */
	pfree(new_event);
}
/*
 * When a backend is requesting the more recent version of an object,
 * if the Local MDVSN cache doesn't have the version, and if a NUKE event
 * hasn't been encountered in the current transaction, it is looked up
 * in the Global MDVSN shared cache.
 *
 * If the object is found in Global MDVSN, return the global version.
 * If the object is not found, generate a new version, record it in Global MDVSN
 * and then return it.
 *
 *   key: The key of the looked-up object
 *   ddl_version: used to return the ddl version for the object
 *   dml_version: used to return the dml version for the object
 *
 */
static void
mdver_request_from_global(Oid key, uint64 *ddl_version, uint64 *dml_version)
{

	Assert(NULL != ddl_version);
	Assert(NULL != dml_version);

	Cache *mdver_glob_mdvsn = mdver_get_glob_mdvsn();
	Assert(NULL != mdver_glob_mdvsn);

	mdver_entry entry = {key, INVALID_MD_VERSION, INVALID_MD_VERSION};

	/* FIXME gcaragea 06/03/2014: Trigger evictions if cache is full (MPP-22923) */
	CacheEntry *localEntry = Cache_AcquireEntry(mdver_glob_mdvsn, &entry);

	Assert(NULL != localEntry);

	/*
	 * We're about to look-up and insert a shared cache entry.
	 * Grab writer lock in exclusive mode, so that no other backend
	 * can insert or update the same entry at the same time.
	 */
	LWLockAcquire(MDVerWriteLock, LW_EXCLUSIVE);

	CacheEntry *cachedEntry = Cache_Lookup(mdver_glob_mdvsn, localEntry);

	if (NULL != cachedEntry)
	{
		/* Not found in LVSN, not nuke happened, eventually found in GVSN */
		mdver_entry *crt_entry = CACHE_ENTRY_PAYLOAD(cachedEntry);

		*ddl_version = crt_entry->ddl_version;
		*dml_version = crt_entry->dml_version;

#ifdef MD_VERSIONING_INSTRUMENTATION
		elog(gp_mdversioning_loglevel, "Found version in Global MDVSN: (%d, " UINT64_FORMAT ", " UINT64_FORMAT "). Adding it to Local MDVSN",
				key, crt_entry->ddl_version, crt_entry->dml_version);
#endif

		/*
		 * We're also done with the entry, release our pincount on it
		 *
		 * TODO gcaragea 05/02/2014: Are there cases where we need to hold the
		 * entry past this point? (MPP-22923)
		 */

		Cache_Release(mdver_glob_mdvsn, cachedEntry);
	}
	else
	{
		/* Not found in LVSN, not nuke happened, not found in GVSN either */

		/* Generate new version */
		*ddl_version = mdver_next_global_version();
		*dml_version = mdver_next_global_version();

		/* Add to GVSN */
		mdver_entry *new_entry = CACHE_ENTRY_PAYLOAD(localEntry);
		new_entry->ddl_version = *ddl_version;
		new_entry->dml_version = *dml_version;

#ifdef MD_VERSIONING_INSTRUMENTATION
		elog(gp_mdversioning_loglevel, "Inserting new version in Global MDVSN: (%d, " UINT64_FORMAT ", " UINT64_FORMAT "). Adding it to Local MDVSN",
				key, new_entry->ddl_version, new_entry->dml_version);
#endif

		Cache_Insert(mdver_glob_mdvsn, localEntry);

	}

	LWLockRelease(MDVerWriteLock);

	/* Release local entry. We don't need it anymore */
	Cache_Release(mdver_glob_mdvsn, localEntry);
}
/*
 * Reconcile an incoming versioning event with an existing Global MDVSN entry
 * for the same versioned object.
 *
 * Each versioning event contains the old version and the new version as known
 * by the originating backend:
 *   VE = (key, oldV, newV)
 * Cached entry contains the current version globally visible:
 *   entry = (key, crtV)
 *
 * We have the following scenarios:
 *  - If oldV == crtV, (i.e. VE old version is the same as the current version)
 *     then nobody else has modified the object since the backend read it.
 *     We simply update the entry with the new version in that case:
 *       entry = (key, crtV) --> entry = (key, newV)
 *
 *  - If oldV < crtV, (i.e. VE old version is different than the current version)
 *     some other backend must have modified the object in the meantime.
 *    We generate an entirely new version new_newV for the object to reflect
 *     the new "combined" object.
 *
 *    The cached entry is updated directly with the new version:
 *        entry = (key, crtV) --> entry = (key, new_newV)
 *
 *    The versioning event in the queue is updated directly:
         VE = (key, oldV, newV)  --> VE = (key, crtV, new_newV)
 *
 *  event: The event containing the versioning information for an update
 *  cached_entry: The existing entry for this object in the Global MDVSN
 *
 * This function is called while the MDVerWriteLock is held in exclusive
 * mode. Don't do anything that is not allowed while holding a LWLock
 * (e.g. allocate memory, or call unsafe functions).
 *
 */
static void
mdver_globalhandler_reconcile(mdver_event *event, CacheEntry *cached_entry)
{

    /* Found existing entry, reconcile and update the version */
    mdver_entry *cached_mdver_entry = CACHE_ENTRY_PAYLOAD(cached_entry);

#ifdef MD_VERSIONING_INSTRUMENTATION
    elog(gp_mdversioning_loglevel, "Updating GlobalMDVSN entry %d: Current (%d,%d). Event: [(%d,%d)->(%d,%d)]",
         event->key,
         (int) cached_mdver_entry->ddl_version, (int) cached_mdver_entry->dml_version,
         (int) event->old_ddl_version, (int) event->old_dml_version,
         (int) event->new_ddl_version, (int) event->new_dml_version);
#endif

    /*
     * Reconcile and resolve conflicts for incoming versioning events.
     *  When a new versioning event is received at the Global MDVSN,
     *  look up if the same object has a conflicting version.
     * If so, resolve conflict by generating a new version.
     */

    uint64 new_ddl_version = event->new_ddl_version;
    uint64 new_dml_version = event->new_dml_version;
    bool conflict = false;

    /*
     * It is safe to read the cached_mdver_entry contents, since
     * we're holding the write lock on the Global MDVSN cache.
     */
    if (cached_mdver_entry->ddl_version != event->old_ddl_version)
    {
        new_ddl_version = mdver_next_global_version();
        conflict = true;
    }

    if (cached_mdver_entry->dml_version != event->old_dml_version)
    {
        new_dml_version = mdver_next_global_version();
        conflict = true;
    }

    if (conflict)
    {

#ifdef MD_VERSIONING_INSTRUMENTATION
        elog(gp_mdversioning_loglevel, "Updating event in the queue (pid=%d, oid=%d): Old event: [(%d,%d)->(%d,%d)]. Modified event: [(%d,%d)->(%d,%d)]",
             event->backend_pid,
             event->key,
             /* Old event */
             (int) event->old_ddl_version, (int) event->old_dml_version,
             (int) event->new_ddl_version, (int) event->new_dml_version,
             /* New event */
             (int) cached_mdver_entry->ddl_version, (int) cached_mdver_entry->dml_version,
             (int) new_ddl_version, (int) new_dml_version);
#endif

        /*
         * A new version for this object is being generated here.
         * We're going to directly update the event in the queue with the new
         * version.
         */

        event->new_ddl_version = new_ddl_version;
        event->new_dml_version = new_dml_version;

        /*
         * We're also updating the VE old version to reflect the current
         * visible global version
         */
        event->old_ddl_version = cached_mdver_entry->ddl_version;
        event->old_dml_version = cached_mdver_entry->dml_version;
    }

    /* About to update the cached entry. Lock entry to make update atomic */
    Cache *glob_mdvsn = mdver_get_glob_mdvsn();
    Cache_LockEntry(glob_mdvsn, cached_entry);

    cached_mdver_entry->ddl_version = new_ddl_version;
    cached_mdver_entry->dml_version = new_dml_version;

    Cache_UnlockEntry(glob_mdvsn, cached_entry);

}