Output parameters not populating MSSQL ODBC - c++

I'm having problems getting this code to populate output parameters with MSSQL ODBC 10.0 client driver and 9.0 as well. I can confirm my binding direction is being set properly.
I am also calling SQLMoreResults, my stored procedure has NOCOUNT set and works fine under another ODBC-based library.
Unfortunately once I get to my execute statement, it succeeds but SQLMoreResults always says there are no more results, and I realize that those parameters aren't populated until it has gone through all of the result sets. The one output parameter I use is a bigint.
Unfortunately I don't know all of the intricacies of ODBC development and there must be something important I'm missing. I do try to reuse my statement handle but I reset it after my first call to SQLProcedureColumns and remove bound variables. Then I rebind.
Any ideas as to where I'm going astray?
bool ODBCConnection::Execute()
{
LLOG("Execute " << (void *)this << " " << (void *)session);
if(session->hstmt == SQL_NULL_HANDLE)
return false;
if(IsCurrent())
session->current = NULL;
session->FlushConnections();
last_insert_table.Clear();
number.Clear();
text.Clear();
time.Clear();
CParser p(statement);
/* parse for stored procedure */
bool isStoredProcedure = false;
if (p.Char('{'))
{
p.Spaces();
String procedure_name;
p.Id("?");
p.Id("=");
if (p.Id("call") || p.Id("CALL")) {
procedure_name = p.ReadId();
isStoredProcedure = true;
//Cout() << "Proc name: " << procedure_name << "\n";
}
SQLSetEnvAttr(session->henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_INTEGER);
SDWORD cbValue5;
SDWORD cbValue4;
SQLSMALLINT ParameterType = SQL_PARAM_INPUT;
if (!IsOk(SQLProcedureColumns (
session->hstmt,
NULL,
0,
NULL,
0,
(SQLCHAR *)~procedure_name,
procedure_name.GetLength(),
NULL,
0
))) {
SQLFreeStmt(session->hstmt, SQL_CLOSE);
return false;
}
char parameter_name [20];
if (!IsOk(SQLBindCol(
session->hstmt,
4, // Column 4 returns column name
SQL_C_CHAR,
parameter_name,
sizeof(parameter_name),
&cbValue4
))) {
}
if (!IsOk(SQLBindCol(
session->hstmt,
5, // Column 5 returns whether parameter is input or output
SQL_C_SHORT,
&ParameterType,
0,
&cbValue5
))) {
}
int i = 0;
while (SQLFetch(session->hstmt) == SQL_SUCCESS) {
Param& p = param[i];
Cout() << ParameterType << "\n";
/*switch (ParameterType) {
case SQL_PARAM_INPUT:
case SQL_PARAM_OUTPUT:
case SQL_PARAM_INPUT_OUTPUT:
p.direction = ParameterType;
break;
case 5:
p.direction = SQL_PARAM_OUTPUT;
break;
default:
break;
}*/
if(ParameterType == 5)
p.direction = SQL_PARAM_OUTPUT;
else
p.direction = ParameterType;
i++;
}
SQLFreeStmt(session->hstmt, SQL_CLOSE);
SQLFreeStmt(session->hstmt, SQL_RESET_PARAMS);
SQLFreeStmt(session->hstmt, SQL_UNBIND);
}
if((p.Id("insert") || p.Id("INSERT")) && (p.Id("into") || p.Id("INTO")) && p.IsId())
last_insert_table = p.ReadId();
if(!IsOk(SQLPrepare(session->hstmt, (SQLCHAR *)~statement, statement.GetCount())))
return false;
parse = false;
bparam = param;
param.Clear();
for(int i = 0; i < bparam.GetCount(); i++) {
Param& p = bparam[i];
SQLSMALLINT DataType;
SQLULEN ParameterSize;
SQLSMALLINT DecimalDigits;
SQLSMALLINT Nullable;
Cout() << "Direction: " << p.direction << "\n";
Cout() << "Length: " << p.li << "\n";
if(!IsOk(SQLDescribeParam(session->hstmt, i + 1, &DataType, &ParameterSize, &DecimalDigits, &Nullable)))
return false;
if(!IsOk(SQLBindParameter(session->hstmt, i + 1, p.direction, p.ctype, DataType,
ParameterSize, DecimalDigits, (SQLPOINTER)~p.data, p.data.GetLength(),
&p.li)))
return false;
}
SQLSMALLINT ncol;
if(!isStoredProcedure)
{
if(!IsOk(SQLExecute(session->hstmt)) || !IsOk(SQLNumResultCols(session->hstmt, &ncol))) {
Cout() << "SQLExecute crashed\n";
SQLFreeStmt(session->hstmt, SQL_CLOSE);
return false;
}
}
else
{
Cout() << "statement: " << statement << "\n";
if(!IsOk(SQLExecute(session->hstmt))) {
Cout() << "SQLExecute crashed\n";
SQLFreeStmt(session->hstmt, SQL_CLOSE);
return false;
}
Cout() << "Calling SQLMoreResults...\n";
//SQLFreeStmt(session->hstmt, SQL_CLOSE);
int iReturn = SQLMoreResults(session->hstmt);
Cout() << "SQLMoreResults return code: " << iReturn << "\n";
while (iReturn == SQL_SUCCESS || iReturn == SQL_SUCCESS_WITH_INFO)
{
iReturn = SQLMoreResults(session->hstmt);
} ;
//SQLFreeStmt(session->hstmt, SQL_RESET_PARAMS);
//SQLFreeStmt(session->hstmt, SQL_UNBIND);
ncol = 0;
}
session->current = this;
info.Clear();
binary.Clear();
for(int i = 1; i <= ncol; i++) {
SQLCHAR ColumnName[256];
SQLSMALLINT NameLength;
SQLSMALLINT DataType;
SQLULEN ColumnSize;
SQLSMALLINT DecimalDigits;
SQLSMALLINT Nullable;
if(!IsOk(SQLDescribeCol(session->hstmt, i, ColumnName, 255, &NameLength, &DataType,
&ColumnSize, &DecimalDigits, &Nullable)))
return false;
binary.Add(false);
SqlColumnInfo& f = info.Add();
f.nullable = Nullable != SQL_NO_NULLS;
f.binary = false;
f.precision = DecimalDigits;
f.scale = 0;
f.width = ColumnSize;
f.name = (char *)ColumnName;
switch(DataType) {
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_BIT:
case SQL_TINYINT:
f.type = DOUBLE_V;
break;
case SQL_BIGINT:
f.type = INT64_V;
break;
case SQL_TYPE_DATE:
case SQL_TYPE_TIMESTAMP:
f.type = TIME_V;
break;
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
f.type = STRING_V;
f.binary = true;
binary.Top() = true;
break;
default:
f.type = STRING_V;
break;
}
}
SQLLEN rc;
SQLRowCount(session->hstmt, &rc);
rowsprocessed = rc;
return true;
}

