| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package UR::DataSource::Pg; | 
| 2 | 3 |  |  | 3 |  | 95 | use strict; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 74 |  | 
| 3 | 3 |  |  | 3 |  | 9 | use warnings; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 3021 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | require UR; | 
| 6 |  |  |  |  |  |  | our $VERSION = "0.46"; # UR $VERSION; | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | UR::Object::Type->define( | 
| 9 |  |  |  |  |  |  | class_name => 'UR::DataSource::Pg', | 
| 10 |  |  |  |  |  |  | is => ['UR::DataSource::RDBMS'], | 
| 11 |  |  |  |  |  |  | is_abstract => 1, | 
| 12 |  |  |  |  |  |  | ); | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | # RDBMS API | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 0 |  |  | 0 | 0 | 0 | sub driver { "Pg" } | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | #sub server { | 
| 19 |  |  |  |  |  |  | #    my $self = shift->_singleton_object(); | 
| 20 |  |  |  |  |  |  | #    $self->_init_database; | 
| 21 |  |  |  |  |  |  | #    return $self->_database_file_path; | 
| 22 |  |  |  |  |  |  | #} | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 0 |  |  | 0 | 0 | 0 | sub owner { shift->_singleton_object->login } | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | #sub login { | 
| 27 |  |  |  |  |  |  | #    undef | 
| 28 |  |  |  |  |  |  | #} | 
| 29 |  |  |  |  |  |  | # | 
| 30 |  |  |  |  |  |  | #sub auth { | 
| 31 |  |  |  |  |  |  | #    undef | 
| 32 |  |  |  |  |  |  | #} | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 3 |  |  | 3 |  | 12 | sub _default_sql_like_escape_string { return '\\\\' }; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | sub _format_sql_like_escape_string { | 
| 37 | 3 |  |  | 3 |  | 8 | my $class = shift; | 
| 38 | 3 |  |  |  |  | 6 | my $escape = shift; | 
| 39 | 3 |  |  |  |  | 8 | return "E'$escape'"; | 
| 40 |  |  |  |  |  |  | } | 
| 41 |  |  |  |  |  |  |  | 
| 42 | 0 |  |  | 0 | 0 | 0 | sub can_savepoint { 1;} | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | sub set_savepoint { | 
| 45 | 0 |  |  | 0 | 0 | 0 | my($self,$sp_name) = @_; | 
| 46 |  |  |  |  |  |  |  | 
| 47 | 0 |  |  |  |  | 0 | my $dbh = $self->get_default_handle; | 
| 48 | 0 |  |  |  |  | 0 | $dbh->pg_savepoint($sp_name); | 
| 49 |  |  |  |  |  |  | } | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | sub rollback_to_savepoint { | 
| 52 | 0 |  |  | 0 | 0 | 0 | my($self,$sp_name) = @_; | 
| 53 |  |  |  |  |  |  |  | 
| 54 | 0 |  |  |  |  | 0 | my $dbh = $self->get_default_handle; | 
| 55 | 0 |  |  |  |  | 0 | $dbh->pg_rollback_to($sp_name); | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | *_init_created_dbh = \&init_created_handle; | 
| 60 |  |  |  |  |  |  | sub init_created_handle | 
| 61 |  |  |  |  |  |  | { | 
| 62 | 0 |  |  | 0 | 0 | 0 | my ($self, $dbh) = @_; | 
| 63 | 0 | 0 |  |  |  | 0 | return unless defined $dbh; | 
| 64 | 0 |  |  |  |  | 0 | $dbh->{LongTruncOk} = 0; | 
| 65 | 0 |  |  |  |  | 0 | return $dbh; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | sub _ignore_table { | 
| 69 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 70 | 0 |  |  |  |  | 0 | my $table_name = shift; | 
| 71 | 0 | 0 |  |  |  | 0 | return 1 if $table_name =~ /^(pg_|sql_)/; | 
| 72 |  |  |  |  |  |  | } | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | sub _get_next_value_from_sequence { | 
| 76 | 0 |  |  | 0 |  | 0 | my($self,$sequence_name) = @_; | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | # we may need to change how this db handle is gotten | 
| 79 | 0 |  |  |  |  | 0 | my $dbh = $self->get_default_handle; | 
| 80 | 0 |  |  |  |  | 0 | my($new_id) = $dbh->selectrow_array("SELECT nextval('$sequence_name')"); | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 0 | 0 |  |  |  | 0 | if ($dbh->err) { | 
| 83 | 0 |  |  |  |  | 0 | die "Failed to prepare SQL to generate a column id from sequence: $sequence_name.\n" . $dbh->errstr . "\n"; | 
| 84 | 0 |  |  |  |  | 0 | return; | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  |  | 
| 87 | 0 |  |  |  |  | 0 | return $new_id; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # The default for PostgreSQL's serial datatype is to create a sequence called | 
| 91 |  |  |  |  |  |  | # tablename_columnname_seq | 
| 92 |  |  |  |  |  |  | sub _get_sequence_name_for_table_and_column { | 
| 93 | 0 |  |  | 0 |  | 0 | my($self,$table_name, $column_name) = @_; | 
| 94 | 0 |  |  |  |  | 0 | return sprintf("%s_%s_seq",$table_name, $column_name); | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub get_bitmap_index_details_from_data_dictionary { | 
| 99 |  |  |  |  |  |  | # FIXME Postgres has bitmap indexes, but we don't support them yet.  See the Oracle | 
| 100 |  |  |  |  |  |  | # datasource module for details about how to get it working | 
| 101 | 0 |  |  | 0 | 0 | 0 | return []; | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | sub get_unique_index_details_from_data_dictionary { | 
| 106 | 0 |  |  | 0 | 0 | 0 | my($self, $owner_name, $table_name) = @_; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 0 |  |  |  |  | 0 | my $sql = qq( | 
| 109 |  |  |  |  |  |  | SELECT c_index.relname, a.attname | 
| 110 |  |  |  |  |  |  | FROM pg_catalog.pg_class c_table | 
| 111 |  |  |  |  |  |  | JOIN pg_catalog.pg_index i ON i.indrelid = c_table.oid | 
| 112 |  |  |  |  |  |  | JOIN pg_catalog.pg_class c_index ON c_index.oid = i.indexrelid | 
| 113 |  |  |  |  |  |  | JOIN pg_catalog.pg_attribute a ON a.attrelid = c_index.oid | 
| 114 |  |  |  |  |  |  | JOIN pg_catalog.pg_namespace n ON c_table.relnamespace = n.oid | 
| 115 |  |  |  |  |  |  | WHERE c_table.relname = ? AND n.nspname = ? | 
| 116 |  |  |  |  |  |  | and (i.indisunique = 't' or i.indisprimary = 't') | 
| 117 |  |  |  |  |  |  | and i.indisvalid = 't' | 
| 118 |  |  |  |  |  |  | ); | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 0 |  |  |  |  | 0 | my $dbh = $self->get_default_handle(); | 
| 121 | 0 | 0 |  |  |  | 0 | return undef unless $dbh; | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 0 |  |  |  |  | 0 | my $sth = $dbh->prepare($sql); | 
| 124 | 0 | 0 |  |  |  | 0 | return undef unless $sth; | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | #my $db_owner = $self->owner();  # We should probably do something with the owner/schema | 
| 127 | 0 |  |  |  |  | 0 | $sth->execute($table_name, $owner_name); | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 0 |  |  |  |  | 0 | my $ret; | 
| 130 | 0 |  |  |  |  | 0 | while (my $data = $sth->fetchrow_hashref()) { | 
| 131 | 0 |  | 0 |  |  | 0 | $ret->{$data->{'relname'}} ||= []; | 
| 132 | 0 |  |  |  |  | 0 | push @{ $ret->{ $data->{'relname'} } }, $data->{'attname'}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 0 |  |  |  |  | 0 | return $ret; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | my %ur_data_type_for_vendor_data_type = ( | 
| 139 |  |  |  |  |  |  | # DB type      UR Type | 
| 140 |  |  |  |  |  |  | 'SMALLINT'  => ['Integer', undef], | 
| 141 |  |  |  |  |  |  | 'BIGINT'    => ['Integer', undef], | 
| 142 |  |  |  |  |  |  | 'SERIAL'    => ['Integer', undef], | 
| 143 |  |  |  |  |  |  | 'TEXT'      => ['Text', undef], | 
| 144 |  |  |  |  |  |  | 'BYTEA'     => ['Blob', undef], | 
| 145 |  |  |  |  |  |  | 'CHARACTER VARYING' => ['Text', undef], | 
| 146 |  |  |  |  |  |  | 'TIMESTAMP WITHOUT TIME ZONE' => ['DateTime', undef], | 
| 147 |  |  |  |  |  |  | 'NUMERIC'   => ['Number', undef], | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | 'DOUBLE PRECISION' => ['Number', undef], | 
| 150 |  |  |  |  |  |  | ); | 
| 151 |  |  |  |  |  |  | sub ur_data_type_for_data_source_data_type { | 
| 152 | 0 |  |  | 0 | 0 | 0 | my($class,$type) = @_; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 0 |  |  |  |  | 0 | $type = $class->normalize_vendor_type($type); | 
| 155 | 0 |  |  |  |  | 0 | my $urtype = $ur_data_type_for_vendor_data_type{$type}; | 
| 156 | 0 | 0 |  |  |  | 0 | unless (defined $urtype) { | 
| 157 | 0 |  |  |  |  | 0 | $urtype = $class->SUPER::ur_data_type_for_data_source_data_type($type); | 
| 158 |  |  |  |  |  |  | } | 
| 159 | 0 |  |  |  |  | 0 | return $urtype; | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | sub _vendor_data_type_for_ur_data_type { | 
| 163 | 0 |  |  | 0 |  | 0 | return ( BOOLEAN     => 'BOOLEAN', | 
| 164 |  |  |  |  |  |  | XML         => 'XML', | 
| 165 |  |  |  |  |  |  | shift->SUPER::_vendor_data_type_for_ur_data_type(), | 
| 166 |  |  |  |  |  |  | ); | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | sub _alter_sth_for_selecting_blob_columns { | 
| 170 | 0 |  |  | 0 |  | 0 | my($self, $sth, $column_objects) = @_; | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 0 |  |  |  |  | 0 | for (my $n = 0; $n < @$column_objects; $n++) { | 
| 173 | 0 | 0 |  |  |  | 0 | next unless defined ($column_objects->[$n]);  # No metaDB info for this one | 
| 174 | 0 | 0 |  |  |  | 0 | if (uc($column_objects->[$n]->data_type) eq 'BLOB') { | 
| 175 | 0 |  |  |  |  | 0 | require DBD::Pg; | 
| 176 | 0 |  |  |  |  | 0 | $sth->bind_param($n+1, undef, { pg_type => DBD::Pg::PG_BYTEA() }); | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | my $DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; | 
| 182 |  |  |  |  |  |  | my $TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.US'; | 
| 183 |  |  |  |  |  |  | sub cast_for_data_conversion { | 
| 184 | 4 |  |  | 4 | 0 | 8 | my($class, $left_type, $right_type, $operator, $sql_clause) = @_; | 
| 185 |  |  |  |  |  |  |  | 
| 186 | 4 |  |  |  |  | 15 | my @retval = ('%s','%s'); | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | # compatible types | 
| 189 | 4 | 50 | 33 |  |  | 53 | if ($left_type->isa($right_type) | 
| 190 |  |  |  |  |  |  | or | 
| 191 |  |  |  |  |  |  | $right_type->isa($left_type) | 
| 192 |  |  |  |  |  |  | ) { | 
| 193 | 0 |  |  |  |  | 0 | return @retval; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | # So far, the only casting is to support using 'like' and one or both are strings | 
| 197 | 4 | 50 | 33 |  |  | 52 | if ($operator ne 'like' | 
|  |  |  | 33 |  |  |  |  | 
| 198 |  |  |  |  |  |  | or | 
| 199 |  |  |  |  |  |  | ( ! $left_type->isa('UR::Value::Text') and ! $right_type->isa('UR::Value::Text') ) | 
| 200 |  |  |  |  |  |  | ) { | 
| 201 | 0 |  |  |  |  | 0 | return @retval; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | # Figure out which one is the non-string | 
| 205 | 4 | 50 |  |  |  | 17 | my($data_type, $i) = $left_type->isa('UR::Value::Text') | 
| 206 |  |  |  |  |  |  | ? ( $right_type, 1) | 
| 207 |  |  |  |  |  |  | : ( $left_type, 0); | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 4 | 100 |  |  |  | 18 | if ($data_type->isa('UR::Value::Timestamp')) { | 
|  |  | 50 |  |  |  |  |  | 
| 210 | 3 |  |  |  |  | 10 | $retval[$i] = qq{to_char(%s, '$TIMESTAMP_FORMAT')}; | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | } elsif ($data_type->isa('UR::Value::DateTime')) { | 
| 213 | 1 |  |  |  |  | 3 | $retval[$i] = qq{to_char(%s, '$DATE_FORMAT')}; | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | } else { | 
| 216 | 0 |  |  |  |  | 0 | @retval = $class->SUPER::cast_for_data_conversion($left_type, $right_type, $operator); | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 | 4 |  |  |  |  | 15 | return @retval; | 
| 220 |  |  |  |  |  |  | } | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | sub _resolve_order_by_clause_for_column { | 
| 223 | 3 |  |  | 3 |  | 6 | my($self, $column_name, $query_plan, $property_meta) = @_; | 
| 224 |  |  |  |  |  |  |  | 
| 225 | 3 |  |  |  |  | 6 | my $column_clause = $column_name; | 
| 226 |  |  |  |  |  |  |  | 
| 227 | 3 |  |  |  |  | 13 | my $is_text_type = $property_meta->is_text; | 
| 228 | 3 | 50 |  |  |  | 12 | if ($is_text_type) { | 
| 229 |  |  |  |  |  |  | # Tell the DB to sort the same order as Perl's cmp | 
| 230 | 3 |  |  |  |  | 5 | $column_clause .= q( COLLATE "C"); | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 | 3 |  |  |  |  | 10 | my $is_desc = $query_plan->order_by_column_is_descending($column_name); | 
| 234 | 3 | 100 |  |  |  | 9 | if ($is_desc) { | 
| 235 | 1 |  |  |  |  | 2 | $column_clause .= q( DESC); | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 | 3 |  |  |  |  | 10 | return $column_clause; | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | sub _assure_schema_exists_for_table { | 
| 242 | 0 |  |  | 0 |  |  | my($self, $table_name, $dbh) = @_; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 0 |  | 0 |  |  |  | $dbh ||= $self->get_default_handle; | 
| 245 |  |  |  |  |  |  |  | 
| 246 | 0 |  |  |  |  |  | my ($schema_name, undef) = $self->_extract_schema_and_table_name($table_name); | 
| 247 | 0 | 0 |  |  |  |  | if ($schema_name) { | 
| 248 | 0 |  |  |  |  |  | my $exists = $dbh->selectrow_array("SELECT schema_name FROM information_schema.schemata WHERE schema_name = ?;", | 
| 249 |  |  |  |  |  |  | undef, $schema_name); | 
| 250 | 0 | 0 |  |  |  |  | unless ($exists) { | 
| 251 | 0 | 0 |  |  |  |  | $dbh->do("CREATE SCHEMA $schema_name") | 
| 252 |  |  |  |  |  |  | or Carp::croak("Could not create schema $schema_name: " . $dbh->errstr); | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | 1; | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | =pod | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =head1 NAME | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | UR::DataSource::Pg - PostgreSQL specific subclass of UR::DataSource::RDBMS | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | This module provides the PostgreSQL-specific methods necessary for interacting with | 
| 268 |  |  |  |  |  |  | PostgreSQL databases | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | L, L | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | =cut | 
| 275 |  |  |  |  |  |  |  |