blob: 3c706305db5b442cb1925a43c44a8a483a16bf75 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>
#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <sstream>
#include "ignite/ignite.h"
#include "ignite/ignition.h"
#include "ignite/examples/person.h"
#include "ignite/examples/organization.h"
using namespace ignite;
using namespace examples;
/**
* This example populates cache with sample data and runs several SQL queries
* over this data using system ODBC API and Apache Ignite ODBC driver.
*
* To run this example you should first install ODBC driver as described in
* README file for the ODBC driver project.
*
* After all pre-requirements are fulfilled just build project as described
* in README and run resulting file.
*
* Note, that all fields which used in queries must be listed in config file
* under queryEntities property of the caches. You can find config file in
* config directory: cpp/examples/odbc-example/config/example-odbc.xml
*
* In addition to all the fields listed under QueryEntity bean, each table
* have two special predefined fields: _key and _val, which represent links
* to whole key and value objects. In some queries _key column is used. Key
* in our case works like an ID for the row and it should always present in
* INSERT statements.
*/
/** Read buffer size. */
enum { ODBC_BUFFER_SIZE = 1024 };
/**
* Represents simple string buffer.
*/
struct OdbcStringBuffer
{
SQLCHAR buffer[ODBC_BUFFER_SIZE];
SQLLEN reallen;
};
/**
* Print result set returned by query.
*
* @param stmt Statement.
*/
void PrintOdbcResultSet(SQLHSTMT stmt)
{
SQLSMALLINT columnsCnt = 0;
// Getting number of columns in result set.
SQLNumResultCols(stmt, &columnsCnt);
std::vector<OdbcStringBuffer> columns(columnsCnt);
// Binding colums.
for (SQLSMALLINT i = 0; i < columnsCnt; ++i)
SQLBindCol(stmt, i + 1, SQL_CHAR, columns[i].buffer, ODBC_BUFFER_SIZE, &columns[i].reallen);
while (true)
{
SQLRETURN ret = SQLFetch(stmt);
if (!SQL_SUCCEEDED(ret))
break;
std::cout << ">>> ";
for (size_t i = 0; i < columns.size(); ++i)
std::cout << std::setw(16) << std::left << columns[i].buffer << " ";
std::cout << std::endl;
}
}
/**
* Extract error message.
*
* @param handleType Type of the handle.
* @param handle Handle.
* @return Error message.
*/
std::string GetOdbcErrorMessage(SQLSMALLINT handleType, SQLHANDLE handle)
{
SQLCHAR sqlstate[7] = {};
SQLINTEGER nativeCode;
SQLCHAR message[ODBC_BUFFER_SIZE];
SQLSMALLINT reallen = 0;
SQLGetDiagRec(handleType, handle, 1, sqlstate, &nativeCode, message, ODBC_BUFFER_SIZE, &reallen);
return std::string(reinterpret_cast<char*>(sqlstate)) + ": " +
std::string(reinterpret_cast<char*>(message), reallen);
}
/**
* Extract error from ODBC handle and throw it as IgniteError.
*
* @param handleType Type of the handle.
* @param handle Handle.
* @param msg Error message.
*/
void ThrowOdbcError(SQLSMALLINT handleType, SQLHANDLE handle, std::string msg)
{
std::stringstream builder;
builder << msg << ": " << GetOdbcErrorMessage(handleType, handle);
std::string errorMsg = builder.str();
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, errorMsg.c_str());
}
/**
* Fetch cache data using ODBC interface.
*/
void GetDataWithOdbc(SQLHDBC dbc, const std::string& query)
{
SQLHSTMT stmt;
// Allocate a statement handle
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
std::vector<SQLCHAR> buf(query.begin(), query.end());
SQLRETURN ret = SQLExecDirect(stmt, &buf[0], static_cast<SQLSMALLINT>(buf.size()));
if (SQL_SUCCEEDED(ret))
PrintOdbcResultSet(stmt);
else
std::cerr << "Failed to execute query: " << GetOdbcErrorMessage(SQL_HANDLE_STMT, stmt) << std::endl;
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
/**
* Populate Person cache with sample data.
*
* @param dbc Database connection.
*/
void PopulatePerson(SQLHDBC dbc)
{
SQLHSTMT stmt;
// Allocate a statement handle
SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to allocate statement handle");
try
{
SQLCHAR query[] =
"INSERT INTO Person (id, orgIdAff, orgId, firstName, lastName, resume, salary) "
"VALUES (?, ?, ?, ?, ?, ?, ?)";
ret = SQLPrepare(stmt, query, static_cast<SQLSMALLINT>(sizeof(query)));
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to prepare query");
// Binding columns.
int64_t keyId = 0;
int64_t keyOrgIdAff = 0;
int64_t orgId = 0;
char firstName[1024] = { 0 };
SQLLEN firstNameLen = SQL_NTS;
char lastName[1024] = { 0 };
SQLLEN lastNameLen = SQL_NTS;
char resume[1024] = { 0 };
SQLLEN resumeLen = SQL_NTS;
double salary = 0.0;
ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &keyId, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &keyOrgIdAff, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &orgId, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
sizeof(firstName), sizeof(firstName), firstName, 0, &firstNameLen);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
sizeof(lastName), sizeof(lastName), lastName, 0, &lastNameLen);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,
sizeof(resume), sizeof(resume), resume, 0, &resumeLen);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &salary, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
// Filling cache.
keyId = 1;
keyOrgIdAff = 1;
orgId = keyOrgIdAff;
strncpy(firstName, "John", sizeof(firstName));
strncpy(lastName, "Doe", sizeof(lastName));
strncpy(resume, "Master Degree.", sizeof(resume));
salary = 2200.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 1;
orgId = keyOrgIdAff;
strncpy(firstName, "Jane", sizeof(firstName));
strncpy(lastName, "Doe", sizeof(lastName));
strncpy(resume, "Bachelor Degree.", sizeof(resume));
salary = 1300.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 2;
orgId = keyOrgIdAff;
strncpy(firstName, "John", sizeof(firstName));
strncpy(lastName, "Smith", sizeof(lastName));
strncpy(resume, "Bachelor Degree.", sizeof(resume));
salary = 1700.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 2;
orgId = keyOrgIdAff;
strncpy(firstName, "Jane", sizeof(firstName));
strncpy(lastName, "Smith", sizeof(lastName));
strncpy(resume, "Master Degree.", sizeof(resume));
salary = 2500.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 2;
orgId = keyOrgIdAff;
strncpy(firstName, "John", sizeof(firstName));
strncpy(lastName, "Roe", sizeof(lastName));
strncpy(resume, "Bachelor Degree.", sizeof(resume));
salary = 1500.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 2;
orgId = keyOrgIdAff;
strncpy(firstName, "Jane", sizeof(firstName));
strncpy(lastName, "Roe", sizeof(lastName));
strncpy(resume, "Bachelor Degree.", sizeof(resume));
salary = 1000.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 1;
orgId = keyOrgIdAff;
strncpy(firstName, "Richard", sizeof(firstName));
strncpy(lastName, "Miles", sizeof(lastName));
strncpy(resume, "Master Degree.", sizeof(resume));
salary = 2400.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
ret = SQLMoreResults(stmt);
if (ret != SQL_NO_DATA)
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "No more data expected");
++keyId;
keyOrgIdAff = 2;
orgId = keyOrgIdAff;
strncpy(firstName, "Mary", sizeof(firstName));
strncpy(lastName, "Major", sizeof(lastName));
strncpy(resume, "Bachelor Degree.", sizeof(resume));
salary = 900.0;
ret = SQLExecute(stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute prepared statement");
}
catch(...)
{
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
// Re-throwing expection.
throw;
}
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
/**
* Populate Organization cache with sample data.
*
* @param dbc Database connection.
*/
void PopulateOrganization(SQLHDBC dbc)
{
SQLHSTMT stmt;
// Allocate a statement handle
SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to allocate statement handle");
try
{
SQLCHAR query1[] = "INSERT INTO \"Organization\".Organization (_key, name) VALUES (1L, 'Microsoft')";
ret = SQLExecDirect(stmt, query1, static_cast<SQLSMALLINT>(sizeof(query1)));
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute query");
SQLFreeStmt(stmt, SQL_CLOSE);
SQLCHAR query2[] = "INSERT INTO \"Organization\".Organization (_key, name) VALUES (2L, 'Red Cross')";
ret = SQLExecDirect(stmt, query2, static_cast<SQLSMALLINT>(sizeof(query2)));
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute query");
}
catch (...)
{
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
// Re-throwing expection.
throw;
}
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
/**
* Adjust salary for specified employee.
*
* @param dbc Database connection.
* @param id Person id.
* @param salary New salary.
*/
void AdjustSalary(SQLHDBC dbc, int64_t id, double salary)
{
SQLHSTMT stmt;
// Allocate a statement handle
SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to allocate statement handle");
try
{
SQLCHAR query[] = "UPDATE Person SET salary=? WHERE id=?";
ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_DOUBLE, 0, 0, &salary, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &id, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLExecDirect(stmt, query, static_cast<SQLSMALLINT>(sizeof(query)));
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute query");
}
catch (...)
{
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
// Re-throwing expection.
throw;
}
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
/**
* Remove specified person.
*
* @param dbc Database connection.
* @param id Person id.
*/
void DeletePerson(SQLHDBC dbc, int64_t id)
{
SQLHSTMT stmt;
// Allocate a statement handle
SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to allocate statement handle");
try
{
SQLCHAR query[] = "DELETE FROM Person WHERE id=?";
ret = SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_BIGINT, 0, 0, &id, 0, 0);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to bind parameter");
ret = SQLExecDirect(stmt, query, static_cast<SQLSMALLINT>(sizeof(query)));
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_STMT, stmt, "Failed to execute query");
}
catch (...)
{
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
// Re-throwing expection.
throw;
}
// Releasing statement handle.
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
}
/**
* Query tables.
*
* @param dbc Database connection.
*/
void QueryData(SQLHDBC dbc)
{
std::cout << std::endl;
std::cout << ">>> Getting list of persons:" << std::endl;
GetDataWithOdbc(dbc, "SELECT firstName, lastName, resume, salary FROM Person ORDER BY id");
std::cout << std::endl;
std::cout << ">>> Getting average salary by degree:" << std::endl;
GetDataWithOdbc(dbc, "SELECT resume, AVG(salary) FROM Person GROUP BY resume ORDER BY resume");
std::cout << std::endl;
std::cout << ">>> Getting people with organizations:" << std::endl;
GetDataWithOdbc(dbc,
"SELECT firstName, lastName, Organization.name FROM Person "
"INNER JOIN \"Organization\".Organization ON Person.orgId = Organization._KEY "
"ORDER BY Person.id");
}
/**
* Program entry point.
*
* @return Exit code.
*/
int main()
{
IgniteConfiguration cfg;
cfg.springCfgPath = "platforms/cpp/examples/odbc-example/config/example-odbc.xml";
try
{
// Start a node.
Ignite grid = Ignition::Start(cfg);
SQLHENV env;
// Allocate an environment handle
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
// We want ODBC 3 support
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, reinterpret_cast<void*>(SQL_OV_ODBC3), 0);
SQLHDBC dbc;
// Allocate a connection handle
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
// Combining connect string
std::string connectStr = "DRIVER={Apache Ignite};SERVER=localhost;PORT=10800;SCHEMA=Person;";
SQLCHAR outstr[ODBC_BUFFER_SIZE];
SQLSMALLINT outstrlen;
// Connecting to ODBC server.
SQLRETURN ret = SQLDriverConnect(dbc, NULL, reinterpret_cast<SQLCHAR*>(&connectStr[0]),
static_cast<SQLSMALLINT>(connectStr.size()), outstr, sizeof(outstr), &outstrlen, SQL_DRIVER_COMPLETE);
if (!SQL_SUCCEEDED(ret))
ThrowOdbcError(SQL_HANDLE_DBC, dbc, "Failed to connect");
std::cout << std::endl;
std::cout << ">>> Cache ODBC example started." << std::endl;
std::cout << std::endl;
// Populate caches.
PopulatePerson(dbc);
PopulateOrganization(dbc);
QueryData(dbc);
std::cout << std::endl;
std::cout << std::endl;
std::cout << ">>> Adjusted salary for Mary Major. Querying again." << std::endl;
AdjustSalary(dbc, 8, 1200.0);
QueryData(dbc);
std::cout << std::endl;
std::cout << std::endl;
std::cout << ">>> Removing several employees. Querying again." << std::endl;
DeletePerson(dbc, 4);
DeletePerson(dbc, 5);
QueryData(dbc);
// Disconneting from the server.
SQLDisconnect(dbc);
// Releasing allocated handles.
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
// Stop node.
Ignition::StopAll(false);
}
catch (IgniteError& err)
{
std::cout << "An error occurred: " << err.GetText() << std::endl;
return err.GetCode();
}
std::cout << std::endl;
std::cout << ">>> Example finished, press 'Enter' to exit ..." << std::endl;
std::cout << std::endl;
std::cin.get();
return 0;
}