Have you tried turning on SQLProfile? Maybe fire that up and watch the actual SQL hitting the database then take those SQL statements and run directly in SSMS to confirm it's doing what you expect.

Never mind, I was being stupid. My parameter binding was actually bound to a copy of the data and not the actual data itself. Thanks to everyone who tried to help.

Related

How to use Windows API to realize line by line printing

I want to use Windows API to realize the line by line printing of the printer.
For example, if there is only one message at present, print one message. Then the printer waits and the paper stops printing. When there is information next time, continue printing on the same paper.
With windows API, I can only print by page in the following way. I don't know how to print by line. After one line is finished, the printing is suspended and the next line continues to be printed after waiting for a new task.
bool CMFCApplication1Dlg::printTicket(CString& szPrinter, CString&
szContent)
{
static DOCINFO di = { sizeof(DOCINFO), (LPTSTR)TEXT("printer"),NULL };
HDC hdcPrint = CreateDC(nullptr, szPrinter.GetBuffer(), nullptr, nullptr);
if (hdcPrint != 0)
{
if (StartDoc(hdcPrint, &di) > 0)
{
StartPage(hdcPrint);
SaveDC(hdcPrint);
int xDistance = 20;
int yDistance = 20;
LOGFONT logFont = { 0 };
logFont.lfCharSet = DEFAULT_CHARSET;
logFont.lfPitchAndFamily = DEFAULT_PITCH;
logFont.lfWeight = FW_NORMAL;
logFont.lfHeight = 60;
logFont.lfWeight = 36;
HFONT hFont = CreateFontIndirect(&logFont);
SelectObject(hdcPrint, hFont);
TextOut(hdcPrint, xDistance, yDistance, szContent.GetBuffer(), szContent.GetLength());
RestoreDC(hdcPrint, -1);
EndPage(hdcPrint);
EndDoc(hdcPrint);
}
else
{
cout << "StartDoc failed!" << endl;
string errorCode = to_string(GetLastError());
cout << "Error code is:" << errorCode << endl;
return false;
}
DeleteDC(hdcPrint);
}
else
{
cout << "CreateDC failed!" << endl;
string errorCode = to_string(GetLastError());
cout << "Error code is :" << errorCode << endl;
return false;
}
return true;
}
bool CMFCApplication1Dlg::SetPrinterParameters(CString& szPrinter)
{
HANDLE hPrinter = nullptr;
PRINTER_INFO_2* pi2 = nullptr;
DEVMODE* pDevMode = nullptr;
PRINTER_DEFAULTS pd;
DWORD dwNeeded = 0;
BOOL bFlag;
LONG lFlag;
WCHAR szDevName[MAX_PATH] = L"";
DWORD dwLength = MAX_PATH;
if (!GetDefaultPrinter(szDevName, &dwLength))
{
return false;
}
szPrinter = szDevName;
return true;
}
Maybe this has something to do with the printer driver?
I use C# winspool library and can't meet this requirement too, maybe I don't know how to use them.
Hope your help.

