gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.

}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}

// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{

	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}

// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:

			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}

// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();

	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}

// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{

		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


// pgAdmin III - PostgreSQL Tools
// gqbGridJoinTable.cpp - Table implementation for Joins Panel Grid

// App headers
#include "pgAdmin3.h"

// wxWindows headers
#include <wx/wx.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include <wx/notebook.h>
#include <wx/regex.h>

// App headers
#include "gqb/gqbGridJoinTable.h"
#include "gqb/gqbGridRestTable.h"
#include "gqb/gqbColumn.h"
#include "gqb/gqbQueryObjs.h"
#include "gqb/gqbViewController.h"

gqbGridJoinTable::gqbGridJoinTable(gqbController *_controller):
	wxGridTableBase()
{
	controller = _controller;
}

gqbGridJoinTable::~gqbGridJoinTable()
{
	emptyTableData();
}

int gqbGridJoinTable::GetNumberRows()
{
	return joins.count();
}

int gqbGridJoinTable::GetNumberCols()
{
	return 3;
}

bool gqbGridJoinTable::IsEmptyCell( int row, int col )
{
	if (joins.count() == 0)
		return true;
	return false;
}

wxString gqbGridJoinTable::GetValue( int row, int col )
{
	if (row >= joins.count())
		return wxEmptyString;
	gqbQueryJoin *obj = (gqbQueryJoin *)joins.getItemAt(row);

	switch(col)
	{
		case 0:
		{
			gqbQueryObject *srcTbl = obj->getSourceQTable();
			wxString sStr = srcTbl ? (srcTbl->getAlias().IsEmpty() ? qtIdent(srcTbl->getName()) : qtIdent(srcTbl->getAlias())) : wxString(wxEmptyString);
			if ( !sStr.IsEmpty() )
				sStr += wxT(".") + qtIdent(obj->getSourceCol());
			return sStr;
		}
		case 1:
			switch(obj->getKindofJoin())
			{
				case _equally:
					return wxT("=");
				case _lesser:
					return wxT("<");
				case _greater:
					return wxT(">");
				case _equlesser:
					return wxT("<=");
				case _equgreater:
					return wxT(">=");
			}
			return wxEmptyString;
		case 2:
		{
			gqbQueryObject *destTbl = obj->getDestQTable();
			wxString dStr = destTbl ? (destTbl->getAlias().IsEmpty() ? qtIdent(destTbl->getName()) : qtIdent(destTbl->getAlias())) : wxString(wxEmptyString);
			if ( !dStr.IsEmpty() )
				dStr += wxT(".") + obj->getDestCol();
			return dStr;
		}
		break;
	};
	return wxT("");
}

void gqbGridJoinTable::SetValue( int row, int col, const wxString &value )
{
	if (col == 1)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(row);
		if (value == wxT("="))
			join->setKindofJoin(_equally);
		else if (value == wxT("<"))
			join->setKindofJoin(_lesser);
		else if (value == wxT(">"))
			join->setKindofJoin(_greater);
		else if (value == wxT("<="))
			join->setKindofJoin(_equlesser);
		else if (value == wxT(">="))
			join->setKindofJoin(_equgreater);
		controller->getView()->Refresh();
	}
}

bool gqbGridJoinTable::ReplaceJoin( gqbQueryJoin *orig, gqbQueryJoin *newVal )
{
	int rowCount = joins.count();
	for ( int index = 0; index < rowCount; index++ )
	{
		if ( joins[ index ] == orig )
		{
			joins[ index ] = newVal;
			return true;
		}
	}
	return false;
}

gqbQueryJoin *gqbGridJoinTable::GetJoin( int row )
{
	if (row >= joins.count())
		return NULL;
	return (gqbQueryJoin *)joins.getItemAt(row);
}

void gqbGridJoinTable::removeJoin(gqbQueryJoin *item)
{
	if (item == NULL || this->joins.count() == 0)
		return;
	int index = joins.getIndex( item );
	if (index == -1)
		return;
	joins.removeItem( item );
	if (GetView())
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
		                        index + 1,
		                        1 );
		GetView()->ProcessTableMessage( msg );
	}
}

void gqbGridJoinTable::removeJoins(gqbQueryObject *obj)
{
	if (!obj)
		return;

	if (obj->getHaveJoins())
	{
		gqbIteratorBase *itrJoins = obj->createJoinsIterator();
		if (itrJoins)
		{
			while (itrJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrJoins->Next();
				removeJoin(tmp);
			}
		}
	}

	if (obj->getHaveRegJoins())
	{
		gqbIteratorBase *itrRegJoins = obj->createRegJoinsIterator();
		if (itrRegJoins)
		{
			while (itrRegJoins->HasNext())
			{
				gqbQueryJoin *tmp = (gqbQueryJoin *)itrRegJoins->Next();
				removeJoin(tmp);
			}
		}
	}
}

