I am trying to use SAP OLAP BAPI for a very simple task. I want to connect to a SAP BW server, send an MDX query, get the result and disconnect. While I seem to have no problems connecting and disconnecting from the server, sending a query and retrieving results seem to be rather non-trivial tasks that I am seeking help with.
According to the SAP documentation, I need to use MDDataSetBW object, by first creating the query object using CreateObject and then retrieving the results using GetAxisInfo and GetCellData.
Right now I am stuck at the very first step of just creating the query object. The documentation states that CreateObject has one import parameter CommandText and two export parameters DataSetID and Return. The problem is that according to SAP Documentation CommandText parameter is a table with a single column called LINE. So basically it's just an MDX query, with each line of the query being a value of the LINE column of each row of this table. The question is how do I pass it? Can anyone point me to a C++ example code that does something like this?
Here's my current prototype code:
#include "stdafx.h"
using namespace std;
RFC_HANDLE Logon()
{
RFC_CONNOPT_R3ONLY conn_options;
conn_options.hostname = SAP_LOGON::Hostname;
conn_options.sysnr = 0;
conn_options.gateway_host = nullptr;
conn_options.gateway_service = nullptr;
RFC_OPTIONS options;
options.mode = RFC_MODE_R3ONLY;
options.destination = SAP_LOGON::Destination;
options.client = SAP_LOGON::Client;
options.user = SAP_LOGON::User;
options.password = SAP_LOGON::Password;
options.language = SAP_LOGON::Language;
options.trace = 0;
options.connopt = &conn_options;
RFC_HANDLE hConn = RfcOpen(&options);
if (hConn == RFC_HANDLE_NULL)
ErrorHandler(_T("RfcOpen"));
return hConn;
}
void MakeStringParam(RFC_PARAMETER& param, rfc_char_t* name, rfc_char_t* buf, unsigned long size)
{
param.name = name;
param.nlen = (unsigned int)wcslen(name);
param.type = TYPC;
param.leng = size;
param.addr = &buf;
}
RFC_HANDLE WIN_DLL_EXPORT_FLAGS CreateQuery(RFC_HANDLE hConn, rfc_char_t* query)
{
rfc_char_t datasetID[256];
rfc_char_t ret[256];
RFC_PARAMETER exporting[3];
MakeStringParam(exporting[0], _T("DataSetID"), datasetID, 256);
MakeStringParam(exporting[1], _T("Return"), ret, 256);
exporting[2].name = nullptr;
// TODO: is it needed?
RFC_PARAMETER importing[2];
MakeStringParam(importing[0], _T("CommandText"), ret, 256);
importing[1].name = nullptr;
// TODO: put query into the table parameter
RFC_TABLE tables[2];
tables[0].name = _T("CommandText");
tables[1].name = nullptr;
rfc_char_t* ex = nullptr;
RFC_RC rc = RfcCallReceive(hConn, _T("BAPI_MDDATASETBW_CREATE_OBJECT"), exporting, importing, tables, &ex);
if (rc == RFC_OK)
{
// TODO: retrieve query handle here
RFC_HANDLE result = RFC_HANDLE_NULL;
return result;
}
return RFC_HANDLE_NULL;
}
int _tmain(int argc, _TCHAR* argv[])
{
RfcInit();
RFC_HANDLE hConn = Logon();
rfc_char_t* query = _T("\
SELECT\
{[Measures].[DA70CEZJ18267Q9RS4XFK7J32]} ON COLUMNS,\
{[0APO_LOCNO].[LEVEL01].AllMembers} ON ROWS\
FROM [0APO_C01/IDES_APO_04]");
RFC_HANDLE hQuery = CreateQuery(hConn, query);
// TODO: retrieve query results using
// - MDDataSetBW.GetAxisData
// - MDDataSetBW.GetAxisInfo
// - MDDataSetBW.GetCellData
RfcClose(hConn);
return 0;
}
Related
I have getResults function and executing a query in C++.
int ABC::getResult(char *query)
{
_CommandPtr pCom;
_bstr_t lv_query;
lv_query = (_bstr_t) sqlQuery;
try
{
pCom.CreateInstance(__uuidof(Command));
pCom->ActiveConnection = pConn;
pCom->CommandType = adCmdText;
pCom->CommandText = lv_query;
pCom->CommandTimeout = 60;
RecordSet = pCom->Execute(NULL, NULL, adCmdText);
}
catch(_com_error &e)
{
_bstr_t bstrSource(e.Source());
_bstr_t bstrDescription(e.Description());
sprintf_s(errStr,sizeof(errStr), "Exception:Failed to get detail from database.");
return ERR_EXCEPPTION;
}
I have to copy the _RecordsetPtr RecordSet of this function which is a public member of ABD class.
I am doing like below:
ABC class will make the connection to Database.
ABC mConnect;
mConnect.getResults(query);
_RecordsetPtr lv_set = std::move(mConnect.RecordSet);
mConnect.CloseConnection();
Above std::move is not working , and I get lv_set as null;
I want to achieve that once I take recordset in local copy , I want to close the DB connection and proceed with local recordset to fetch the results
Is there any way to achieve it?
Update:
no had not implement the move ctor
Rather we have similar function as below,
and it give proper RecordSet when I do
_RecordsetPtr lv_set = std::move(mConnect.RecordSet);
int ABC::getResults (char *sqlQuery)
{
_bstr_t lv_query;
lv_query = (_bstr_t) sqlQuery;
CString lv_error_log;
// Define Other Variables
HRESULT hr = S_OK;
_variant_t index;
index.vt = VT_I2;
try
{
// Open recordset from Authors table.
hr = RecordSet.CreateInstance(__uuidof(Recordset));
if(FAILED(hr))
{
return -1;
}
RecordSet->CursorLocation = adUseClient;
// Pass the Cursor type and Lock type to the Recordset.
RecordSet->Open (lv_query, strConn, adOpenDynamic,
adLockBatchOptimistic, adCmdText);
}
return QRY_EXECUTE_SUCCESS;
}
I'm using the OLE DB bulk copy operation against a SQL Server database but I'm having trouble while loading data into bit columns - they are always populated with true!
I created a simple reproduction program from Microsoft's sample code with the snippet that I adjusted below. My program includes a little SQL script to create the destination table. I had to download and install the x64 version of the SQL Server OLE DB driver to build this.
// Set up custom bindings.
oneBinding.dwPart = DBPART_VALUE | DBPART_LENGTH | DBPART_STATUS;
oneBinding.iOrdinal = 1;
oneBinding.pTypeInfo = NULL;
oneBinding.obValue = ulOffset + offsetof(COLUMNDATA, bData);
oneBinding.obLength = ulOffset + offsetof(COLUMNDATA, dwLength);
oneBinding.obStatus = ulOffset + offsetof(COLUMNDATA, dwStatus);
oneBinding.cbMaxLen = 1; // Size of varchar column.
oneBinding.pTypeInfo = NULL;
oneBinding.pObject = NULL;
oneBinding.pBindExt = NULL;
oneBinding.dwFlags = 0;
oneBinding.eParamIO = DBPARAMIO_NOTPARAM;
oneBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED;
oneBinding.bPrecision = 0;
oneBinding.bScale = 0;
oneBinding.wType = DBTYPE_BOOL;
ulOffset = oneBinding.cbMaxLen + offsetof(COLUMNDATA, bData);
ulOffset = ROUND_UP(ulOffset, COLUMN_ALIGNVAL);
if (FAILED(hr =
pIFastLoad->QueryInterface(IID_IAccessor, (void**)&pIAccessor)))
return hr;
if (FAILED(hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
1,
&oneBinding,
ulOffset,
&hAccessor,
&oneStatus)))
return hr;
// Set up memory buffer.
pData = new BYTE[40];
if (!(pData /* = new BYTE[40]*/)) {
hr = E_FAIL;
goto cleanup;
}
pcolData = (COLUMNDATA*)pData;
pcolData->dwLength = 1;
pcolData->dwStatus = 0;
for (i = 0; i < 10; i++)
{
if (i % 2 == 0)
{
pcolData->bData[0] = 0x00;
}
else
{
pcolData->bData[0] = 0xFF;
}
if (FAILED(hr = pIFastLoad->InsertRow(hAccessor, pData)))
goto cleanup;
}
It's entirely likely that I'm putting the wrong value into the buffer, or have some other constant value incorrect. I did find an article describing the safety of various data type conversions and it looks like byte to bool is safe... but how would the buffer know what kind of data I'm putting in there if it's just a byte array?
Figured this out, I had not correctly switched over the demo from loading strings to fixed-width values. For strings, the data blob gets a 1-width pointer to the value whereas fixed-width values get the actual data.
So my COLUMNDATA struct now looks like this:
// How to lay out each column in memory.
struct COLUMNDATA {
DBLENGTH dwLength; // Length of data (not space allocated).
DWORD dwStatus; // Status of column.
VARIANT_BOOL bData; // Value, or if a string, a pointer to the value.
};
With the relevant length fix here:
pcolData = (COLUMNDATA*)pData;
pcolData->dwLength = sizeof(VARIANT_BOOL); // using a short.. make it two
pcolData->dwStatus = DBSTATUS_S_OK; // Indicates that the data value is to be used, not null
And the little value-setting for loop looks like this:
for (i = 0; i < 10; i++)
{
if (i % 2 == 0)
{
pcolData->bData = VARIANT_TRUE;
}
else
{
pcolData->bData = VARIANT_FALSE;
}
if (FAILED(hr = pIFastLoad->InsertRow(hAccessor, pData)))
goto cleanup;
}
I've updated the repository with the working code. I was inspired to make this change after reading the documentation for the obValue property.
I am new in c++ and I use for GUI wxWidget. My question is how this code for wxListCtrl change to working code for virtual ListCtrl...
now my code is below and work but I have to try with virtual style
wxString SQL = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + table + "'";
int gstate = mysql_query(conn,SQL);
if(!gstate){
res = mysql_store_result(conn);
int num = 0;
lcData->SetColumnWidth(0,wxLIST_AUTOSIZE_USEHEADER);
lcData->InsertColumn(0,"rb.");
while(row = mysql_fetch_row(res)){
lcData->SetColumnWidth(num+1, wxLIST_AUTOSIZE_USEHEADER);
lcData->InsertColumn(num+1,row[0]);
num++;
}
if(res != NULL)
mysql_free_result(res);
}
SQL = tcSQL->GetValue();
tcLog->AppendText(SQL+"\n");
gstate = mysql_query(conn,SQL);
if(!gstate){
res = mysql_store_result(conn);
long num_field = mysql_num_fields(res);
long num = 0;
lcData->SetColumnWidth(0,40);
while(row = mysql_fetch_row(res)){
lcData->InsertItem(num, wxString::Format(_T("%4d"),num+1));
for(long i = 0; i < num_field; i++){
lcData->SetItem(num,i+1,row[i] );
}
num++;
}
if(res != NULL)
mysql_free_result(res);
Where I compiling the program and run I have this error :
SharedScreens
Thx.
YuMERA
The idea of a virtual wxListCtrl is that you don't put the data into it, but provide the data on request, when the control needs it. So to use the virtual control, you need to change the structure of your code and avoid getting all the items from the database in the first place and, instead, retrieve them on demand from your overridden OnGetItemText() method. Of course, in order to be able to override it, you must derive your own class from wxListCtrl first and you also need to tell the control how many items is it going to have (which you would obtain from select count(*) ... query in your case).
I have the following code here that executes a query. Originally, I used SQL Injection to return row results. Hearing I should use parametrization, I rearranged my code and read the MySQL docs on how to do so. I'm using MySQL's C library in a C++ application.
However, it's not returning the results.
I know my SQL statement is 100% fine. It has been tested. The only thing I changed was changing %d (injection) to ?, which accepts the player's ID.
This returns -1. It's a SELECT statement though, so maybe it's normal?
// Get the number of affected rows
affected_rows = mysql_stmt_affected_rows(m_stmt);
This returns 2. This is correct. I have two fields being returned.
// Store the field count
m_fieldCount = mysql_field_count(&m_conn);
This returns 0 (success)
if (mysql_stmt_store_result(m_stmt))
Finally, this returns null.
m_result = mysql_store_result(&m_conn);
I need m_result so I can read the rows. "mysql_stmt_store_result" sounds similar, but doesn't return MYSQL_RESULT.
m_result = mysql_store_result(&m_conn);
/// <summary>
/// Executes a query.
/// </summary>
/// <param name="query">The query to execute.</param>
/// <returns>Returns true on success, else false.</returns>
bool SQLConnection::executeQuery_New(const char *query)
{
int param_count = 0;
int affected_rows = 0;
// Validate connection.
if (!m_connected)
return false;
// Initialize the statement
m_stmt = mysql_stmt_init(&m_conn);
if (!m_stmt) {
fprintf(stderr, " mysql_stmt_init(), out of memory\n");
return false;
}
// Prepare the statement
if (mysql_stmt_prepare(m_stmt, query, strlen(query))) {
fprintf(stderr, " mysql_stmt_prepare(), INSERT failed\n");
fprintf(stderr, " %s\n", mysql_stmt_error(m_stmt));
return false;
}
// Get the parameter count from the statement
param_count = mysql_stmt_param_count(m_stmt);
if (param_count != m_bind.size()) {
fprintf(stderr, " invalid parameter count returned by MySQL\n");
return false;
}
// Bind buffers
// The parameter binds are stored in std::vector<MYSQL_BIND>
// I need to convert std::vector<MYSQL_BIND> m_bind to MYSQL_BIND *bnd
MYSQL_BIND *bind = new MYSQL_BIND[m_bind.size() + 1];
memset(bind, 0, sizeof(bind) * m_bind.size());
for (int i = 0; i < param_count; i++)
bind[i] = m_bind[i];
if (mysql_stmt_bind_param(m_stmt, &bind[0]))
{
fprintf(stderr, " mysql_stmt_bind_param() failed\n");
fprintf(stderr, " %s\n", mysql_stmt_error(m_stmt));
return false;
}
// Execute the query
if (mysql_stmt_execute(m_stmt)) {
fprintf(stderr, " mysql_stmt_execute(), 1 failed\n");
fprintf(stderr, " %s\n", mysql_stmt_error(m_stmt));
return false;
}
// Get the number of affected rows
affected_rows = mysql_stmt_affected_rows(m_stmt);
//if (affected_rows == -1) {
// fprintf(stderr, " mysql_stmt_execute(), 1 failed\n");
// fprintf(stderr, " %s\n", mysql_stmt_error(m_stmt));
// return false;
//}
// Store the field count
m_fieldCount = mysql_field_count(&m_conn);
// Store the result
if (mysql_stmt_store_result(m_stmt))
{
fprintf(stderr, " failed retrieving result\n");
fprintf(stderr, " %s\n", mysql_error(&m_conn));
int d = mysql_errno(&m_conn);
return false;
}
// This looks similar to the last above statement, but I need m_result. I used mysql_store_result earlier when using injection and it worked fine, but here in this case it returns null.
m_result = mysql_store_result(&m_conn);
// Close the statement
if (mysql_stmt_close(m_stmt)) {
/* mysql_stmt_close() invalidates stmt, so call */
/* mysql_error(mysql) rather than mysql_stmt_error(stmt) */
fprintf(stderr, " failed while closing the statement\n");
fprintf(stderr, " %s\n", mysql_error(&m_conn));
return false;
}
// Delete bind array
if (bind) {
delete[] bind;
bind = NULL;
}
return true;
}
How I'm adding an int parameter (player's id):
void SQLConnection::addParam(int buffer, enum_field_types type, unsigned long length)
{
MYSQL_BIND bind;
memset(&bind, 0, sizeof(bind));
bind.buffer = (char *)&buffer;
bind.buffer_type = type;
bind.is_null = 0;
bind.length = &length;
m_bind.push_back(bind);
}
My variables and their types:
class SQLConnection
{
private:
MYSQL m_conn;
MYSQL_ROW m_row;
MYSQL_RES *m_result;
char m_errorMessage[ERROR_MSG_MAX];
bool m_connected;
MYSQL_STMT *m_stmt;
std::vector<MYSQL_BIND> m_bind;
int m_fieldCount;
// ...
And finally its calling function at the end of the SQL statement:
...WHERE player_id = ?;");
conn.addParam(m_id, MYSQL_TYPE_LONGLONG, 0);
if (!conn.executeQuery_New(buffer)) {
conn.close();
return "";
}
// Close the connection.
conn.close();
std::string s = conn.getField("max_value_column_name");
The error code I get is 2014:
https://dev.mysql.com/doc/refman/5.7/en/commands-out-of-sync.html
Just for the sake of interest, this is a prior function I used. This worked fine for injection. Using the new function above with parameterization is the one causing the issues.
bool SQLConnection::executeQuery(const char *query)
{
// Validate connection.
if (!m_connected)
return false;
// Execute the query
int status = mysql_query(&m_conn, query);
if (status != 0) {
sprintf(m_errorMessage, "Error: %s", mysql_error(&m_conn));
return false;
}
// Store the result
m_result = mysql_store_result(&m_conn);
return true;
}
After I started having language religious wars in my head about using C# over C++, I thought I'd give one last attempt here. Any help is appreciated.
Edit:
This is how I read in column names prior to parameterization (maybe this code needs to be updated after calling mysql_stmt_store_result(m_stmt)?
std::string SQLConnection::getField(const char *fieldName)
{
MYSQL_FIELD *field = NULL;
unsigned int name_field = 0;
mysql_stmt_data_seek(m_stmt, 0);
mysql_stmt_fetch_column(m_stmt, &bind, 0, 0);
//mysql_data_seek(m_result, 0);
//mysql_field_seek(m_result, 0);
const unsigned int num_fields = mysql_stmt_field_count(m_stmt);
// const unsigned int num_fields = mysql_num_fields(m_result);
std::vector<char *> headers(num_fields);
for (unsigned int i = 0; (field = mysql_fetch_field(m_result)); i++)
{
headers[i] = field->name;
if (strcmp(fieldName, headers[i]) == 0)
name_field = i;
}
while ((m_row = mysql_fetch_row(m_result))) {
return std::string(m_row[name_field]);
}
return "";
}
Edit:
What I'm finding is in this last function there are equivalent functions for statements, like mysql_num_fields() is mysql_stmt_field_count(). I'm thinking these need to be updated because it's using m_stmt and not m_result anymore, which gives reason to update the functions so m_stmt is used. It's not very apparent how to update the second half of the function though.
You may need a better understanding of how stmt works.You can't get the final results by mysql_store_result() when you're using stmt.
You shoud bind several buffers for the statement you're using to accept the result set. You can finish this by mysql_stmt_bind_result(), just like mysql_stmt_bind_param().
Then you can use the buffers bound by mysql_stmt_bind_result() to return row data. You can finish this by mysql_stmt_fetch().
Do the fetch method repeatedly, so you can get the whole result set row by row.
The basic sequence of calls:
mysql_stmt_init
mysql_stmt_prepare
mysql_stmt_bind_param
mysql_stmt_execute
mysql_stmt_bind_result
mysql_stmt_store_result
mysql_stmt_fetch (repeatedly, row by row)
mysql_stmt_free_result
It works for me.
It's a long time since I finished this part of my project, you'd better read the manual carefully and find more examples of stmt.
Sorry for my poor English. Good luck!
I've written a C++ wrapper function for libxml2 that makes it easy for me to do queries on an XML document:
bool XPathQuery(
const std::string& doc,
const std::string& query,
XPathResults& results);
But I have a problem: I need to be able to do another XPath query on the results of my first query.
Currently I do this by storing the entire subdocument in my XPathResult object, and then I pass XPathResult.subdoc into the XPathQuery function. This is awfully inefficient.
So I'm wondering ... does libxml2 provide anything that would make it easy to store the context of an xpath query (a reference to a node, perhaps?) and then perform another query using that reference as the xpath root?
You should reuse the xmlXPathContext and just change its node member.
#include <stdio.h>
#include <libxml/xpath.h>
#include <libxml/xmlerror.h>
static xmlChar buffer[] =
"<?xml version=\"1.0\"?>\n<foo><bar><baz/></bar></foo>\n";
int
main()
{
const char *expr = "/foo";
xmlDocPtr document = xmlReadDoc(buffer,NULL,NULL,XML_PARSE_COMPACT);
xmlXPathContextPtr ctx = xmlXPathNewContext(document);
//ctx->node = xmlDocGetRootElement(document);
xmlXPathCompExprPtr p = xmlXPathCtxtCompile(ctx, (xmlChar *)expr);
xmlXPathObjectPtr res = xmlXPathCompiledEval(p, ctx);
if (XPATH_NODESET != res->type)
return 1;
fprintf(stderr, "Got object from first query:\n");
xmlXPathDebugDumpObject(stdout, res, 0);
xmlNodeSetPtr ns = res->nodesetval;
if (!ns->nodeNr)
return 1;
ctx->node = ns->nodeTab[0];
xmlXPathFreeObject(res);
expr = "bar/baz";
p = xmlXPathCtxtCompile(ctx, (xmlChar *)expr);
res = xmlXPathCompiledEval(p, ctx);
if (XPATH_NODESET != res->type)
return 1;
ns = res->nodesetval;
if (!ns->nodeNr)
return 1;
fprintf(stderr, "Got object from second query:\n");
xmlXPathDebugDumpObject(stdout, res, 0);
xmlXPathFreeContext(ctx);
return 0;
}