1 /* 2 * Copyright (C) 2011 Emweb bv, Herent, Belgium. 3 * 4 * See the LICENSE file for terms of use. 5 * 6 * Originally contributed by Lukasz Matuszewski 7 */ 8 #include <Wt/Dbo/backend/Firebird.h> 9 10 #include "Wt/Dbo/Exception.h" 11 #include "Wt/Dbo/Logger.h" 12 #include "Wt/Dbo/StringStream.h" 13 14 #include <cstdio> 15 #include <ctime> 16 #include <iostream> 17 #include <sstream> 18 19 #ifdef WT_WIN32 20 #define snprintf _snprintf 21 #define timegm _mkgmtime 22 #endif 23 24 #include <ibpp.h> 25 26 namespace { 27 #ifndef WT_WIN32 28 thread_local std::tm local_tm; 29 #endif 30 thread_local_gmtime(const time_t * timep)31 std::tm *thread_local_gmtime(const time_t *timep) 32 { 33 #ifdef WT_WIN32 34 return std::gmtime(timep); // Already returns thread-local pointer 35 #else // !WT_WIN32 36 gmtime_r(timep, &local_tm); 37 return &local_tm; 38 #endif // WT_WIN32 39 } 40 41 static constexpr int MAX_VARCHAR_LENGTH = 32765; 42 } 43 44 namespace Wt 45 { 46 namespace Dbo 47 { 48 49 LOGGER("Dbo.backend.Firebird"); 50 51 namespace backend { 52 class FirebirdException : public ::Wt::Dbo::Exception 53 { 54 public: FirebirdException(const std::string & msg)55 FirebirdException(const std::string& msg) 56 : ::Wt::Dbo::Exception(msg) 57 { } 58 }; 59 60 class Firebird_impl { 61 public: 62 IBPP::Database m_db; 63 IBPP::Transaction m_tra; 64 ~Firebird_impl()65 ~Firebird_impl() 66 { 67 if (m_db->Connected()) 68 m_db->Disconnect(); 69 } 70 Firebird_impl()71 Firebird_impl() 72 { } 73 Firebird_impl(IBPP::Database db)74 Firebird_impl(IBPP::Database db) : 75 m_db(db) 76 { } 77 }; 78 79 class FirebirdStatement final : public SqlStatement 80 { 81 public: FirebirdStatement(Firebird & conn,const std::string & sql)82 FirebirdStatement(Firebird& conn, const std::string& sql) 83 : conn_(conn), 84 sql_(convertToNumberedPlaceholders(sql)) 85 { 86 lastId_ = -1; 87 row_ = affectedRows_ = 0; 88 columnCount_ = 0; 89 90 snprintf(name_, 64, "SQL%p%08X", (void*)this, rand()); 91 92 LOG_DEBUG(this << " for: " << sql_); 93 94 IBPP::Transaction tr = conn_.impl_->m_tra; 95 96 m_stmt = IBPP::StatementFactory(conn_.impl_->m_db, tr); 97 98 if (!m_stmt.intf()) 99 throw FirebirdException("Could not create a IBPP::Statement"); 100 101 try { 102 m_stmt->Prepare(sql_); 103 } catch(IBPP::LogicException &e) { 104 throw FirebirdException(e.what()); 105 } 106 107 columnCount_ = m_stmt->Columns(); 108 } 109 ~FirebirdStatement()110 virtual ~FirebirdStatement() 111 { } 112 reset()113 virtual void reset() override 114 { } 115 116 // The first column index is 1! bind(int column,const std::string & value)117 virtual void bind(int column, const std::string& value) override 118 { 119 LOG_DEBUG("bind: " << column << " " << value); 120 121 m_stmt->Set(column + 1, value); 122 } 123 bind(int column,short value)124 virtual void bind(int column, short value) override 125 { 126 LOG_DEBUG("bind: " << column << " " << value); 127 128 m_stmt->Set(column + 1, value); 129 } 130 bind(int column,int value)131 virtual void bind(int column, int value) override 132 { 133 LOG_DEBUG("bind: " << column << " " << value); 134 135 m_stmt->Set(column + 1, value); 136 } 137 bind(int column,long long value)138 virtual void bind(int column, long long value) override 139 { 140 LOG_DEBUG("bind: " << column << " " << value); 141 142 m_stmt->Set(column + 1, (int64_t) value); 143 } 144 bind(int column,float value)145 virtual void bind(int column, float value) override 146 { 147 LOG_DEBUG("bind: " << column << " " << value); 148 149 m_stmt->Set(column + 1, value); 150 } 151 bind(int column,double value)152 virtual void bind(int column, double value) override 153 { 154 LOG_DEBUG("bind: " << column << " " << value); 155 156 m_stmt->Set(column + 1, value); 157 } 158 getMilliSeconds(const std::chrono::system_clock::time_point & tp)159 int getMilliSeconds(const std::chrono::system_clock::time_point &tp) 160 { 161 std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()); 162 return ms.count()%1000; 163 } 164 bind(int column,const std::chrono::duration<int,std::milli> & value)165 virtual void bind(int column, 166 const std::chrono::duration<int, std::milli> & value) override 167 { 168 std::chrono::system_clock::time_point tp(value); 169 std::time_t time = std::chrono::system_clock::to_time_t(tp); 170 std::tm *tm = thread_local_gmtime(&time); 171 char mbstr[100]; 172 std::strftime(mbstr, sizeof(mbstr), "%Y-%b-%d %H:%M:%S", tm); 173 LOG_DEBUG("bind: " << column << " " << mbstr); 174 175 int h = tm->tm_hour; 176 int m = tm->tm_min; 177 int s = tm->tm_sec; 178 int ms = getMilliSeconds(std::chrono::system_clock::time_point(value)); 179 IBPP::Time t(h, m, s, ms * 10); 180 181 m_stmt->Set(column + 1, t); 182 } 183 bind(int column,const std::chrono::system_clock::time_point & value,SqlDateTimeType type)184 virtual void bind(int column, 185 const std::chrono::system_clock::time_point& value, 186 SqlDateTimeType type) override 187 { 188 std::time_t t = std::chrono::system_clock::to_time_t(value); 189 std::tm *tm = thread_local_gmtime(&t); 190 char mbstr[100]; 191 std::strftime(mbstr, sizeof(mbstr), "%Y-%b-%d %H:%M:%S", tm); 192 LOG_DEBUG("bind: " << column << " " << mbstr); 193 194 if (type == SqlDateTimeType::Date) { 195 IBPP::Date idate(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); 196 197 m_stmt->Set(column + 1, idate); 198 } else { 199 int h = tm->tm_hour; 200 int m = tm->tm_min; 201 int s = tm->tm_sec; 202 int ms = getMilliSeconds(value); 203 204 IBPP::Timestamp ts(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, 205 h, m, s, ms * 10); 206 207 m_stmt->Set(column + 1, ts); 208 } 209 } 210 bind(int column,const std::vector<unsigned char> & value)211 virtual void bind(int column, const std::vector<unsigned char>& value) override 212 { 213 214 IBPP::Blob b = IBPP::BlobFactory(conn_.impl_->m_db, 215 conn_.impl_->m_tra); 216 b->Create(); 217 if (value.size() > 0) 218 b->Write((void *)&value.front(), value.size()); 219 b->Close(); 220 m_stmt->Set(column + 1, b); 221 } 222 bindNull(int column)223 virtual void bindNull(int column) override 224 { 225 LOG_DEBUG("bind: " << column << " null"); 226 227 m_stmt->SetNull(column + 1); 228 } 229 execute()230 virtual void execute() override 231 { 232 if (conn_.showQueries()) 233 LOG_INFO(sql_); 234 235 try { 236 m_stmt->Execute(); 237 affectedRows_ = m_stmt->AffectedRows(); 238 239 row_ = 0; 240 } catch(IBPP::LogicException &e) { 241 throw FirebirdException(e.what()); 242 } 243 244 if (affectedRows_ == 0) { 245 const std::string returning = " returning "; 246 std::size_t j = sql_.rfind(returning); 247 if (j != std::string::npos && 248 sql_.find(' ', j + returning.length()) == std::string::npos) { 249 if (m_stmt->Columns() == 1) { 250 lastId_ = -1; 251 m_stmt->Get(1, lastId_); 252 } 253 } 254 } 255 } 256 insertedId()257 virtual long long insertedId() override 258 { 259 return lastId_; 260 } 261 affectedRowCount()262 virtual int affectedRowCount() override 263 { 264 return affectedRows_; 265 } 266 nextRow()267 virtual bool nextRow() override 268 { 269 try { 270 return m_stmt->Fetch(); 271 } catch(IBPP::Exception &e) { 272 throw FirebirdException(e.what()); 273 } 274 } 275 columnCount()276 virtual int columnCount() const override 277 { 278 return columnCount_; 279 } 280 getString(int column,std::string * value,int size)281 void getString(int column, std::string *value, int size) 282 { 283 m_stmt->Get(column, *value); 284 } 285 getResult(int column,std::string * value,int size)286 virtual bool getResult(int column, std::string *value, int size) override 287 { 288 if (m_stmt->IsNull(++column)) 289 return false; 290 291 getString(column, value, size); 292 293 LOG_DEBUG("getResult " << column << " " << *value); 294 295 return true; 296 } 297 getResult(int column,short * value)298 virtual bool getResult(int column, short *value) override 299 { 300 if (m_stmt->IsNull(++column)) 301 return false; 302 303 m_stmt->Get(column, *value); 304 305 LOG_DEBUG("getResult " << column << " " << *value); 306 307 return true; 308 } 309 getResult(int column,int * value)310 virtual bool getResult(int column, int *value) override 311 { 312 if (m_stmt->IsNull(++column)) 313 return false; 314 315 m_stmt->Get(column, *value); 316 317 LOG_DEBUG("getResult " << column << " " << *value); 318 319 return true; 320 } 321 getResult(int column,long long * value)322 virtual bool getResult(int column, long long *value) override 323 { 324 if (m_stmt->IsNull(++column)) 325 return false; 326 327 m_stmt->Get(column, *((int64_t *)value)); 328 329 LOG_DEBUG("getResult " << column << " " << *value); 330 331 return true; 332 } 333 getResult(int column,float * value)334 virtual bool getResult(int column, float *value) override 335 { 336 if (m_stmt->IsNull(++column)) 337 return false; 338 339 m_stmt->Get(column, *value); 340 341 LOG_DEBUG("getResult " << column << " " << *value); 342 343 return true; 344 } 345 getResult(int column,double * value)346 virtual bool getResult(int column, double *value) override 347 { 348 if (m_stmt->IsNull(++column)) 349 return false; 350 351 m_stmt->Get(column, *value); 352 353 LOG_DEBUG("getResult " << column << " " << *value); 354 355 return true; 356 } 357 getResult(int column,std::chrono::system_clock::time_point * value,SqlDateTimeType type)358 virtual bool getResult(int column, 359 std::chrono::system_clock::time_point *value, 360 SqlDateTimeType type) override 361 { 362 363 if (m_stmt->IsNull(++column)) 364 return false; 365 366 switch(type) { 367 case SqlDateTimeType::Date: { 368 IBPP::Date d; 369 m_stmt->Get(column, d); 370 std::tm tm = std::tm(); 371 tm.tm_year = d.Year() - 1900; 372 tm.tm_mon = d.Month() - 1; 373 tm.tm_mday = d.Day(); 374 std::time_t t = timegm(&tm); 375 *value = std::chrono::system_clock::from_time_t(t); 376 break; 377 } 378 379 case SqlDateTimeType::DateTime: { 380 IBPP::Timestamp tm; 381 m_stmt->Get(column, tm); 382 std::tm time = std::tm(); 383 time.tm_year = tm.Year() - 1900; 384 time.tm_mon = tm.Month() - 1; 385 time.tm_mday = tm.Day(); 386 time.tm_hour = tm.Hours(); 387 time.tm_min = tm.Minutes(); 388 time.tm_sec = tm.Seconds(); 389 std::time_t t = timegm(&time); 390 *value = std::chrono::system_clock::from_time_t(t); 391 *value += std::chrono::milliseconds(tm.SubSeconds() / 10); 392 break; 393 } 394 case SqlDateTimeType::Time: 395 break; 396 } 397 std::time_t t = std::chrono::system_clock::to_time_t(*value); 398 LOG_DEBUG("getResult " << column << " " << std::ctime(&t)); 399 400 return true; 401 } 402 403 getResult(int column,std::chrono::duration<int,std::milli> * value)404 virtual bool getResult(int column, 405 std::chrono::duration<int, std::milli> *value) override 406 { 407 408 if (m_stmt->IsNull(++column)) 409 return false; 410 411 IBPP::Time t; 412 m_stmt->Get(column, t); 413 *value = std::chrono::hours(t.Hours()) + std::chrono::minutes(t.Minutes()) + 414 std::chrono::seconds(t.Seconds()) + std::chrono::milliseconds(t.SubSeconds() / 10); 415 416 LOG_DEBUG("getResult " << column << " " << value->count() << "ms"); 417 418 return true; 419 } 420 getResult(int column,std::vector<unsigned char> * value,int size)421 virtual bool getResult(int column, 422 std::vector<unsigned char> *value, 423 int size) override 424 { 425 if (m_stmt->IsNull(++column)) 426 return false; 427 428 IBPP::Blob b = IBPP::BlobFactory(conn_.impl_->m_db, 429 conn_.impl_->m_tra); 430 m_stmt->Get(column, b); 431 b->Open(); 432 int largest = -1, segments = -1; 433 b->Info(&size, &largest, &segments); 434 value->resize(size); 435 if (size > 0) 436 b->Read((void *)&value->front(), size); 437 438 return true; 439 } 440 sql()441 virtual std::string sql() const override 442 { 443 return sql_; 444 } 445 446 private: 447 Firebird& conn_; 448 std::string sql_; 449 IBPP::Statement m_stmt; 450 char name_[64]; 451 452 int lastId_, row_, affectedRows_; 453 int columnCount_; 454 455 setValue(int column,const std::string & value)456 void setValue(int column, const std::string& value) 457 { 458 m_stmt->Set(column + 1, value); 459 } 460 convertToNumberedPlaceholders(const std::string & sql)461 std::string convertToNumberedPlaceholders(const std::string& sql) 462 { 463 return sql; 464 } 465 }; 466 Firebird()467 Firebird::Firebird() 468 : m_writableTransaction(true) 469 { 470 impl_ = new Firebird_impl(); 471 } 472 Firebird(const std::string & ServerName,const std::string & DatabaseName,const std::string & UserName,const std::string & UserPassword,const std::string & RoleName,const std::string & CharSet,const std::string & CreateParams)473 Firebird::Firebird(const std::string& ServerName, 474 const std::string& DatabaseName, 475 const std::string& UserName, 476 const std::string& UserPassword, 477 const std::string& RoleName, 478 const std::string& CharSet, 479 const std::string& CreateParams) 480 : m_writableTransaction(true) 481 { 482 IBPP::Database db = IBPP::DatabaseFactory(ServerName, 483 DatabaseName, 484 UserName, UserPassword, 485 RoleName, 486 CharSet, CreateParams); 487 db->Connect(); 488 489 impl_ = new Firebird_impl(db); 490 } 491 Firebird(IBPP::Database db)492 Firebird::Firebird(IBPP::Database db) 493 { 494 impl_ = new Firebird_impl(db); 495 } 496 Firebird(const Firebird & other)497 Firebird::Firebird(const Firebird& other) 498 : SqlConnection(other), 499 m_writableTransaction(other.m_writableTransaction) 500 { 501 IBPP::Database db = 502 IBPP::DatabaseFactory(other.impl_->m_db->ServerName(), 503 other.impl_->m_db->DatabaseName(), 504 other.impl_->m_db->Username(), 505 other.impl_->m_db->UserPassword(), 506 other.impl_->m_db->RoleName(), 507 other.impl_->m_db->CharSet(), 508 other.impl_->m_db->CreateParams()); 509 db->Connect(); 510 511 impl_ = new Firebird_impl(db); 512 } 513 connect(const std::string & ServerName,const std::string & DatabaseName,const std::string & UserName,const std::string & UserPassword,const std::string & RoleName,const std::string & CharSet,const std::string & CreateParams)514 bool Firebird::connect(const std::string& ServerName, 515 const std::string& DatabaseName, 516 const std::string& UserName, 517 const std::string& UserPassword, 518 const std::string& RoleName, 519 const std::string& CharSet, 520 const std::string& CreateParams) 521 { 522 IBPP::Database db = IBPP::DatabaseFactory(ServerName, 523 DatabaseName, 524 UserName, UserPassword, 525 RoleName, 526 CharSet, CreateParams); 527 db->Connect(); 528 529 impl_->m_db = db; 530 531 return true; 532 } 533 ~Firebird()534 Firebird::~Firebird() 535 { 536 clearStatementCache(); 537 delete impl_; 538 } 539 clone()540 std::unique_ptr<SqlConnection> Firebird::clone() const 541 { 542 return std::unique_ptr<SqlConnection>(new Firebird(*this)); 543 } 544 prepareStatement(const std::string & sql)545 std::unique_ptr<SqlStatement> Firebird::prepareStatement(const std::string& sql) 546 { 547 return std::unique_ptr<SqlStatement>( 548 new FirebirdStatement(*this, sql)); 549 } 550 autoincrementType()551 std::string Firebird::autoincrementType() const 552 { 553 return "bigint"; 554 } 555 autoincrementSql()556 std::string Firebird::autoincrementSql() const 557 { 558 return std::string(); 559 } 560 561 std::vector<std::string> autoincrementCreateSequenceSql(const std::string & table,const std::string & id)562 Firebird::autoincrementCreateSequenceSql(const std::string &table, 563 const std::string &id) const 564 { 565 std::vector<std::string> sql; 566 567 std::string sequenceId = "seq_" + table + "_" + id; 568 569 sql.push_back(std::string("create generator ") + sequenceId); 570 sql.push_back(std::string("set generator ") + sequenceId + " to 0"); 571 572 std::stringstream trigger; 573 trigger << "CREATE TRIGGER seq_trig_" << table 574 << " FOR \"" << table << "\"" 575 << " ACTIVE BEFORE INSERT POSITION 0" 576 << " AS" 577 << " BEGIN" 578 << " if (NEW.\"" << id << "\" is NULL)" 579 << " then NEW.\"" << id << "\" " 580 << " = GEN_ID(" << sequenceId << ", 1);" 581 << " END "; 582 583 sql.push_back(trigger.str()); 584 585 return sql; 586 } 587 588 std::vector<std::string> autoincrementDropSequenceSql(const std::string & table,const std::string & id)589 Firebird::autoincrementDropSequenceSql(const std::string &table, 590 const std::string &id) const 591 { 592 std::vector<std::string> sql; 593 594 sql.push_back(std::string("drop trigger seq_trig_") + table); 595 sql.push_back(std::string("drop generator seq_") 596 + table + "_" + id); 597 598 return sql; 599 } 600 autoincrementInsertSuffix(const std::string & id)601 std::string Firebird::autoincrementInsertSuffix(const std::string& id) const 602 { 603 return " returning \"" + id + "\""; 604 } 605 dateTimeType(SqlDateTimeType type)606 const char *Firebird::dateTimeType(SqlDateTimeType type) const 607 { 608 switch (type) { 609 case SqlDateTimeType::Date: 610 return "date"; 611 case SqlDateTimeType::DateTime: 612 return "timestamp"; 613 case SqlDateTimeType::Time: 614 return "time"; 615 } 616 617 std::stringstream ss; 618 ss << __FILE__ << ":" << __LINE__ << ": implementation error"; 619 throw FirebirdException(ss.str()); 620 } 621 blobType()622 const char *Firebird::blobType() const 623 { 624 return "blob"; 625 } 626 startTransaction()627 void Firebird::startTransaction() 628 { 629 if (!impl_->m_tra.intf()) { 630 if (m_writableTransaction) { 631 impl_->m_tra = IBPP::TransactionFactory(impl_->m_db, 632 IBPP::amWrite, 633 IBPP::ilReadCommitted, 634 IBPP::lrWait); 635 } else { 636 impl_->m_tra = IBPP::TransactionFactory(impl_->m_db, 637 IBPP::amRead, 638 IBPP::ilReadCommitted, 639 IBPP::lrWait); 640 } 641 } 642 643 if (!impl_->m_tra.intf()) 644 throw FirebirdException("Could not start transaction!"); 645 646 impl_->m_tra->Start(); 647 } 648 commitTransaction()649 void Firebird::commitTransaction() 650 { 651 if (!impl_->m_tra.intf()) 652 throw FirebirdException("Transaction was not started yet!"); 653 654 impl_->m_tra->Commit(); 655 } 656 rollbackTransaction()657 void Firebird::rollbackTransaction() 658 { 659 if (!impl_->m_tra.intf()) 660 throw FirebirdException("Transaction was not started yet!"); 661 662 impl_->m_tra->Rollback(); 663 } 664 textType(int size)665 std::string Firebird::textType(int size) const 666 { 667 if (size != -1 && size <= MAX_VARCHAR_LENGTH) 668 return std::string("varchar (") + std::to_string(size) + ")"; 669 else 670 return std::string("blob sub_type text"); 671 } 672 booleanType()673 const char *Firebird::booleanType() const 674 { 675 return "smallint"; 676 } 677 prepareForDropTables()678 void Firebird::prepareForDropTables() 679 { 680 clearStatementCache(); 681 } 682 limitQueryMethod()683 LimitQuery Firebird::limitQueryMethod() const 684 { 685 return LimitQuery::RowsFromTo; 686 } 687 supportAlterTable()688 bool Firebird::supportAlterTable() const 689 { 690 return true; 691 } 692 connection()693 IBPP::Database Firebird::connection() 694 { 695 return impl_->m_db; 696 } 697 } 698 } 699 } 700