void gqbGridJoinTable::AppendJoin(gqbQueryJoin *item)
{
	bool notify = true;
	if ( item == NULL )
	{
		item = new gqbQueryJoin(NULL, NULL, NULL, NULL, _equally);
	}
	joins.addItem( item );
	if (notify && GetView() )
	{
		wxGridTableMessage msg( this,
		                        wxGRIDTABLE_NOTIFY_ROWS_I

NSERTED,
		                        joins.count(),
		                        1 );
		GetView()->ProcessTableMessage( msg );

		int row = GetView()->GetNumberRows() - 1;

		wxString strChoices[] = {wxT("="), wxT("<"), wxT("<="), wxT(">"), wxT(">=")};

		GetView()->SetCellRenderer(row, 0, new wxGridCellButtonRenderer);
		GetView()->SetCellRenderer(row, 1, new wxGridCellComboBoxRenderer);
		GetView()->SetCellEditor(row, 1, new dxGridCellSizedChoiceEditor(WXSIZEOF(strChoices), strChoices));
		GetView()->SetCellRenderer(row, 2, new wxGridCellButtonRenderer);

		GetView()->SetReadOnly( row, 0 );
		GetView()->SetReadOnly( row, 2 );
	}
}

// Removes all items from gqbGridJoinTable
void gqbGridJoinTable::emptyTableData()
{
	for (int index = joins.count() - 1; index >= 0; index--)
	{
		gqbQueryJoin *join = (gqbQueryJoin *)joins[index];
		joins.removeItem(join);

		// Join with either source or destination not present needs to be removed here
		if (!(join->getSourceQTable() && join->getDestQTable()))
		{
			delete join;
			join = NULL;
		}
	}
}

gqbQueryObject *gqbGridJoinTable::DeleteRow(size_t pos)
{
	gqbQueryJoin *join = (gqbQueryJoin *)joins.getItemAt(pos);
	gqbQueryObject *srcTbl = NULL;
	if (join)
	{
		this->removeJoin(join);
		if (join->getSourceQTable() && join->getDestQTable())
		{
			srcTbl = join->getSourceQTable();
			srcTbl->removeJoin(join, true);
		}
	}

	return srcTbl;
}

wxString gqbGridJoinTable::GetColLabelValue(int col)
{
	switch(col)
	{
		case 0:
			return _("Source Column");
		case 1:
			return _("Join Type");
		case 2:
			return _("Destination Column");
	}
	return wxEmptyString;
}

void gqbGridJoinTable::selectJoin(gqbQueryJoin *join)
{
	int indexRow = joins.getIndex(join);
	if (indexRow != -1 && GetView())
	{
		GetView()->ClearSelection();
		GetView()->SelectRow(indexRow);
	}
}


bool PandasScanFunction::PandasScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p,
                                                     FunctionOperatorData *operator_state,
                                                     ParallelState *parallel_state_p)

#include "duckdb_python/pandas_scan.hpp"
#include "duckdb_python/array_wrapper.hpp"
#include "duckdb/parallel/parallel_state.hpp"
#include "utf8proc_wrapper.hpp"
#include "duckdb/common/types/timestamp.hpp"
#include "duckdb_python/vector_conversion.hpp"
#include "duckdb/main/client_context.hpp"

#include "duckdb/common/atomic.hpp"

namespace duckdb {

struct PandasScanFunctionData : public TableFunctionData {
	PandasScanFunctionData(py::handle df, idx_t row_count, vector<PandasColumnBindData> pandas_bind_data,
	                       vector<LogicalType> sql_types)
	    : df(df), row_count(row_count), lines_read(0), pandas_bind_data(move(pandas_bind_data)),
	      sql_types(move(sql_types)) {
	}
	py::handle df;
	idx_t row_count;
	atomic<idx_t> lines_read;
	vector<PandasColumnBindData> pandas_bind_data;
	vector<LogicalType> sql_types;

	~PandasScanFunctionData() override {
		py::gil_scoped_acquire acquire;
		pandas_bind_data.clear();
	}
};

struct PandasScanState : public FunctionOperatorData {
	PandasScanState(idx_t start, idx_t end) : start(start), end(end) {
	}

	idx_t start;
	idx_t end;
	vector<column_t> column_ids;
};

struct ParallelPandasScanState : public ParallelState {
	ParallelPandasScanState() : position(0) {
	}

