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