Custom resolution on GCP VM (with T4 GPU on Windows Server 2019)

I am currently searching for a way to set a fully custom resolution on a Windows Server 2019 VM with GPU (T4, with grid licence, and virtual workstation grid drivers) with C++.
I have tried different way to achieve this, I can make this work on my laptop, but seems to have some limitations on GCP VMs (or Windows Server limitation).
I have tried to do this with ChangeDisplaySettings/ChangeDisplaySettingsEx (winuser.h), I can change to a known resolution, but can't make it works with a custom one (not even with CDS_ENABLE_UNSAFE_MODE).
DWORD deviceIndex = 0;
DISPLAY_DEVICE displayDevice = { 0 };
displayDevice.cb = sizeof(DISPLAY_DEVICE);
while (EnumDisplayDevices(NULL, deviceIndex, &displayDevice, 0)) {
deviceIndex++;
DEVMODE dm = { 0 };
dm.dmSize = sizeof(DEVMODE);
DEVMODE finalDm = { 0 };
finalDm.dmSize = sizeof(DEVMODE);
//Check if able to retrieve current settings
if (!EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &dm)) {
continue;
}
//Check if there is a difference in resolution list if UNSAFE_MODE is enabled or not (it seems to not change anything)
int result = ChangeDisplaySettingsEx(displayDevice.DeviceName, &dm, 0, CDS_DISABLE_UNSAFE_MODES, NULL);
std::cout << "CDS_DISABLE_UNSAFE_MODE" << std::endl;
if (result == DISP_CHANGE_SUCCESSFUL) {
for (int i = 0; EnumDisplaySettings(displayDevice.DeviceName, i, &dm) != 0; i++) {
if (dm.dmBitsPerPel == 32) {
std::cout << i << ". Found available resolution : " << dm.dmPelsWidth << " x " << dm.dmPelsHeight << " x " << dm.dmBitsPerPel << " # " << dm.dmDisplayFrequency << std::endl;
}
}
}
result = ChangeDisplaySettingsEx(displayDevice.DeviceName, &dm, 0, CDS_ENABLE_UNSAFE_MODES, NULL);
std::cout << "CDS_ENABLE_UNSAFE_MODE" << std::endl;
if (result == DISP_CHANGE_SUCCESSFUL) {
for (int i = 0; EnumDisplaySettings(displayDevice.DeviceName, i, &dm) != 0; i++) {
if (dm.dmBitsPerPel == 32) {
std::cout << i << ". Found available resolution : " << dm.dmPelsWidth << " x " << dm.dmPelsHeight << " x " << dm.dmBitsPerPel << " # " << dm.dmDisplayFrequency << std::endl;
}
}
}
std::cout << "Please enter width : ";
int width, height;
std::cin >> width;
std::cout << "Please enter height : ";
std::cin >> height;
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
if (width > height) {
dm.dmDisplayOrientation = DMDO_DEFAULT;
}
else {
dm.dmDisplayOrientation = DMDO_90;
}
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYORIENTATION;
//result = ChangeDisplaySettingsEx(displayDevice.DeviceName, &dm, NULL, CDS_TEST, NULL);
result = ChangeDisplaySettingsEx(displayDevice.DeviceName, &dm, NULL, 0, NULL);
if (result != DISP_CHANGE_SUCCESSFUL) {
std::cout << "Impossible to ChangeDisplaySettings" << endl;
}
else {
std::cout << "OK" << endl;
}
break;
}
I then take a look at NVAPI, and same here, I can make it works on my PC but still nothing on the GCP VMs... I have found a way to make NVAPI create and use custom resolution on my local PC, but can't make it works on GCP VM once again... (Code example found here)
NvAPI_Status result = NVAPI_ERROR;
NvU32 primaryDisplayId = 0;
//Testing resolution
int horizontal = 1920, vertical = 1090;
result = NvAPI_Initialize();
if (result != NVAPI_OK) {
printf("Could not initialize NvAPI");
return false;
}
MONITORINFOEX monInfo;
HMONITOR hMon;
const POINT ptZero = { 0, 0 };
// determine the location of the primary monitor
hMon = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
ZeroMemory(&monInfo, sizeof(monInfo));
monInfo.cbSize = sizeof(monInfo);
GetMonitorInfo(hMon, &monInfo);
result = NvAPI_DISP_GetGDIPrimaryDisplayId(&primaryDisplayId);
if (result != NVAPI_OK) {
printf("Could not get display ID from device");
NvAPI_Unload();
return false;
}
NvU32 deviceCount = 0;
NV_CUSTOM_DISPLAY cd[NVAPI_MAX_DISPLAYS] = { 0 };
float refreshRate = 60;
// timing computation (to get timing that suits the changes made)
NV_TIMING_FLAG flag = { 0 };
NV_TIMING_INPUT timing = { 0 };
timing.version = NV_TIMING_INPUT_VER;
timing.height = vertical;
timing.width = horizontal;
timing.rr = refreshRate;
timing.flag = flag;
timing.type = NV_TIMING_OVERRIDE_CVT_RB;
result = NvAPI_DISP_GetTiming(primaryDisplayId, &timing, &cd[0].timing);
if (result != NVAPI_OK) {
printf("Failed to get timing for display"); // failed to get custom display timing
NvAPI_Unload();
return false;
}
cd[0].width = horizontal;
cd[0].height = vertical;
cd[0].xRatio = 1;
cd[0].yRatio = 1;
cd[0].srcPartition = { 0, 0, 1.0, 1.0 };
cd[0].depth = 32;
cd[0].version = NV_CUSTOM_DISPLAY_VER;
cd[0].colorFormat = NV_FORMAT_A8R8G8B8;
//Returns NVAPI_ERROR on GCP but NVAPI_OK on my laptop
result = NvAPI_DISP_TryCustomDisplay(&primaryDisplayId, 1, cd);
if (result != NVAPI_OK) {
printf("Could not set custom resolution");
NvAPI_DISP_RevertCustomDisplayTrial(&primaryDisplayId, 1);
NvAPI_Unload();
return false;
}
else {
NvAPI_DISP_SaveCustomDisplay(&primaryDisplayId, 1, true, true);
}
This part works perfectly well on my laptop, I can use a new dynamic resolution (It works with 1920x400, 1920x500, 1920x600), but not on my GCP VM, this parts :
NvAPI_DISP_TryCustomDisplay(&primaryDisplayId, 1, cd);
always returns NVAPI_ERROR
I have found another trick, I can edit this registry entry : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video{RANDOM_ID}\0001\NV_Modes
(Here is an old pdf, after some testing, it seems it is still working this way)
If I add some resolution using NVAPI, I can then set through ChangeDisplaySettingsEx function this resolution (it needs GPU driver restart, or Windows restart be able to change to a fresh new added resolution).
But I need to be able to rotate screen, playing with "dmDisplayOrientation", and it does not seem to work on GCP VM once again, if I authorize for example 1920x1090 I can set resolution to this, but cannot set 1090x1920 with a "dmDisplayOrientation = DMDO_90" (even if I authorize 1090x1920 too...)
So if anyone found a way, or have any idea on how to do this, it would be great, I am running out of idea right now...