	std::mutex lock;
	idx_t position;
};

PandasScanFunction::PandasScanFunction()
    : TableFunction("pandas_scan", {LogicalType::POINTER}, PandasScanFunc, PandasScanBind, PandasScanInit, nullptr,
                    nullptr, nullptr, PandasScanCardinality, nullptr, nullptr, PandasScanMaxThreads,
                    PandasScanInitParallelState, PandasScanFuncParallel, PandasScanParallelInit,
                    PandasScanParallelStateNext, true, false, PandasProgress) {
}

unique_ptr<FunctionData> PandasScanFunction::PandasScanBind(ClientContext &context, vector<Value> &inputs,
                                                            unordered_map<string, Value> &named_parameters,
                                                            vector<LogicalType> &input_table_types,
                                                            vector<string> &input_table_names,
                                                            vector<LogicalType> &return_types, vector<string> &names) {
	py::gil_scoped_acquire acquire;
	py::handle df((PyObject *)(inputs[0].GetPointer()));

	vector<PandasColumnBindData> pandas_bind_data;
	VectorConversion::BindPandas(df, pandas_bind_data, return_types, names);

	auto df_columns = py::list(df.attr("columns"));
	auto get_fun = df.attr("__getitem__");

	idx_t row_count = py::len(get_fun(df_columns[0]));
	return make_unique<PandasScanFunctionData>(df, row_count, move(pandas_bind_data), return_types);
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanInit(ClientContext &context,
                                                                    const FunctionData *bind_data_p,
                                                                    const vector<column_t> &column_ids,
                                                                    TableFilterCollection *filters) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto result = make_unique<PandasScanState>(0, bind_data.row_count);
	result->column_ids = column_ids;
	return move(result);
}

idx_t PandasScanFunction::PandasScanMaxThreads(ClientContext &context, const FunctionData *bind_data_p) {
	if (context.verify_parallelism) {
		return context.db->NumberOfThreads();
	}
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	return bind_data.row_count / PANDAS_PARTITION_COUNT + 1;
}

unique_ptr<ParallelState> PandasScanFunction::PandasScanInitParallelState(ClientContext &context,
                                                                          const FunctionData *bind_data_p,
                                                                          const vector<column_t> &column_ids,
                                                                          TableFilterCollection *filters) {
	return make_unique<ParallelPandasScanState>();
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanParallelInit(ClientContext &context,
                                                                            const FunctionData *bind_data_p,
                                                                            ParallelState *state,
                                                                            const vector<column_t> &column_ids,
                                                                            TableFilterCollection *filters) {
	auto result = make_unique<PandasScanState>(0, 0);
	result->column_ids = column_ids;
	if (!PandasScanParallelStateNext(context, bind_data_p, result.get(), state)) {
		return nullptr;
	}
	return move(result);
}

{
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto &parallel_state = (ParallelPandasScanState &)*parallel_state_p;
	auto &state = (PandasScanState &)*operator_state;

	lock_guard<mutex> parallel_lock(parallel_state.lock);
	if (parallel_state.position >= bind_data.row_count) {
		return false;
	}
	state.start = parallel_state.position;
	parallel_state.position += PANDAS_PARTITION_COUNT;
	if (parallel_state.position > bind_data.row_count) {
		parallel_state.position = bind_data.row_count;
	}
	state.end = parallel_state.position;
	return true;
}

int PandasScanFunction::PandasProgress(ClientContext &context, const FunctionData *bind_data_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	if (bind_data.row_count == 0) {
		return 100;
	}
	auto percentage = bind_data.lines_read * 100 / bind_data.row_count;
	return percentage;
}

void PandasScanFunction::PandasScanFuncParallel(ClientContext &context, const FunctionData *bind_data,
                                                FunctionOperatorData *operator_state, DataChunk *input,
                                                DataChunk &output, ParallelState *parallel_state_p) {
	//! FIXME: Have specialized parallel function from pandas scan here
	PandasScanFunc(context, bind_data, operator_state, input, output);
}

//! The main pandas scan function: note that this can be called in parallel without the GIL
//! hence this needs to be GIL-safe, i.e. no methods that create Python objects are allowed
void PandasScanFunction::PandasScanFunc(ClientContext &context, const FunctionData *bind_data,
                                        FunctionOperatorData *operator_state, DataChunk *input, DataChunk &output) {
	if (!operator_state) {
		return;
	}
	auto &data = (PandasScanFunctionData &)*bind_data;
	auto &state = (PandasScanState &)*operator_state;

	if (state.start >= state.end) {
		return;
	}
	idx_t this_count = std::min((idx_t)STANDARD_VECTOR_SIZE, state.end - state.start);
	output.SetCardinality(this_count);
	for (idx_t idx = 0; idx < state.column_ids.size(); idx++) {
		auto col_idx = state.column_ids[idx];
		if (col_idx == COLUMN_IDENTIFIER_ROW_ID) {
			output.data[idx].Sequence(state.start, this_count);
		} else {
			VectorConversion::NumpyToDuckDB(data.pandas_bind_data[col_idx], data.pandas_bind_data[col_idx].numpy_col,
			                                this_count, state.start, output.data[idx]);
		}
	}
	state.start += this_count;
	data.lines_read += this_count;
}

unique_ptr<NodeStatistics> PandasScanFunction::PandasScanCardinality(ClientContext &context,
                                                                     const FunctionData *bind_data) {
	auto &data = (PandasScanFunctionData &)*bind_data;
	return make_unique<NodeStatistics>(data.row_count, data.row_count);
}

} // namespace duckdb


{
	auto result = make_unique<PandasScanState>(0, 0);
	result->column_ids = column_ids;
	if (!PandasScanParallelStateNext(context, bind_data_p, result.get(), state)) {
		return nullptr;
	}
	return move(result);
}

#include "duckdb_python/pandas_scan.hpp"
#include "duckdb_python/array_wrapper.hpp"
#include "duckdb/parallel/parallel_state.hpp"
#include "utf8proc_wrapper.hpp"
#include "duckdb/common/types/timestamp.hpp"
#include "duckdb_python/vector_conversion.hpp"
#include "duckdb/main/client_context.hpp"

#include "duckdb/common/atomic.hpp"

namespace duckdb {

struct PandasScanFunctionData : public TableFunctionData {
	PandasScanFunctionData(py::handle df, idx_t row_count, vector<PandasColumnBindData> pandas_bind_data,
	                       vector<LogicalType> sql_types)
	    : df(df), row_count(row_count), lines_read(0), pandas_bind_data(move(pandas_bind_data)),
	      sql_types(move(sql_types)) {
	}
	py::handle df;
	idx_t row_count;
	atomic<idx_t> lines_read;
	vector<PandasColumnBindData> pandas_bind_data;
	vector<LogicalType> sql_types;

	~PandasScanFunctionData() override {
		py::gil_scoped_acquire acquire;
		pandas_bind_data.clear();
	}
};

struct PandasScanState : public FunctionOperatorData {
	PandasScanState(idx_t start, idx_t end) : start(start), end(end) {
	}

