PageRenderTime 153ms CodeModel.GetById 15ms RepoModel.GetById 2ms app.codeStats 0ms

/lib/Tpda3/Db/Connection/Firebird.pm

http://github.com/stefansbv/Tpda3
Perl | 483 lines | 373 code | 104 blank | 6 comment | 9 complexity | b5fb20aa786409329f1c6594bfc763b2 MD5 | raw file
  1. package Tpda3::Db::Connection::Firebird;
  2. # ABSTRACT: Connect to a Firebird database
  3. use strict;
  4. use warnings;
  5. use DBI;
  6. use Log::Log4perl qw(get_logger :levels);
  7. use Regexp::Common;
  8. use Try::Tiny;
  9. require Tpda3::Exceptions;
  10. sub new {
  11. my ( $class, $model ) = @_;
  12. my $self = {};
  13. $self->{model} = $model;
  14. bless $self, $class;
  15. return $self;
  16. }
  17. sub driver {
  18. return 'Firebird';
  19. }
  20. sub db_connect {
  21. my ( $self, $conf ) = @_;
  22. my $log = get_logger();
  23. my ( $dbname, $host, $port ) = @{$conf}{qw(dbname host port)};
  24. my ( $driver, $user, $pass, $role ) = @{$conf}{qw(driver user pass role)};
  25. $log->trace("Database driver is: $driver");
  26. $log->trace("Parameters:");
  27. $log->trace( " > Database = ", $dbname ? $dbname : '?', "\n" );
  28. $log->trace( " > Host = ", $host ? $host : '?', "\n" );
  29. $log->trace( " > Port = ", $port ? $port : '?', "\n" );
  30. $log->trace( " > User = ", $user ? $user : '?', "\n" );
  31. my $dsn = qq{dbi:Firebird:dbname=$dbname;host=$host;port=$port};
  32. $dsn .= q{;ib_dialect=3;ib_charset=UTF8};
  33. $dsn .= qq{;ib_role=$role} if $role;
  34. $self->{_dbh} = DBI->connect(
  35. $dsn, $user, $pass,
  36. { FetchHashKeyName => 'NAME_lc',
  37. AutoCommit => 1,
  38. RaiseError => 0,
  39. PrintError => 0,
  40. LongReadLen => 524288,
  41. LongTruncOk => 1,
  42. HandleError => sub { $self->handle_error() },
  43. ib_enable_utf8 => 1,
  44. }
  45. );
  46. # Default date format: ISO
  47. $self->{_dbh}{ib_time_all} = 'ISO';
  48. # $self->{_dbh}{ib_timestampformat} = '%y-%m-%d %H:%M';
  49. # $self->{_dbh}{ib_dateformat} = '%Y-%m-%d';
  50. # $self->{_dbh}{ib_timeformat} = '%H:%M';
  51. return $self->{_dbh};
  52. }
  53. sub handle_error {
  54. my $self = shift;
  55. if ( defined $self->{_dbh} and $self->{_dbh}->isa('DBI::db') ) {
  56. my $errorstr = $self->{_dbh}->errstr;
  57. Exception::Db::SQL->throw(
  58. logmsg => $errorstr,
  59. usermsg => $self->parse_error($errorstr),
  60. );
  61. }
  62. else {
  63. my $errorstr = DBI->errstr;
  64. Exception::Db::Connect->throw(
  65. logmsg => $errorstr,
  66. usermsg => $self->parse_error($errorstr),
  67. );
  68. }
  69. return;
  70. }
  71. sub parse_error {
  72. my ( $self, $fb ) = @_;
  73. my $log = get_logger();
  74. $log->error("EE: $fb");
  75. my $message_type
  76. = $fb eq q{} ? "nomessage"
  77. : $fb =~ m/operation for file ($RE{quoted})/smi ? "dbnotfound:$1"
  78. : $fb =~ m/\-Table unknown\s*\-(.*)\-/smi ? "relnotfound:$1"
  79. : $fb =~ m/Your user name and password/smi ? "userpass"
  80. : $fb =~ m/no route to host/smi ? "network"
  81. : $fb =~ m/network request to host ($RE{quoted})/smi ? "nethost:$1"
  82. : $fb =~ m/install_driver($RE{balanced}{-parens=>'()'})/smi
  83. ? "driver:$1"
  84. : $fb =~ m/not connected/smi ? "notconn"
  85. : "unknown";
  86. # Analize and translate
  87. my ( $type, $name ) = split /:/, $message_type, 2;
  88. $name = $name ? $name : '';
  89. my $translations = {
  90. driver => "error#Database driver $name not found",
  91. dbnotfound => "error#Database $name not found",
  92. relnotfound => "error#Relation $name not found",
  93. userpass => "error#Authentication failed",
  94. nethost => "error#Network problem: host $name",
  95. network => "error#Network problem",
  96. unknown => "error#Database error",
  97. notconn => "error#Not connected",
  98. };
  99. my $message;
  100. if ( exists $translations->{$type} ) {
  101. $message = $translations->{$type};
  102. }
  103. else {
  104. $log->error('EE: Translation error for: $fb!');
  105. }
  106. return $message;
  107. }
  108. sub table_list {
  109. my $self = shift;
  110. my $log = get_logger();
  111. $log->info('Geting list of tables');
  112. my $sql = q{SELECT TRIM(LOWER(RDB$RELATION_NAME)) AS table_name
  113. FROM RDB$RELATIONS
  114. WHERE RDB$SYSTEM_FLAG=0
  115. AND RDB$VIEW_BLR IS NULL
  116. };
  117. $self->{_dbh}->{AutoCommit} = 1; # disable transactions
  118. $self->{_dbh}->{RaiseError} = 0;
  119. my $table_list;
  120. try {
  121. $table_list = $self->{_dbh}->selectcol_arrayref($sql);
  122. }
  123. catch {
  124. $log->fatal("Transaction aborted because $_")
  125. or print STDERR "$_\n";
  126. };
  127. return $table_list;
  128. }
  129. sub view_list {
  130. my $self = shift;
  131. my $log = get_logger();
  132. $log->info('Geting list of procedures');
  133. my $sql = q{SELECT DISTINCT TRIM(LOWER(RDB$VIEW_NAME)) AS view_name
  134. FROM RDB$VIEW_RELATIONS;
  135. };
  136. $self->{_dbh}->{AutoCommit} = 1; # disable transactions
  137. $self->{_dbh}->{RaiseError} = 0;
  138. my $view_list;
  139. try {
  140. $view_list = $self->{_dbh}->selectcol_arrayref($sql);
  141. }
  142. catch {
  143. $log->fatal("Transaction aborted because $_")
  144. or print STDERR "$_\n";
  145. };
  146. return $view_list;
  147. }
  148. sub procedure_list {
  149. my $self = shift;
  150. my $log = get_logger();
  151. $log->info('Geting list of procedures');
  152. my $sql = q{SELECT TRIM(LOWER(RDB$PROCEDURE_NAME)) AS proc_name
  153. FROM RDB$PROCEDURES;
  154. };
  155. $self->{_dbh}->{AutoCommit} = 1; # disable transactions
  156. $self->{_dbh}->{RaiseError} = 0;
  157. my $proc_list;
  158. try {
  159. $proc_list = $self->{_dbh}->selectcol_arrayref($sql);
  160. }
  161. catch {
  162. $log->fatal("Transaction aborted because $_")
  163. or print STDERR "$_\n";
  164. };
  165. return $proc_list;
  166. }
  167. sub trigger_list {
  168. my $self = shift;
  169. my $log = get_logger();
  170. $log->info('Geting list of triggers');
  171. my $sql = q{SELECT TRIM(LOWER(RDB$TRIGGER_NAME)) AS trigger_name,
  172. TRIM(LOWER(RDB$RELATION_NAME)) AS table_name
  173. FROM RDB$TRIGGERS
  174. WHERE RDB$SYSTEM_FLAG=0;
  175. };
  176. $self->{_dbh}->{AutoCommit} = 1; # disable transactions
  177. $self->{_dbh}->{RaiseError} = 0;
  178. my $triggers;
  179. try {
  180. $triggers = $self->{_dbh}->selectall_arrayref(
  181. $sql, { Slice => {} }
  182. ); # return an AoH
  183. }
  184. catch {
  185. $log->fatal("Transaction aborted because $_")
  186. or print STDERR "$_\n";
  187. };
  188. return $triggers;
  189. }
  190. sub sequences_list {
  191. my $self = shift;
  192. my $log = get_logger();
  193. $log->info('Geting list of generators');
  194. my $sql = q{SELECT TRIM(LOWER(RDB$GENERATOR_NAME)) AS gen_name
  195. FROM RDB$GENERATORS
  196. WHERE RDB$SYSTEM_FLAG=0;
  197. };
  198. $self->{_dbh}->{AutoCommit} = 1; # disable transactions
  199. $self->{_dbh}->{RaiseError} = 0;
  200. my $seq_list;
  201. try {
  202. $seq_list = $self->{_dbh}->selectcol_arrayref($sql);
  203. }
  204. catch {
  205. $log->fatal("Transaction aborted because $_")
  206. or print STDERR "$_\n";
  207. };
  208. return $seq_list;
  209. }
  210. sub table_info_short {
  211. my ( $self, $table ) = @_;
  212. my $log = get_logger();
  213. $log->info("Geting table info for $table");
  214. $table = uc $table;
  215. my $sql = qq(SELECT RDB\$FIELD_POSITION AS pos
  216. , LOWER(r.RDB\$FIELD_NAME) AS name
  217. , r.RDB\$DEFAULT_VALUE AS defa
  218. , r.RDB\$NULL_FLAG AS is_nullable
  219. , f.RDB\$FIELD_LENGTH AS length
  220. , f.RDB\$FIELD_PRECISION AS prec
  221. , CASE
  222. WHEN f.RDB\$FIELD_SCALE > 0 THEN (f.RDB\$FIELD_SCALE)
  223. WHEN f.RDB\$FIELD_SCALE < 0 THEN (f.RDB\$FIELD_SCALE * -1)
  224. ELSE 0
  225. END AS scale
  226. , CASE f.RDB\$FIELD_TYPE
  227. WHEN 261 THEN 'blob'
  228. WHEN 14 THEN 'char'
  229. WHEN 40 THEN 'cstring'
  230. WHEN 11 THEN 'd_float'
  231. WHEN 27 THEN 'double'
  232. WHEN 10 THEN 'float'
  233. WHEN 16 THEN
  234. CASE f.RDB\$FIELD_SCALE
  235. WHEN 0 THEN 'int64'
  236. ELSE 'numeric'
  237. END
  238. WHEN 8 THEN
  239. CASE f.RDB\$FIELD_SCALE
  240. WHEN 0 THEN 'integer'
  241. ELSE 'numeric'
  242. END
  243. WHEN 9 THEN 'quad'
  244. WHEN 7 THEN
  245. CASE f.RDB\$FIELD_SCALE
  246. WHEN 0 THEN 'smallint'
  247. ELSE 'numeric'
  248. END
  249. WHEN 12 THEN 'date'
  250. WHEN 13 THEN 'time'
  251. WHEN 35 THEN 'timestamp'
  252. WHEN 37 THEN 'varchar'
  253. ELSE 'UNKNOWN'
  254. END AS type
  255. FROM RDB\$RELATION_FIELDS r
  256. LEFT JOIN RDB\$FIELDS f
  257. ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
  258. WHERE r.RDB\$RELATION_NAME = '$table'
  259. ORDER BY r.RDB\$FIELD_POSITION;
  260. );
  261. $self->{_dbh}{ChopBlanks} = 1; # trim CHAR fields
  262. my $flds_ref;
  263. try {
  264. my $sth = $self->{_dbh}->prepare($sql);
  265. $sth->execute;
  266. $flds_ref = $sth->fetchall_hashref('pos');
  267. }
  268. catch {
  269. $log->fatal("Transaction aborted because $_")
  270. or print STDERR "$_\n";
  271. };
  272. return $flds_ref;
  273. }
  274. sub table_keys {
  275. my ( $self, $table, $foreign ) = @_;
  276. my $log = get_logger();
  277. my $type = $foreign ? 'FOREIGN KEY' : 'PRIMARY KEY';
  278. $log->info("Geting '$table' table $type(s) names");
  279. $table = uc $table;
  280. my $sql = qq( SELECT TRIM(LOWER(s.RDB\$FIELD_NAME)) AS column_name
  281. FROM RDB\$INDEX_SEGMENTS s
  282. LEFT JOIN RDB\$INDICES i
  283. ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
  284. LEFT JOIN RDB\$RELATION_CONSTRAINTS rc
  285. ON rc.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
  286. LEFT JOIN RDB\$REF_CONSTRAINTS refc
  287. ON rc.RDB\$CONSTRAINT_NAME = refc.RDB\$CONSTRAINT_NAME
  288. LEFT JOIN RDB\$RELATION_CONSTRAINTS rc2
  289. ON rc2.RDB\$CONSTRAINT_NAME = refc.RDB\$CONST_NAME_UQ
  290. LEFT JOIN RDB\$INDICES i2
  291. ON i2.RDB\$INDEX_NAME = rc2.RDB\$INDEX_NAME
  292. LEFT JOIN RDB\$INDEX_SEGMENTS s2
  293. ON i2.RDB\$INDEX_NAME = s2.RDB\$INDEX_NAME
  294. WHERE i.RDB\$RELATION_NAME = '$table'
  295. AND rc.RDB\$CONSTRAINT_TYPE = '$type'
  296. );
  297. $log->trace("SQL= $sql");
  298. $self->{_dbh}{AutoCommit} = 1; # disable transactions
  299. $self->{_dbh}{RaiseError} = 0;
  300. my $pkf_aref;
  301. try {
  302. $pkf_aref = $self->{_dbh}->selectcol_arrayref($sql);
  303. }
  304. catch {
  305. $log->fatal("Transaction aborted because $_")
  306. or print STDERR "$_\n";
  307. };
  308. return $pkf_aref;
  309. }
  310. sub table_exists {
  311. my ( $self, $table ) = @_;
  312. my $log = get_logger();
  313. $log->info("Checking if $table table exists");
  314. $table = uc $table;
  315. my $sql = qq(SELECT COUNT(RDB\$RELATION_NAME)
  316. FROM RDB\$RELATIONS
  317. WHERE RDB\$SYSTEM_FLAG=0
  318. AND RDB\$VIEW_BLR IS NULL
  319. AND RDB\$RELATION_NAME = '$table';
  320. );
  321. $log->trace("SQL= $sql");
  322. my $val_ret;
  323. try {
  324. ($val_ret) = $self->{_dbh}->selectrow_array($sql);
  325. }
  326. catch {
  327. $log->fatal("Transaction aborted because $_")
  328. or print STDERR "$_\n";
  329. };
  330. return $val_ret;
  331. }
  332. sub has_feature_returning { 1 }
  333. 1;
  334. =head1 SYNOPSIS
  335. use Tpda3::Db::Connection::Firebird;
  336. my $db = Tpda3::Db::Connection::Firebird->new();
  337. $db->db_connect($connection);
  338. =head2 new
  339. Constructor method.
  340. =head2 db_connect
  341. Connect to the database.
  342. =head2 handle_error
  343. Log errors.
  344. =head2 parse_error
  345. Parse a database error message, and translate it for the user.
  346. RDBMS specific (and maybe version specific?).
  347. =head2 table_list
  348. Return list of tables from the database.
  349. =head2 table_info_short
  350. Table info 'short'. The 'table_info' method from the Firebird driver
  351. doesn't seem to be reliable.
  352. =head2 table_keys
  353. Get the primary key field name of the table.
  354. =head2 table_exists
  355. Check if table exists in the database.
  356. =head2 sequences_list
  357. Return list of sequences from the database.
  358. =head2 has_feature_returning
  359. Returns yes for Firebird, meaning that is has the
  360. INSERT... RETURNING feature.
  361. Should check for the Firebird version?
  362. =head1 ACKNOWLEDGEMENTS
  363. Information schema queries inspired from:
  364. - http://www.alberton.info/firebird_sql_meta_info.html by Lorenzo Alberton
  365. - Flamerobin Copyright (c) 2004-2013 The FlameRobin Development Team
  366. =cut