Receipt printing via serial port (RS-232)

I'd like to write a c++ program(windows console application) to print receipts from a server (via websocket) to a locally connected(via rs-232) receipt printer(NCR 7197 rev 1.0).
I got both the websocket connection and the serial handle operating and ready.
My problem is, that I can't find any example to guide me through the process of printing, or how to even begin. I mean, do I have to write some bytes or read any before I start passing the "document" to the printer, or do I need to write any config bytes, or anything.
If someone has any suggestions to where to start, or any example preferably in c++, I will be thankful.
I came across the solution that I developed way back than, while cleaning my old projects folder. Seeing the multiple hundred of views on this question, I wanted to post an answer. Here is my solution, but it might be outdated:
The function to connect to a device through a COM port:
// Serial port connection handle connect method
HANDLE connect_h(wchar_t* s_port, int s_flowcontrol, int s_baudrate, int s_bytesize, int s_stopbits, int s_parity) {
wchar_t* port = s_port;
std::wstring ws(port);
std::string port_w(ws.begin(), ws.end());
// Open serial port
HANDLE hSerial;
hSerial = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(hSerial == INVALID_HANDLE_VALUE) {
if(GetLastError() == ERROR_FILE_NOT_FOUND) {
// Serial port does not exist. Inform user.
std::cout << "Error: Serial port does not exists. Port: " << std::endl;
}
// Some other error occurred. Inform user.
std::cout << "Error: Cannot connect to port: " + port_w << std::endl;
std::cout << "Error code: " + GetLastError() << std::endl;
}
// Do some basic settings
DCB dcbSerialParams = { 0 };
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
if(!GetCommState(hSerial, &dcbSerialParams)) {
// Error getting state
std::cout << "Error: Cannot get port state. Port: " + port_w << std::endl;
std::cout << "Error code: " + GetLastError() << std::endl;
}
dcbSerialParams.BaudRate = s_baudrate;
dcbSerialParams.ByteSize = s_bytesize;
dcbSerialParams.StopBits = s_stopbits;
dcbSerialParams.Parity = s_parity;
// If flowcontrol set to XON/XOFF
if(s_flowcontrol == 1) {
dcbSerialParams.fDtrControl = DTR_CONTROL_DISABLE;
dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE;
dcbSerialParams.fOutX = true;
dcbSerialParams.fInX = true;
dcbSerialParams.XonChar = 0x11;
dcbSerialParams.XoffChar = 0x16;
}
if(!SetCommState(hSerial, &dcbSerialParams)) {
// error setting serial port state
std::cout << "Error: Cannot set port state. Port: " + port_w << std::endl;
std::cout << "Error code: " + GetLastError() << std::endl;
}
// Set timeouts
COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hSerial, &timeouts)) {
// Error occureed. Inform user
std::cout << "Error: Cannot set port timeouts. Port: " + port_w << std::endl;
std::cout << "Error code: " + GetLastError() << std::endl;
}
return hSerial;
}
Closing the serial port connection:
// Serial port connection handle close method
void close_h(const HANDLE &hSerial) {
CloseHandle(hSerial);
}
The function to print through the connected COM port:
// Printing recipe via serial port handle
std::string set_print(const HANDLE &hSerial, std::string &document) {
std::string result = "";
std::string printable = "";
// Format flags
bool _bold_flag = false;
bool _underline_flag = false;
bool _italic_flag = false;
// Process document for printing
for(int i = 0; i < (int)document.length(); ++i) {
if(document[i] == '\\') {
switch (document[i + 1]) {
// new line
case 'n':
printable.push_back((char)(0x0A));
i++;
break;
// underline format begin/end
case 'u':
if(!_underline_flag) {
printable.push_back((char)(0x1B));
printable.push_back((char)(0x2D));
printable.push_back('1');
_underline_flag = true;
}
else {
printable.push_back((char)(0x1B));
printable.push_back((char)(0x2D));
printable.push_back('0');
_underline_flag = false;
}
i++;
break;
// bold format begin/end
case 'b':
if (!_bold_flag) {
printable.push_back((char)(0x1B));
printable.push_back((char)(0x47));
printable.push_back('1');
_bold_flag = true;
}
else {
printable.push_back((char)(0x1B));
printable.push_back((char)(0x47));
printable.push_back('0');
_bold_flag = false;
}
i++;
break;
// italic format begin/end
case 'i':
if (!_italic_flag) {
printable.push_back((char)(0x1B));
printable.push_back((char)(0x49));
printable.push_back('1');
_italic_flag = true;
}
else {
printable.push_back((char)(0x1B));
printable.push_back((char)(0x49));
printable.push_back('0');
_italic_flag = false;
}
i++;
break;
// if not recognized
default:
printable.push_back(document[i]);
break;
}
}
else {
printable.push_back(document[i]);
}
}
// Additional push
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
printable.push_back((char)(0x0A));
// Add EOF bytes
printable.push_back((char)(0x03));
printable.push_back((char)(0x04));
// Add knife cut command
printable.push_back((char)(0x19));
printable.push_back((char)(0x1B));
printable.push_back((char)(0x76));
// Convert to hexadecimal
int* hex_print = new int[printable.length()];
for(int i = 0; i < (int)printable.length(); ++i) {
hex_print[i] = (int)printable[i];
}
// Print
for(int i = 0; i < (int)printable.length(); ++i) {
int rq[1] = { hex_print[i] };
DWORD rqBytesWritten = 0;
if(!WriteFile(hSerial, rq, 1, &rqBytesWritten, NULL)) {
// error occurred. Report to user.
std::cout << "Error: Can't write byte. written: " + rqBytesWritten << std::endl;
}
}
// return status
return result;
}
And a test main function, to demonstrate the usage of these functions:
// MAIN()
int main(int argc, char* argv[]) {
// Create connection on COM port 1
HANDLE sh_printer = connect_h(L"COM1", 0, 38400);
// Print a string povided in first argument
set_print(sh_printer, args[1]);
// Close the COM port connection
close_h(sh_printer);
return 0;
}
I hope I could be of any help to anyone, who tires to use this ancient way of printing a receipt. :)