	idx_t start;
	idx_t end;
	vector<column_t> column_ids;
};

struct ParallelPandasScanState : public ParallelState {
	ParallelPandasScanState() : position(0) {
	}

	std::mutex lock;
	idx_t position;
};

PandasScanFunction::PandasScanFunction()
    : TableFunction("pandas_scan", {LogicalType::POINTER}, PandasScanFunc, PandasScanBind, PandasScanInit, nullptr,
                    nullptr, nullptr, PandasScanCardinality, nullptr, nullptr, PandasScanMaxThreads,
                    PandasScanInitParallelState, PandasScanFuncParallel, PandasScanParallelInit,
                    PandasScanParallelStateNext, true, false, PandasProgress) {
}

unique_ptr<FunctionData> PandasScanFunction::PandasScanBind(ClientContext &context, vector<Value> &inputs,
                                                            unordered_map<string, Value> &named_parameters,
                                                            vector<LogicalType> &input_table_types,
                                                            vector<string> &input_table_names,
                                                            vector<LogicalType> &return_types, vector<string> &names) {
	py::gil_scoped_acquire acquire;
	py::handle df((PyObject *)(inputs[0].GetPointer()));

	vector<PandasColumnBindData> pandas_bind_data;
	VectorConversion::BindPandas(df, pandas_bind_data, return_types, names);

	auto df_columns = py::list(df.attr("columns"));
	auto get_fun = df.attr("__getitem__");

	idx_t row_count = py::len(get_fun(df_columns[0]));
	return make_unique<PandasScanFunctionData>(df, row_count, move(pandas_bind_data), return_types);
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanInit(ClientContext &context,
                                                                    const FunctionData *bind_data_p,
                                                                    const vector<column_t> &column_ids,
                                                                    TableFilterCollection *filters) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto result = make_unique<PandasScanState>(0, bind_data.row_count);
	result->column_ids = column_ids;
	return move(result);
}

idx_t PandasScanFunction::PandasScanMaxThreads(ClientContext &context, const FunctionData *bind_data_p) {
	if (context.verify_parallelism) {
		return context.db->NumberOfThreads();
	}
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	return bind_data.row_count / PANDAS_PARTITION_COUNT + 1;
}

unique_ptr<ParallelState> PandasScanFunction::PandasScanInitParallelState(ClientContext &context,
                                                                          const FunctionData *bind_data_p,
                                                                          const vector<column_t> &column_ids,
                                                                          TableFilterCollection *filters) {
	return make_unique<ParallelPandasScanState>();
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanParallelInit(ClientContext &context,
                                                                            const FunctionData *bind_data_p,
                                                                            ParallelState *state,
                                                                            const vector<column_t> &column_ids,
                                                                            TableFilterCollection *filters)

bool PandasScanFunction::PandasScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p,
                                                     FunctionOperatorData *operator_state,
                                                     ParallelState *parallel_state_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto &parallel_state = (ParallelPandasScanState &)*parallel_state_p;
	auto &state = (PandasScanState &)*operator_state;

	lock_guard<mutex> parallel_lock(parallel_state.lock);
	if (parallel_state.position >= bind_data.row_count) {
		return false;
	}
	state.start = parallel_state.position;
	parallel_state.position += PANDAS_PARTITION_COUNT;
	if (parallel_state.position > bind_data.row_count) {
		parallel_state.position = bind_data.row_count;
	}
	state.end = parallel_state.position;
	return true;
}

int PandasScanFunction::PandasProgress(ClientContext &context, const FunctionData *bind_data_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	if (bind_data.row_count == 0) {
		return 100;
	}
	auto percentage = bind_data.lines_read * 100 / bind_data.row_count;
	return percentage;
}

void PandasScanFunction::PandasScanFuncParallel(ClientContext &context, const FunctionData *bind_data,
                                                FunctionOperatorData *operator_state, DataChunk *input,
                                                DataChunk &output, ParallelState *parallel_state_p) {
	//! FIXME: Have specialized parallel function from pandas scan here
	PandasScanFunc(context, bind_data, operator_state, input, output);
}

//! The main pandas scan function: note that this can be called in parallel without the GIL
//! hence this needs to be GIL-safe, i.e. no methods that create Python objects are allowed
void PandasScanFunction::PandasScanFunc(ClientContext &context, const FunctionData *bind_data,
                                        FunctionOperatorData *operator_state, DataChunk *input, DataChunk &output) {
	if (!operator_state) {
		return;
	}
	auto &data = (PandasScanFunctionData &)*bind_data;
	auto &state = (PandasScanState &)*operator_state;

	if (state.start >= state.end) {
		return;
	}
	idx_t this_count = std::min((idx_t)STANDARD_VECTOR_SIZE, state.end - state.start);
	output.SetCardinality(this_count);
	for (idx_t idx = 0; idx < state.column_ids.size(); idx++) {
		auto col_idx = state.column_ids[idx];
		if (col_idx == COLUMN_IDENTIFIER_ROW_ID) {
			output.data[idx].Sequence(state.start, this_count);
		} else {
			VectorConversion::NumpyToDuckDB(data.pandas_bind_data[col_idx], data.pandas_bind_data[col_idx].numpy_col,
			                                this_count, state.start, output.data[idx]);
		}
	}
	state.start += this_count;
	data.lines_read += this_count;
}

unique_ptr<NodeStatistics> PandasScanFunction::PandasScanCardinality(ClientContext &context,
                                                                     const FunctionData *bind_data) {
	auto &data = (PandasScanFunctionData &)*bind_data;
	return make_unique<NodeStatistics>(data.row_count, data.row_count);
}

} // namespace duckdb


{
	if (context.verify_parallelism) {
		return context.db->NumberOfThreads();
	}

#include "duckdb_python/pandas_scan.hpp"
#include "duckdb_python/array_wrapper.hpp"
#include "duckdb/parallel/parallel_state.hpp"
#include "utf8proc_wrapper.hpp"
#include "duckdb/common/types/timestamp.hpp"
#include "duckdb_python/vector_conversion.hpp"
#include "duckdb/main/client_context.hpp"

#include "duckdb/common/atomic.hpp"

namespace duckdb {

struct PandasScanFunctionData : public TableFunctionData {
	PandasScanFunctionData(py::handle df, idx_t row_count, vector<PandasColumnBindData> pandas_bind_data,
	                       vector<LogicalType> sql_types)
	    : df(df), row_count(row_count), lines_read(0), pandas_bind_data(move(pandas_bind_data)),
	      sql_types(move(sql_types)) {
	}
	py::handle df;
	idx_t row_count;
	atomic<idx_t> lines_read;
	vector<PandasColumnBindData> pandas_bind_data;
	vector<LogicalType> sql_types;

	~PandasScanFunctionData() override {
		py::gil_scoped_acquire acquire;
		pandas_bind_data.clear();
	}
};

struct PandasScanState : public FunctionOperatorData {
	PandasScanState(idx_t start, idx_t end) : start(start), end(end) {
	}

	idx_t start;
	idx_t end;
	vector<column_t> column_ids;
};

struct ParallelPandasScanState : public ParallelState {
	ParallelPandasScanState() : position(0) {
	}

	std::mutex lock;
	idx_t position;
};

PandasScanFunction::PandasScanFunction()
    : TableFunction("pandas_scan", {LogicalType::POINTER}, PandasScanFunc, PandasScanBind, PandasScanInit, nullptr,
                    nullptr, nullptr, PandasScanCardinality, nullptr, nullptr, PandasScanMaxThreads,
                    PandasScanInitParallelState, PandasScanFuncParallel, PandasScanParallelInit,
                    PandasScanParallelStateNext, true, false, PandasProgress) {
}

unique_ptr<FunctionData> PandasScanFunction::PandasScanBind(ClientContext &context, vector<Value> &inputs,
                                                            unordered_map<string, Value> &named_parameters,
                                                            vector<LogicalType> &input_table_types,
                                                            vector<string> &input_table_names,
                                                            vector<LogicalType> &return_types, vector<string> &names) {
	py::gil_scoped_acquire acquire;
	py::handle df((PyObject *)(inputs[0].GetPointer()));

	vector<PandasColumnBindData> pandas_bind_data;
	VectorConversion::BindPandas(df, pandas_bind_data, return_types, names);

	auto df_columns = py::list(df.attr("columns"));
	auto get_fun = df.attr("__getitem__");

	idx_t row_count = py::len(get_fun(df_columns[0]));
	return make_unique<PandasScanFunctionData>(df, row_count, move(pandas_bind_data), return_types);
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanInit(ClientContext &context,
                                                                    const FunctionData *bind_data_p,
                                                                    const vector<column_t> &column_ids,
                                                                    TableFilterCollection *filters) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto result = make_unique<PandasScanState>(0, bind_data.row_count);
	result->column_ids = column_ids;
	return move(result);
}

idx_t PandasScanFunction::PandasScanMaxThreads(ClientContext &context, const FunctionData *bind_data_p)

	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	return bind_data.row_count / PANDAS_PARTITION_COUNT + 1;
}

unique_ptr<ParallelState> PandasScanFunction::PandasScanInitParallelState(ClientContext &context,
                                                                          const FunctionData *bind_data_p,
                                                                          const vector<column_t> &column_ids,
                                                                          TableFilterCollection *filters) {
	return make_unique<ParallelPandasScanState>();
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanParallelInit(ClientContext &context,
                                                                            const FunctionData *bind_data_p,
                                                                            ParallelState *state,
                                                                            const vector<column_t> &column_ids,
                                                                            TableFilterCollection *filters) {
	auto result = make_unique<PandasScanState>(0, 0);
	result->column_ids = column_ids;
	if (!PandasScanParallelStateNext(context, bind_data_p, result.get(), state)) {
		return nullptr;
	}
	return move(result);
}

bool PandasScanFunction::PandasScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p,
                                                     FunctionOperatorData *operator_state,
                                                     ParallelState *parallel_state_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto &parallel_state = (ParallelPandasScanState &)*parallel_state_p;
	auto &state = (PandasScanState &)*operator_state;

	lock_guard<mutex> parallel_lock(parallel_state.lock);
	if (parallel_state.position >= bind_data.row_count) {
		return false;
	}
	state.start = parallel_state.position;
	parallel_state.position += PANDAS_PARTITION_COUNT;
	if (parallel_state.position > bind_data.row_count) {
		parallel_state.position = bind_data.row_count;
	}
	state.end = parallel_state.position;
	return true;
}

int PandasScanFunction::PandasProgress(ClientContext &context, const FunctionData *bind_data_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	if (bind_data.row_count == 0) {
		return 100;
	}
	auto percentage = bind_data.lines_read * 100 / bind_data.row_count;
	return percentage;
}

void PandasScanFunction::PandasScanFuncParallel(ClientContext &context, const FunctionData *bind_data,
                                                FunctionOperatorData *operator_state, DataChunk *input,
                                                DataChunk &output, ParallelState *parallel_state_p) {
	//! FIXME: Have specialized parallel function from pandas scan here
	PandasScanFunc(context, bind_data, operator_state, input, output);
}

//! The main pandas scan function: note that this can be called in parallel without the GIL
//! hence this needs to be GIL-safe, i.e. no methods that create Python objects are allowed
void PandasScanFunction::PandasScanFunc(ClientContext &context, const FunctionData *bind_data,
                                        FunctionOperatorData *operator_state, DataChunk *input, DataChunk &output) {
	if (!operator_state) {
		return;
	}
	auto &data = (PandasScanFunctionData &)*bind_data;
	auto &state = (PandasScanState &)*operator_state;

	if (state.start >= state.end) {
		return;
	}
	idx_t this_count = std::min((idx_t)STANDARD_VECTOR_SIZE, state.end - state.start);
	output.SetCardinality(this_count);
	for (idx_t idx = 0; idx < state.column_ids.size(); idx++) {
		auto col_idx = state.column_ids[idx];
		if (col_idx == COLUMN_IDENTIFIER_ROW_ID) {
			output.data[idx].Sequence(state.start, this_count);
		} else {
			VectorConversion::NumpyToDuckDB(data.pandas_bind_data[col_idx], data.pandas_bind_data[col_idx].numpy_col,
			                                this_count, state.start, output.data[idx]);
		}
	}
	state.start += this_count;
	data.lines_read += this_count;
}

unique_ptr<NodeStatistics> PandasScanFunction::PandasScanCardinality(ClientContext &context,
                                                                     const FunctionData *bind_data) {
	auto &data = (PandasScanFunctionData &)*bind_data;
	return make_unique<NodeStatistics>(data.row_count, data.row_count);
}

} // namespace duckdb


#include "duckdb_python/pandas_scan.hpp"
#include "duckdb_python/array_wrapper.hpp"
#include "duckdb/parallel/parallel_state.hpp"
#include "utf8proc_wrapper.hpp"
#include "duckdb/common/types/timestamp.hpp"
#include "duckdb_python/vector_conversion.hpp"
#include "duckdb/main/client_context.hpp"

#include "duckdb/common/atomic.hpp"

namespace duckdb {

struct PandasScanFunctionData : public TableFunctionData {
	PandasScanFunctionData(py::handle df, idx_t row_count, vector<PandasColumnBindData> pandas_bind_data,
	                       vector<LogicalType> sql_types)
	    : df(df), row_count(row_count), lines_read(0), pandas_bind_data(move(pandas_bind_data)),
	      sql_types(move(sql_types)) {
	}
	py::handle df;
	idx_t row_count;
	atomic<idx_t> lines_read;
	vector<PandasColumnBindData> pandas_bind_data;
	vector<LogicalType> sql_types;

	~PandasScanFunctionData() override {
		py::gil_scoped_acquire acquire;
		pandas_bind_data.clear();
	}
};

struct PandasScanState : public FunctionOperatorData {
	PandasScanState(idx_t start, idx_t end) : start(start), end(end) {
	}

	idx_t start;
	idx_t end;
	vector<column_t> column_ids;
};

struct ParallelPandasScanState : public ParallelState {
	ParallelPandasScanState() : position(0) {
	}

	std::mutex lock;
	idx_t position;
};

PandasScanFunction::PandasScanFunction()
    : TableFunction("pandas_scan", {LogicalType::POINTER}, PandasScanFunc, PandasScanBind, PandasScanInit, nullptr,
                    nullptr, nullptr, PandasScanCardinality, nullptr, nullptr, PandasScanMaxThreads,
                    PandasScanInitParallelState, PandasScanFuncParallel, PandasScanParallelInit,
                    PandasScanParallelStateNext, true, false, PandasProgress) {
}

unique_ptr<FunctionData> PandasScanFunction::PandasScanBind(ClientContext &context, vector<Value> &inputs,
                                                            unordered_map<string, Value> &named_parameters,
                                                            vector<LogicalType> &input_table_types,
                                                            vector<string> &input_table_names,
                                                            vector<LogicalType> &return_types, vector<string> &names) {
	py::gil_scoped_acquire acquire;
	py::handle df((PyObject *)(inputs[0].GetPointer()));

	vector<PandasColumnBindData> pandas_bind_data;
	VectorConversion::BindPandas(df, pandas_bind_data, return_types, names);

	auto df_columns = py::list(df.attr("columns"));
	auto get_fun = df.attr("__getitem__");

	idx_t row_count = py::len(get_fun(df_columns[0]));
	return make_unique<PandasScanFunctionData>(df, row_count, move(pandas_bind_data), return_types);
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanInit(ClientContext &context,
                                                                    const FunctionData *bind_data_p,
                                                                    const vector<column_t> &column_ids,
                                                                    TableFilterCollection *filters) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto result = make_unique<PandasScanState>(0, bind_data.row_count);
	result->column_ids = column_ids;
	return move(result);
}

idx_t PandasScanFunction::PandasScanMaxThreads(ClientContext &context, const FunctionData *bind_data_p) {
	if (context.verify_parallelism) {
		return context.db->NumberOfThreads();
	}

	return bind_data.row_count / PANDAS_PARTITION_COUNT + 1;
}

unique_ptr<ParallelState> PandasScanFunction::PandasScanInitParallelState(ClientContext &context,
                                                                          const FunctionData *bind_data_p,
                                                                          const vector<column_t> &column_ids,
                                                                          TableFilterCollection *filters) {
	return make_unique<ParallelPandasScanState>();
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanParallelInit(ClientContext &context,
                                                                            const FunctionData *bind_data_p,
                                                                            ParallelState *state,
                                                                            const vector<column_t> &column_ids,
                                                                            TableFilterCollection *filters) {
	auto result = make_unique<PandasScanState>(0, 0);
	result->column_ids = column_ids;
	if (!PandasScanParallelStateNext(context, bind_data_p, result.get(), state)) {
		return nullptr;
	}
	return move(result);
}

bool PandasScanFunction::PandasScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p,
                                                     FunctionOperatorData *operator_state,
                                                     ParallelState *parallel_state_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto &parallel_state = (ParallelPandasScanState &)*parallel_state_p;
	auto &state = (PandasScanState &)*operator_state;

	lock_guard<mutex> parallel_lock(parallel_state.lock);
	if (parallel_state.position >= bind_data.row_count) {
		return false;
	}
	state.start = parallel_state.position;
	parallel_state.position += PANDAS_PARTITION_COUNT;
	if (parallel_state.position > bind_data.row_count) {
		parallel_state.position = bind_data.row_count;
	}
	state.end = parallel_state.position;
	return true;
}

int PandasScanFunction::PandasProgress(ClientContext &context, const FunctionData *bind_data_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	if (bind_data.row_count == 0) {
		return 100;
	}
	auto percentage = bind_data.lines_read * 100 / bind_data.row_count;
	return percentage;
}

void PandasScanFunction::PandasScanFuncParallel(ClientContext &context, const FunctionData *bind_data,
                                                FunctionOperatorData *operator_state, DataChunk *input,
                                                DataChunk &output, ParallelState *parallel_state_p) {
	//! FIXME: Have specialized parallel function from pandas scan here
	PandasScanFunc(context, bind_data, operator_state, input, output);
}

//! The main pandas scan function: note that this can be called in parallel without the GIL
//! hence this needs to be GIL-safe, i.e. no methods that create Python objects are allowed
void PandasScanFunction::PandasScanFunc(ClientContext &context, const FunctionData *bind_data,
                                        FunctionOperatorData *operator_state, DataChunk *input, DataChunk &output) {
	if (!operator_state) {
		return;
	}
	auto &data = (PandasScanFunctionData &)*bind_data;
	auto &state = (PandasScanState &)*operator_state;

	if (state.start >= state.end) {
		return;
	}
	idx_t this_count = std::min((idx_t)STANDARD_VECTOR_SIZE, state.end - state.start);
	output.SetCardinality(this_count);
	for (idx_t idx = 0; idx < state.column_ids.size(); idx++) {
		auto col_idx = state.column_ids[idx];
		if (col_idx == COLUMN_IDENTIFIER_ROW_ID) {
			output.data[idx].Sequence(state.start, this_count);
		} else {
			VectorConversion::NumpyToDuckDB(data.pandas_bind_data[col_idx], data.pandas_bind_data[col_idx].numpy_col,
			                                this_count, state.start, output.data[idx]);
		}
	}
	state.start += this_count;
	data.lines_read += this_count;
}

unique_ptr<NodeStatistics> PandasScanFunction::PandasScanCardinality(ClientContext &context,
                                                                     const FunctionData *bind_data) {
	auto &data = (PandasScanFunctionData &)*bind_data;
	return make_unique<NodeStatistics>(data.row_count, data.row_count);
}

} // namespace duckdb


return bind_data.row_count / PANDAS_PARTITION_COUNT + 1;
}