Calling Mysql 5.6 stored procedure from mariadb client

Hi I have written some code for calling a stored procedure in a MySQL database using a MariaDB client, however the stmt->bind parameter is not being populated with the necessary MYSQL_BIND array for receiving the necessary data.
Am I doing something wrong or is there a bug with the driver? Malloc'ing the data does not break the implementation, but MySQL has its own more efficient means of managing memory for these arrays than malloc.
If you are asking, I am using and abusing a switch statement, as I would like this function to be made eventually asynchronous in a cooroutine functor class working against a pool of connections, where a state value is used to control where we are in the switch statement.
//the function that does all the heavy lifting for persistence
xt::data::io::ret_val mysql_model::call_stored_proc(xt::data::model_factory& factory,
const xt::object& obj,
const xt::object* p_prev_obj,
xt::data::io_call_back*& cb,
const char* command)
{
MYSQL *mysql = NULL;
int state = 0;
MYSQL_STMT* stmt = NULL;
int status = 0;
size_t parameter_length = 0;
std::vector<MYSQL_BIND> ps_params;
xt::data::io::ret_val ret = xt::data::io::io_success;
xt::string sql("CALL ");
xt::messaging::event_type type = xt::messaging::new_object;
if(std::strcmp(command, "update") == 0)
{
sql += m_update_proc;
type = xt::messaging::update_object;
}
else if(std::strcmp(command, "delete") == 0)
{
sql += m_delete_proc;
type = xt::messaging::delete_object;
}
else
{
sql += m_insert_proc;
assert(command == "insert");
}
sql += "(?";
for(size_t i = 0;i < m_p_mem_row_set->get_field_handlers().size();i++)
{
sql += ",?";
}
sql += ")";
switch(state)
{
case 0:
mysql= mysql_init(NULL);
if(!mysql_set_server_option(mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON))
{
show_error(mysql);
return xt::data::io::io_exception;
}
if (!mysql_real_connect(mysql, m_server.data(),
m_user_name.data(),
m_password.data(),
m_schema.data(),
0,
NULL,
0))
{
LOG_ERROR3("Could not connect error(%s) [%s] \"%s\"", xt::longToString(mysql_errno(mysql)), mysql_sqlstate(mysql), mysql_error(mysql));
ret = xt::data::io::io_exception;
break;
}
stmt = mysql_stmt_init(mysql);
if(!stmt)
{
LOG_ERROR("Could not initialize statement\n");
ret = xt::data::io::io_exception;
break;
}
stmt->bind = NULL;
if(mysql_stmt_prepare(stmt, sql.data(), (unsigned long)sql.size()))
{
LOG_ERROR3("Could not prepare statement error(%s) [%s] \"%s\"", xt::longToString(mysql_errno(mysql)), mysql_sqlstate(mysql), mysql_error(mysql));
ret = xt::data::io::io_exception;
break;
}
parameter_length = m_p_mem_row_set->get_field_handlers().size() + 2;
ps_params.resize(parameter_length);
{
xt::data::io::ret_val r = prepare_memset(ps_params, m_p_mem_row_set->get_field_handlers(), obj, command);
if(r != xt::data::io::io_success)
{
ret = xt::data::io::io_exception;
break;
}
}
//reuse params so offset from 1
if(mysql_stmt_bind_param(stmt, &ps_params[1]))
{
LOG_ERROR2("Could not bind parameters error: %s (errno: %s)\n", mysql_stmt_error(stmt), xt::longToString(mysql_stmt_errno(stmt)));
ret = xt::data::io::io_exception;
break;
}
status = mysql_stmt_execute(stmt);
if(status)
{
LOG_ERROR2("Could not execute stored procedure error: %s (errno: %s)\n", mysql_stmt_error(stmt), xt::longToString(mysql_stmt_errno(stmt)));
ret = xt::data::io::io_exception;
break;
}
{
/* the column count is > 0 if there is a result set */
/* 0 if the result is only the final status packet */
int num_fields = mysql_stmt_field_count(stmt);
if (num_fields > 0)
{
if(num_fields != parameter_length)
{
LOG_ERROR("parameter fields do not match returned resultset");
ret = xt::data::io::io_exception;
break;
}
//buffer to be a write target
for(size_t i = 0;i < parameter_length;i++)
{
if(ps_params[i].buffer_type == MYSQL_TYPE_STRING || ps_params[i].buffer_type == MYSQL_TYPE_DECIMAL)
{
ps_params[i].buffer = (char *) new char[ps_params[i].buffer_length + 1];
memset(ps_params[i].buffer, 0, ps_params[i].buffer_length + 1);
ps_params[i].length = new unsigned long;
*ps_params[i].length = ps_params[i].buffer_length;
}
}
#pragma message("hack!!!!! bind not being allocated for stored proc")
if (!(stmt->bind= (MYSQL_BIND *)malloc(stmt->field_count * sizeof(MYSQL_BIND))))
{
// SET_CLIENT_STMT_ERROR(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
LOG_ERROR("Out of memory");
ret = xt::data::io::io_exception;
break;
}
status = mysql_stmt_bind_result(stmt, &ps_params[0]);
if(status)
{
LOG_ERROR2("Could not bind results error: %s (errno: %s)\n", mysql_stmt_error(stmt), xt::longToString(mysql_stmt_errno(stmt)));
ret = xt::data::io::io_exception;
break;
}
status = mysql_stmt_fetch(stmt);
if (status == 1 || status == MYSQL_NO_DATA)
{
LOG_ERROR("No data returned from stored proc");
ret = xt::data::io::io_exception;
break;
}
else if(status)
{
LOG_ERROR2("Error on fetching results error: %s (errno: %s)\n", mysql_stmt_error(stmt), xt::longToString(mysql_stmt_errno(stmt)));
ret = xt::data::io::io_exception;
break;
}
else
{
ret = (xt::data::io::ret_val) *((int *) ps_params[0].buffer);
if(ret != xt::data::io::io_success)
{
const char* error_message = ((char *) ps_params[1].buffer);
LOG_ERROR(error_message);
// dont break; we want to clean memory up regardless
}
//now clean up memory
delete ps_params[1].length;
ps_params[1].length = NULL;
delete [] ps_params[1].buffer;
ps_params[1].buffer = NULL;
for (int i = 2; i < parameter_length && ret == xt::data::io::io_success; ++i)
{
rowset_item* item = m_p_mem_row_set->get_field_handlers()[i - 2];
switch (ps_params[i].buffer_type)
{
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_DECIMAL:
if(ret == xt::data::io::io_success)
{
((rowset_item_string_t<char>&)*item).set_data(static_cast<char*>(ps_params[i].buffer), const_cast<xt::object*>(&obj), 0);
}
delete ps_params[i].length;
ps_params[i].length = NULL;
delete [] ps_params[i].buffer;
ps_params[i].buffer = NULL;
break;
case MYSQL_TYPE_DATE:
delete (DATE_STRUCT*)ps_params[i].buffer;
ps_params[i].buffer = NULL;
break;
case MYSQL_TYPE_TIME:
delete (TIME_STRUCT*)ps_params[i].buffer;
ps_params[i].buffer = NULL;
break;
case MYSQL_TYPE_TIMESTAMP:
delete (TIMESTAMP_STRUCT*)ps_params[i].buffer;
ps_params[i].buffer = NULL;
break;
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
case MYSQL_TYPE_BIT:
break;
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_VAR_STRING:
default:
LOG_ERROR1("unexpected type (%s)",xt::longToString(ps_params[i].buffer_type));
assert(false);
ret = xt::data::io::io_exception;
break;
}
}
}
}
else
{
LOG_ERROR("no data returned from stored proc");
assert(false);
ret = xt::data::io::io_exception;
break;
}
}
default:
break;
}
if(stmt != NULL)
{
free(stmt->bind);
mysql_stmt_close(stmt);
}
if(mysql != NULL)
{
mysql_close(mysql);
}
cb->on_model_request_complete( ret,
type,
factory,
*this,
obj,
p_prev_obj,
true);
cb = NULL;
return ret;
}

How to use MySQL Embedded in a multithreaded environment?

I am writing a program that uses MySQLe as embedded backend. The database library is owned by an object called "Domain". This Domain object runs within the main thread.
The program launches another thread running a XML-RPC server (boost::thread and xmlrpc_c::serverAbyss). It is linked to the Domain object.
When the XML-RPC server makes the Domain object execute an SQL query the program crashes:
Program received signal: “EXC_BAD_ACCESS”.
[Switching to process 73191]
[Switching to process 73191]
Xcode could not locate source file: regex.cpp (line: 74)
When the master thread calls Domain object's method that executes SQL queries the program still runs.
/*
* Ports listening
*
* - create a Rpc_Server object
* - create a dedicated thread
*/
Rpc_Server server(&domain, &conf_params, &router);
boost::thread server_thread(boost::bind(&Rpc_Server::run, &server)); // This thread makes the server crash
/*
* Domain routine
*
* - Check for ready jobs every minute
*/
while (1) {
v_jobs jobs = domain.get_ready_jobs(conf_params.get_param("node_name")); // This method does NOT make the server crash
sleep(60);
}
Both the Domain object's methods and the Database object's methods lock a mutex to avoid multi access.
bool Mysql::execute(const std::string* query) {
MYSQL_RES* res;
MYSQL_ROW row;
if ( query == NULL )
return false;
this->updates_mutex.lock();
std::cout << query->c_str() << std::endl;
if ( mysql_query(this->mysql, query->c_str()) != 0 ) {
std::cerr << query << std::endl << mysql_error(this->mysql);
UNLOCK_MUTEX;
return false;
}
res = mysql_store_result(this->mysql);
if (res)
while ( ( row = mysql_fetch_row(res) ) )
for ( uint i=0 ; i < mysql_num_fields(res) ; i++ )
std::cout << row[i] << std::endl;
else
if ( mysql_field_count(this->mysql) != 0 ) {
std::cerr << "Erreur : " << mysql_error(this->mysql) << std::endl;
mysql_free_result(res);
this->updates_mutex.unlock();
return false;
}
mysql_free_result(res);
this->updates_mutex.unlock();
return true;
}
bool Domain::add_node(const std::string* running_node, const std::string* n, const int* w) {
std::string query;
this->updates_mutex.lock();
query = "START TRANSACTION;";
if ( this->database.execute(&query) == false ) {
this->updates_mutex.unlock();
return false;
}
query = "REPLACE INTO node (node_name,node_weight) VALUES ('";
query += n->c_str();
query += "','";
query += boost::lexical_cast<std::string>(*w);
query += "');";
if ( this->database.execute(&query) == false ) {
query = "ROLLBACK;";
this->database.execute(&query);
this->updates_mutex.unlock();
return false;
}
query = "COMMIT;"
if ( this->database.execute(&query) == false ) {
this->updates_mutex.unlock();
return false;
} else
this->updates_mutex.unlock();
return true;
}
The MySQLe is created there:
bool Mysql::prepare(const std::string* node_name, const std::string* db_skeleton) {
static char* server_args[] = {"this_program","--datadir=."};
static char* server_groups[] = {"embedded","server","this_program_SERVER",(char *)NULL};
std::string query("CREATE DATABASE IF NOT EXISTS ");
// DB init
if ( mysql_library_init(sizeof(server_args) / sizeof(char *), server_args, server_groups) )
std::cerr << "could not initialize MySQL library" << std::endl;
std::cout << "mysql init..." << std::endl;
if ( (this->mysql = mysql_init(NULL)) == NULL )
std::cerr << mysql_error(this->mysql) << std::endl;
if ( ! mysql_thread_safe() ) {
std::cerr << "MySQL is NOT theadsafe !" << std::endl;
return false;
}
mysql_options(this->mysql, MYSQL_READ_DEFAULT_GROUP, "embedded");
mysql_options(this->mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL);
mysql_real_connect(this->mysql, NULL, NULL, NULL, NULL, 0, NULL, 0);
// Creates the schema
query += this->translate_into_db(node_name);
query += ";";
if ( this->execute(&query) == false )
return false;
// Creates the schema
query = "CREATE SCHEMA IF NOT EXISTS ";
query += this->translate_into_db(node_name);
query += " DEFAULT CHARACTER SET latin1;";
this->execute(&query);
// Uses it
query = "USE " + this->translate_into_db(node_name) + ";";
this->execute(&query);
// Loads the skeleton from file
return this->load_file(db_skeleton->c_str());
}
Am I wrong somewhere?
Do you have an example to show me?
I found the solution to my problem. Each thread needs to initialize the MySQL environment. That is to say execute some mysql_* functions.
Here are the modified / new methods :
bool Mysql::atomic_execute(const std::string* query) {
MYSQL_RES* res;
MYSQL_ROW row;
boost::regex empty_string("^\\s+$", boost::regex::perl);
if ( query == NULL )
return false;
if ( query->empty() == true or boost::regex_match(*query, empty_string) == true ) {
std::cerr << "Error : query is empty !" << std::endl;
return false;
}
this->updates_mutex.lock();
if ( mysql_query(this->mysql, query->c_str()) != 0 ) {
std::cerr << query << std::endl << mysql_error(this->mysql);
this->updates_mutex.unlock();;
return false;
}
res = mysql_store_result(this->mysql);
if (res)
while ( ( row = mysql_fetch_row(res) ) )
for ( uint i=0 ; i < mysql_num_fields(res) ; i++ )
std::cout << row[i] << std::endl;
else
if ( mysql_field_count(this->mysql) != 0 ) {
std::cerr << "Erreur : " << mysql_error(this->mysql) << std::endl;
mysql_free_result(res);
this->updates_mutex.unlock();
return false;
}
mysql_free_result(res);
this->updates_mutex.unlock();
return true;
}
bool Mysql::standalone_execute(const v_queries* queries) {
MYSQL* local_mysql = this->init();
std::string query = "START TRANSACTION;";
if ( this->atomic_execute(&query) == false ) {
mysql_close(local_mysql);
return false;
}
BOOST_FOREACH(std::string q, *queries) {
std::cout << q.c_str() << std::endl;
if ( this->atomic_execute(&q) == false ) {
query = "ROLLBACK";
this->atomic_execute(&query);
mysql_close(local_mysql);
return false;
}
}
query = "COMMIT";
if ( this->atomic_execute(&query) == false ) {
mysql_close(local_mysql);
return false;
}
mysql_close(local_mysql);
return true;
}
MYSQL* Mysql::init() {
MYSQL* local_mysql;
local_mysql = mysql_init(this->mysql);
mysql_options(this->mysql, MYSQL_READ_DEFAULT_GROUP, "embedded");
mysql_options(this->mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL);
mysql_real_connect(local_mysql, NULL, NULL, NULL, NULL, 0, NULL, 0);
return local_mysql;
}
The atomic_execute method is used to send single queries to the server.
The standalone_execute method initializes a connection and a transaction, then it sends the whole queries to the server using atomic_execute.
I do not know if a ROLLBACK is useful in case of COMMIT's failure...
The code might need some improvements but it works.