#include "duckdb_python/pandas_scan.hpp"
#include "duckdb_python/array_wrapper.hpp"
#include "duckdb/parallel/parallel_state.hpp"
#include "utf8proc_wrapper.hpp"
#include "duckdb/common/types/timestamp.hpp"
#include "duckdb_python/vector_conversion.hpp"
#include "duckdb/main/client_context.hpp"

#include "duckdb/common/atomic.hpp"

namespace duckdb {

struct PandasScanFunctionData : public TableFunctionData {
	PandasScanFunctionData(py::handle df, idx_t row_count, vector<PandasColumnBindData> pandas_bind_data,
	                       vector<LogicalType> sql_types)
	    : df(df), row_count(row_count), lines_read(0), pandas_bind_data(move(pandas_bind_data)),
	      sql_types(move(sql_types)) {
	}
	py::handle df;
	idx_t row_count;
	atomic<idx_t> lines_read;
	vector<PandasColumnBindData> pandas_bind_data;
	vector<LogicalType> sql_types;

	~PandasScanFunctionData() override {
		py::gil_scoped_acquire acquire;
		pandas_bind_data.clear();
	}
};

struct PandasScanState : public FunctionOperatorData {
	PandasScanState(idx_t start, idx_t end) : start(start), end(end) {
	}

	idx_t start;
	idx_t end;
	vector<column_t> column_ids;
};

struct ParallelPandasScanState : public ParallelState {
	ParallelPandasScanState() : position(0) {
	}

	std::mutex lock;
	idx_t position;
};

PandasScanFunction::PandasScanFunction()
    : TableFunction("pandas_scan", {LogicalType::POINTER}, PandasScanFunc, PandasScanBind, PandasScanInit, nullptr,
                    nullptr, nullptr, PandasScanCardinality, nullptr, nullptr, PandasScanMaxThreads,
                    PandasScanInitParallelState, PandasScanFuncParallel, PandasScanParallelInit,
                    PandasScanParallelStateNext, true, false, PandasProgress) {
}

unique_ptr<FunctionData> PandasScanFunction::PandasScanBind(ClientContext &context, vector<Value> &inputs,
                                                            unordered_map<string, Value> &named_parameters,
                                                            vector<LogicalType> &input_table_types,
                                                            vector<string> &input_table_names,
                                                            vector<LogicalType> &return_types, vector<string> &names) {
	py::gil_scoped_acquire acquire;
	py::handle df((PyObject *)(inputs[0].GetPointer()));

	vector<PandasColumnBindData> pandas_bind_data;
	VectorConversion::BindPandas(df, pandas_bind_data, return_types, names);

	auto df_columns = py::list(df.attr("columns"));
	auto get_fun = df.attr("__getitem__");

	idx_t row_count = py::len(get_fun(df_columns[0]));
	return make_unique<PandasScanFunctionData>(df, row_count, move(pandas_bind_data), return_types);
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanInit(ClientContext &context,
                                                                    const FunctionData *bind_data_p,
                                                                    const vector<column_t> &column_ids,
                                                                    TableFilterCollection *filters) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto result = make_unique<PandasScanState>(0, bind_data.row_count);
	result->column_ids = column_ids;
	return move(result);
}

idx_t PandasScanFunction::PandasScanMaxThreads(ClientContext &context, const FunctionData *bind_data_p) {
	if (context.verify_parallelism) {
		return context.db->NumberOfThreads();
	}
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;

unique_ptr<ParallelState> PandasScanFunction::PandasScanInitParallelState(ClientContext &context,
                                                                          const FunctionData *bind_data_p,
                                                                          const vector<column_t> &column_ids,
                                                                          TableFilterCollection *filters) {
	return make_unique<ParallelPandasScanState>();
}

unique_ptr<FunctionOperatorData> PandasScanFunction::PandasScanParallelInit(ClientContext &context,
                                                                            const FunctionData *bind_data_p,
                                                                            ParallelState *state,
                                                                            const vector<column_t> &column_ids,
                                                                            TableFilterCollection *filters) {
	auto result = make_unique<PandasScanState>(0, 0);
	result->column_ids = column_ids;
	if (!PandasScanParallelStateNext(context, bind_data_p, result.get(), state)) {
		return nullptr;
	}
	return move(result);
}

bool PandasScanFunction::PandasScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p,
                                                     FunctionOperatorData *operator_state,
                                                     ParallelState *parallel_state_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	auto &parallel_state = (ParallelPandasScanState &)*parallel_state_p;
	auto &state = (PandasScanState &)*operator_state;

	lock_guard<mutex> parallel_lock(parallel_state.lock);
	if (parallel_state.position >= bind_data.row_count) {
		return false;
	}
	state.start = parallel_state.position;
	parallel_state.position += PANDAS_PARTITION_COUNT;
	if (parallel_state.position > bind_data.row_count) {
		parallel_state.position = bind_data.row_count;
	}
	state.end = parallel_state.position;
	return true;
}

int PandasScanFunction::PandasProgress(ClientContext &context, const FunctionData *bind_data_p) {
	auto &bind_data = (const PandasScanFunctionData &)*bind_data_p;
	if (bind_data.row_count == 0) {
		return 100;
	}
	auto percentage = bind_data.lines_read * 100 / bind_data.row_count;
	return percentage;
}

void PandasScanFunction::PandasScanFuncParallel(ClientContext &context, const FunctionData *bind_data,
                                                FunctionOperatorData *operator_state, DataChunk *input,
                                                DataChunk &output, ParallelState *parallel_state_p) {
	//! FIXME: Have specialized parallel function from pandas scan here
	PandasScanFunc(context, bind_data, operator_state, input, output);
}

//! The main pandas scan function: note that this can be called in parallel without the GIL
//! hence this needs to be GIL-safe, i.e. no methods that create Python objects are allowed
void PandasScanFunction::PandasScanFunc(ClientContext &context, const FunctionData *bind_data,
                                        FunctionOperatorData *operator_state, DataChunk *input, DataChunk &output) {
	if (!operator_state) {
		return;
	}
	auto &data = (PandasScanFunctionData &)*bind_data;
	auto &state = (PandasScanState &)*operator_state;

	if (state.start >= state.end) {
		return;
	}
	idx_t this_count = std::min((idx_t)STANDARD_VECTOR_SIZE, state.end - state.start);
	output.SetCardinality(this_count);
	for (idx_t idx = 0; idx < state.column_ids.size(); idx++) {
		auto col_idx = state.column_ids[idx];
		if (col_idx == COLUMN_IDENTIFIER_ROW_ID) {
			output.data[idx].Sequence(state.start, this_count);
		} else {
			VectorConversion::NumpyToDuckDB(data.pandas_bind_data[col_idx], data.pandas_bind_data[col_idx].numpy_col,
			                                this_count, state.start, output.data[idx]);
		}
	}
	state.start += this_count;
	data.lines_read += this_count;
}

unique_ptr<NodeStatistics> PandasScanFunction::PandasScanCardinality(ClientContext &context,
                                                                     const FunctionData *bind_data) {
	auto &data = (PandasScanFunctionData &)*bind_data;
	return make_unique<NodeStatistics>(data.row_count, data.row_count);
}

} // namespace duckdb