Initial Commit
This commit is contained in:
492
database/perl/vendor/lib/DBI/DBD/Metadata.pm
vendored
Normal file
492
database/perl/vendor/lib/DBI/DBD/Metadata.pm
vendored
Normal file
@@ -0,0 +1,492 @@
|
||||
package DBI::DBD::Metadata;
|
||||
|
||||
# $Id: Metadata.pm 14213 2010-06-30 19:29:18Z Martin $
|
||||
#
|
||||
# Copyright (c) 1997-2003 Jonathan Leffler, Jochen Wiedmann,
|
||||
# Steffen Goeldner and Tim Bunce
|
||||
#
|
||||
# You may distribute under the terms of either the GNU General Public
|
||||
# License or the Artistic License, as specified in the Perl README file.
|
||||
|
||||
use strict;
|
||||
|
||||
use Exporter ();
|
||||
use Carp;
|
||||
|
||||
use DBI;
|
||||
use DBI::Const::GetInfoType qw(%GetInfoType);
|
||||
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT = qw(write_getinfo_pm write_typeinfo_pm);
|
||||
|
||||
our $VERSION = "2.014214";
|
||||
|
||||
|
||||
=head1 NAME
|
||||
|
||||
DBI::DBD::Metadata - Generate the code and data for some DBI metadata methods
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
The idea is to extract metadata information from a good quality
|
||||
ODBC driver and use it to generate code and data to use in your own
|
||||
DBI driver for the same database.
|
||||
|
||||
To generate code to support the get_info method:
|
||||
|
||||
perl -MDBI::DBD::Metadata -e "write_getinfo_pm('dbi:ODBC:dsn-name','user','pass','Driver')"
|
||||
|
||||
perl -MDBI::DBD::Metadata -e write_getinfo_pm dbi:ODBC:foo_db username password Driver
|
||||
|
||||
To generate code to support the type_info method:
|
||||
|
||||
perl -MDBI::DBD::Metadata -e "write_typeinfo_pm('dbi:ODBC:dsn-name','user','pass','Driver')"
|
||||
|
||||
perl -MDBI::DBD::Metadata -e write_typeinfo_pm dbi:ODBC:dsn-name user pass Driver
|
||||
|
||||
Where C<dbi:ODBC:dsn-name> is the connection to use to extract the
|
||||
data, and C<Driver> is the name of the driver you want the code
|
||||
generated for (the driver name gets embedded into the output in
|
||||
numerous places).
|
||||
|
||||
=head1 Generating a GetInfo package for a driver
|
||||
|
||||
The C<write_getinfo_pm> in the DBI::DBD::Metadata module generates a
|
||||
DBD::Driver::GetInfo package on standard output.
|
||||
|
||||
This method generates a DBD::Driver::GetInfo package from the data
|
||||
source you specified in the parameter list or in the environment
|
||||
variable DBI_DSN.
|
||||
DBD::Driver::GetInfo should help a DBD author implement the DBI
|
||||
get_info() method.
|
||||
Because you are just creating this package, it is very unlikely that
|
||||
DBD::Driver already provides a good implementation for get_info().
|
||||
Thus you will probably connect via DBD::ODBC.
|
||||
|
||||
Once you are sure that it is producing reasonably sane data, you should
|
||||
typically redirect the standard output to lib/DBD/Driver/GetInfo.pm, and
|
||||
then hand edit the result.
|
||||
Do not forget to update your Makefile.PL and MANIFEST to include this as
|
||||
an extra PM file that should be installed.
|
||||
|
||||
If you connect via DBD::ODBC, you should use version 0.38 or greater;
|
||||
|
||||
Please take a critical look at the data returned!
|
||||
ODBC drivers vary dramatically in their quality.
|
||||
|
||||
The generator assumes that most values are static and places these
|
||||
values directly in the %info hash.
|
||||
A few examples show the use of CODE references and the implementation
|
||||
via subroutines.
|
||||
It is very likely that you will have to write additional subroutines for
|
||||
values depending on the session state or server version, e.g.
|
||||
SQL_DBMS_VER.
|
||||
|
||||
A possible implementation of DBD::Driver::db::get_info() may look like:
|
||||
|
||||
sub get_info {
|
||||
my($dbh, $info_type) = @_;
|
||||
require DBD::Driver::GetInfo;
|
||||
my $v = $DBD::Driver::GetInfo::info{int($info_type)};
|
||||
$v = $v->($dbh) if ref $v eq 'CODE';
|
||||
return $v;
|
||||
}
|
||||
|
||||
Please replace Driver (or "<foo>") with the name of your driver.
|
||||
Note that this stub function is generated for you by write_getinfo_pm
|
||||
function, but you must manually transfer the code to Driver.pm.
|
||||
|
||||
=cut
|
||||
|
||||
sub write_getinfo_pm
|
||||
{
|
||||
my ($dsn, $user, $pass, $driver) = @_ ? @_ : @ARGV;
|
||||
my $dbh = DBI->connect($dsn, $user, $pass, {RaiseError=>1});
|
||||
$driver = "<foo>" unless defined $driver;
|
||||
|
||||
print <<PERL;
|
||||
|
||||
# Transfer this to ${driver}.pm
|
||||
|
||||
# The get_info function was automatically generated by
|
||||
# DBI::DBD::Metadata::write_getinfo_pm v$DBI::DBD::Metadata::VERSION.
|
||||
|
||||
package DBD::${driver}::db; # This line can be removed once transferred.
|
||||
|
||||
sub get_info {
|
||||
my(\$dbh, \$info_type) = \@_;
|
||||
require DBD::${driver}::GetInfo;
|
||||
my \$v = \$DBD::${driver}::GetInfo::info{int(\$info_type)};
|
||||
\$v = \$v->(\$dbh) if ref \$v eq 'CODE';
|
||||
return \$v;
|
||||
}
|
||||
|
||||
# Transfer this to lib/DBD/${driver}/GetInfo.pm
|
||||
|
||||
# The \%info hash was automatically generated by
|
||||
# DBI::DBD::Metadata::write_getinfo_pm v$DBI::DBD::Metadata::VERSION.
|
||||
|
||||
package DBD::${driver}::GetInfo;
|
||||
|
||||
use strict;
|
||||
use DBD::${driver};
|
||||
|
||||
# Beware: not officially documented interfaces...
|
||||
# use DBI::Const::GetInfoType qw(\%GetInfoType);
|
||||
# use DBI::Const::GetInfoReturn qw(\%GetInfoReturnTypes \%GetInfoReturnValues);
|
||||
|
||||
my \$sql_driver = '${driver}';
|
||||
my \$sql_ver_fmt = '%02d.%02d.%04d'; # ODBC version string: ##.##.#####
|
||||
my \$sql_driver_ver = sprintf \$sql_ver_fmt, split (/\\./, \$DBD::${driver}::VERSION);
|
||||
PERL
|
||||
|
||||
my $kw_map = 0;
|
||||
{
|
||||
# Informix CLI (ODBC) v3.81.0000 does not return a list of keywords.
|
||||
local $\ = "\n";
|
||||
local $, = "\n";
|
||||
my ($kw) = $dbh->get_info($GetInfoType{SQL_KEYWORDS});
|
||||
if ($kw)
|
||||
{
|
||||
print "\nmy \@Keywords = qw(\n";
|
||||
print sort split /,/, $kw;
|
||||
print ");\n\n";
|
||||
print "sub sql_keywords {\n";
|
||||
print q% return join ',', @Keywords;%;
|
||||
print "\n}\n\n";
|
||||
$kw_map = 1;
|
||||
}
|
||||
}
|
||||
|
||||
print <<'PERL';
|
||||
|
||||
sub sql_data_source_name {
|
||||
my $dbh = shift;
|
||||
return "dbi:$sql_driver:" . $dbh->{Name};
|
||||
}
|
||||
|
||||
sub sql_user_name {
|
||||
my $dbh = shift;
|
||||
# CURRENT_USER is a non-standard attribute, probably undef
|
||||
# Username is a standard DBI attribute
|
||||
return $dbh->{CURRENT_USER} || $dbh->{Username};
|
||||
}
|
||||
|
||||
PERL
|
||||
|
||||
print "\nour \%info = (\n";
|
||||
foreach my $key (sort keys %GetInfoType)
|
||||
{
|
||||
my $num = $GetInfoType{$key};
|
||||
my $val = eval { $dbh->get_info($num); };
|
||||
if ($key eq 'SQL_DATA_SOURCE_NAME') {
|
||||
$val = '\&sql_data_source_name';
|
||||
}
|
||||
elsif ($key eq 'SQL_KEYWORDS') {
|
||||
$val = ($kw_map) ? '\&sql_keywords' : 'undef';
|
||||
}
|
||||
elsif ($key eq 'SQL_DRIVER_NAME') {
|
||||
$val = "\$INC{'DBD/$driver.pm'}";
|
||||
}
|
||||
elsif ($key eq 'SQL_DRIVER_VER') {
|
||||
$val = '$sql_driver_ver';
|
||||
}
|
||||
elsif ($key eq 'SQL_USER_NAME') {
|
||||
$val = '\&sql_user_name';
|
||||
}
|
||||
elsif (not defined $val) {
|
||||
$val = 'undef';
|
||||
}
|
||||
elsif ($val eq '') {
|
||||
$val = "''";
|
||||
}
|
||||
elsif ($val =~ /\D/) {
|
||||
$val =~ s/\\/\\\\/g;
|
||||
$val =~ s/'/\\'/g;
|
||||
$val = "'$val'";
|
||||
}
|
||||
printf "%s %5d => %-30s # %s\n", (($val eq 'undef') ? '#' : ' '), $num, "$val,", $key;
|
||||
}
|
||||
print ");\n\n1;\n\n__END__\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head1 Generating a TypeInfo package for a driver
|
||||
|
||||
The C<write_typeinfo_pm> function in the DBI::DBD::Metadata module generates
|
||||
on standard output the data needed for a driver's type_info_all method.
|
||||
It also provides default implementations of the type_info_all
|
||||
method for inclusion in the driver's main implementation file.
|
||||
|
||||
The driver parameter is the name of the driver for which the methods
|
||||
will be generated; for the sake of examples, this will be "Driver".
|
||||
Typically, the dsn parameter will be of the form "dbi:ODBC:odbc_dsn",
|
||||
where the odbc_dsn is a DSN for one of the driver's databases.
|
||||
The user and pass parameters are the other optional connection
|
||||
parameters that will be provided to the DBI connect method.
|
||||
|
||||
Once you are sure that it is producing reasonably sane data, you should
|
||||
typically redirect the standard output to lib/DBD/Driver/TypeInfo.pm,
|
||||
and then hand edit the result if necessary.
|
||||
Do not forget to update your Makefile.PL and MANIFEST to include this as
|
||||
an extra PM file that should be installed.
|
||||
|
||||
Please take a critical look at the data returned!
|
||||
ODBC drivers vary dramatically in their quality.
|
||||
|
||||
The generator assumes that all the values are static and places these
|
||||
values directly in the %info hash.
|
||||
|
||||
A possible implementation of DBD::Driver::type_info_all() may look like:
|
||||
|
||||
sub type_info_all {
|
||||
my ($dbh) = @_;
|
||||
require DBD::Driver::TypeInfo;
|
||||
return [ @$DBD::Driver::TypeInfo::type_info_all ];
|
||||
}
|
||||
|
||||
Please replace Driver (or "<foo>") with the name of your driver.
|
||||
Note that this stub function is generated for you by the write_typeinfo_pm
|
||||
function, but you must manually transfer the code to Driver.pm.
|
||||
|
||||
=cut
|
||||
|
||||
|
||||
# These two are used by fmt_value...
|
||||
my %dbi_inv;
|
||||
my %sql_type_inv;
|
||||
|
||||
#-DEBUGGING-#
|
||||
#sub print_hash
|
||||
#{
|
||||
# my ($name, %hash) = @_;
|
||||
# print "Hash: $name\n";
|
||||
# foreach my $key (keys %hash)
|
||||
# {
|
||||
# print "$key => $hash{$key}\n";
|
||||
# }
|
||||
#}
|
||||
#-DEBUGGING-#
|
||||
|
||||
sub inverse_hash
|
||||
{
|
||||
my (%hash) = @_;
|
||||
my (%inv);
|
||||
foreach my $key (keys %hash)
|
||||
{
|
||||
my $val = $hash{$key};
|
||||
die "Double mapping for key value $val ($inv{$val}, $key)!"
|
||||
if (defined $inv{$val});
|
||||
$inv{$val} = $key;
|
||||
}
|
||||
return %inv;
|
||||
}
|
||||
|
||||
sub fmt_value
|
||||
{
|
||||
my ($num, $val) = @_;
|
||||
if (!defined $val)
|
||||
{
|
||||
$val = "undef";
|
||||
}
|
||||
elsif ($val !~ m/^[-+]?\d+$/)
|
||||
{
|
||||
# All the numbers in type_info_all are integers!
|
||||
# Anything that isn't an integer is a string.
|
||||
# Ensure that no double quotes screw things up.
|
||||
$val =~ s/"/\\"/g if ($val =~ m/"/o);
|
||||
$val = qq{"$val"};
|
||||
}
|
||||
elsif ($dbi_inv{$num} =~ m/^(SQL_)?DATA_TYPE$/)
|
||||
{
|
||||
# All numeric...
|
||||
$val = $sql_type_inv{$val}
|
||||
if (defined $sql_type_inv{$val});
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
sub write_typeinfo_pm
|
||||
{
|
||||
my ($dsn, $user, $pass, $driver) = @_ ? @_ : @ARGV;
|
||||
my $dbh = DBI->connect($dsn, $user, $pass, {AutoCommit=>1, RaiseError=>1});
|
||||
$driver = "<foo>" unless defined $driver;
|
||||
|
||||
print <<PERL;
|
||||
|
||||
# Transfer this to ${driver}.pm
|
||||
|
||||
# The type_info_all function was automatically generated by
|
||||
# DBI::DBD::Metadata::write_typeinfo_pm v$DBI::DBD::Metadata::VERSION.
|
||||
|
||||
package DBD::${driver}::db; # This line can be removed once transferred.
|
||||
|
||||
sub type_info_all
|
||||
{
|
||||
my (\$dbh) = \@_;
|
||||
require DBD::${driver}::TypeInfo;
|
||||
return [ \@\$DBD::${driver}::TypeInfo::type_info_all ];
|
||||
}
|
||||
|
||||
# Transfer this to lib/DBD/${driver}/TypeInfo.pm.
|
||||
# Don't forget to add version and intellectual property control information.
|
||||
|
||||
# The \%type_info_all hash was automatically generated by
|
||||
# DBI::DBD::Metadata::write_typeinfo_pm v$DBI::DBD::Metadata::VERSION.
|
||||
|
||||
package DBD::${driver}::TypeInfo;
|
||||
|
||||
{
|
||||
require Exporter;
|
||||
require DynaLoader;
|
||||
\@ISA = qw(Exporter DynaLoader);
|
||||
\@EXPORT = qw(type_info_all);
|
||||
use DBI qw(:sql_types);
|
||||
|
||||
PERL
|
||||
|
||||
# Generate SQL type name mapping hashes.
|
||||
# See code fragment in DBI specification.
|
||||
my %sql_type_map;
|
||||
foreach (@{$DBI::EXPORT_TAGS{sql_types}})
|
||||
{
|
||||
no strict 'refs';
|
||||
$sql_type_map{$_} = &{"DBI::$_"}();
|
||||
$sql_type_inv{$sql_type_map{$_}} = $_;
|
||||
}
|
||||
#-DEBUG-# print_hash("sql_type_map", %sql_type_map);
|
||||
#-DEBUG-# print_hash("sql_type_inv", %sql_type_inv);
|
||||
|
||||
my %dbi_map =
|
||||
(
|
||||
TYPE_NAME => 0,
|
||||
DATA_TYPE => 1,
|
||||
COLUMN_SIZE => 2,
|
||||
LITERAL_PREFIX => 3,
|
||||
LITERAL_SUFFIX => 4,
|
||||
CREATE_PARAMS => 5,
|
||||
NULLABLE => 6,
|
||||
CASE_SENSITIVE => 7,
|
||||
SEARCHABLE => 8,
|
||||
UNSIGNED_ATTRIBUTE => 9,
|
||||
FIXED_PREC_SCALE => 10,
|
||||
AUTO_UNIQUE_VALUE => 11,
|
||||
LOCAL_TYPE_NAME => 12,
|
||||
MINIMUM_SCALE => 13,
|
||||
MAXIMUM_SCALE => 14,
|
||||
SQL_DATA_TYPE => 15,
|
||||
SQL_DATETIME_SUB => 16,
|
||||
NUM_PREC_RADIX => 17,
|
||||
INTERVAL_PRECISION => 18,
|
||||
);
|
||||
|
||||
#-DEBUG-# print_hash("dbi_map", %dbi_map);
|
||||
|
||||
%dbi_inv = inverse_hash(%dbi_map);
|
||||
|
||||
#-DEBUG-# print_hash("dbi_inv", %dbi_inv);
|
||||
|
||||
my $maxlen = 0;
|
||||
foreach my $key (keys %dbi_map)
|
||||
{
|
||||
$maxlen = length($key) if length($key) > $maxlen;
|
||||
}
|
||||
|
||||
# Print the name/value mapping entry in the type_info_all array;
|
||||
my $fmt = " \%-${maxlen}s => \%2d,\n";
|
||||
my $numkey = 0;
|
||||
my $maxkey = 0;
|
||||
print " \$type_info_all = [\n {\n";
|
||||
foreach my $i (sort { $a <=> $b } keys %dbi_inv)
|
||||
{
|
||||
printf($fmt, $dbi_inv{$i}, $i);
|
||||
$numkey++;
|
||||
$maxkey = $i;
|
||||
}
|
||||
print " },\n";
|
||||
|
||||
print STDERR "### WARNING - Non-dense set of keys ($numkey keys, $maxkey max key)\n"
|
||||
unless $numkey = $maxkey + 1;
|
||||
|
||||
my $h = $dbh->type_info_all;
|
||||
my @tia = @$h;
|
||||
my %odbc_map = map { uc $_ => $tia[0]->{$_} } keys %{$tia[0]};
|
||||
shift @tia; # Remove the mapping reference.
|
||||
my $numtyp = $#tia;
|
||||
|
||||
#-DEBUG-# print_hash("odbc_map", %odbc_map);
|
||||
|
||||
# In theory, the key/number mapping sequence for %dbi_map
|
||||
# should be the same as the one from the ODBC driver. However, to
|
||||
# prevent the possibility of mismatches, and to deal with older
|
||||
# missing attributes or unexpected new ones, we chase back through
|
||||
# the %dbi_inv and %odbc_map hashes, generating @dbi_to_odbc
|
||||
# to map our new key number to the old one.
|
||||
# Report if @dbi_to_odbc is not an identity mapping.
|
||||
my @dbi_to_odbc;
|
||||
foreach my $num (sort { $a <=> $b } keys %dbi_inv)
|
||||
{
|
||||
# Find the name in %dbi_inv that matches this index number.
|
||||
my $dbi_key = $dbi_inv{$num};
|
||||
#-DEBUG-# print "dbi_key = $dbi_key\n";
|
||||
#-DEBUG-# print "odbc_key = $odbc_map{$dbi_key}\n";
|
||||
# Find the index in %odbc_map that has this key.
|
||||
$dbi_to_odbc[$num] = (defined $odbc_map{$dbi_key}) ? $odbc_map{$dbi_key} : undef;
|
||||
}
|
||||
|
||||
# Determine the length of the longest formatted value in each field
|
||||
my @len;
|
||||
for (my $i = 0; $i <= $numtyp; $i++)
|
||||
{
|
||||
my @odbc_val = @{$tia[$i]};
|
||||
for (my $num = 0; $num <= $maxkey; $num++)
|
||||
{
|
||||
# Find the value of the entry in the @odbc_val array.
|
||||
my $val = (defined $dbi_to_odbc[$num]) ? $odbc_val[$dbi_to_odbc[$num]] : undef;
|
||||
$val = fmt_value($num, $val);
|
||||
#-DEBUG-# print "val = $val\n";
|
||||
$val = "$val,";
|
||||
$len[$num] = length($val) if !defined $len[$num] || length($val) > $len[$num];
|
||||
}
|
||||
}
|
||||
|
||||
# Generate format strings to left justify each string in maximum field width.
|
||||
my @fmt;
|
||||
for (my $i = 0; $i <= $maxkey; $i++)
|
||||
{
|
||||
$fmt[$i] = "%-$len[$i]s";
|
||||
#-DEBUG-# print "fmt[$i] = $fmt[$i]\n";
|
||||
}
|
||||
|
||||
# Format the data from type_info_all
|
||||
for (my $i = 0; $i <= $numtyp; $i++)
|
||||
{
|
||||
my @odbc_val = @{$tia[$i]};
|
||||
print " [ ";
|
||||
for (my $num = 0; $num <= $maxkey; $num++)
|
||||
{
|
||||
# Find the value of the entry in the @odbc_val array.
|
||||
my $val = (defined $dbi_to_odbc[$num]) ? $odbc_val[$dbi_to_odbc[$num]] : undef;
|
||||
$val = fmt_value($num, $val);
|
||||
printf $fmt[$num], "$val,";
|
||||
}
|
||||
print " ],\n";
|
||||
}
|
||||
|
||||
print " ];\n\n 1;\n}\n\n__END__\n";
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
Jonathan Leffler <jleffler@us.ibm.com> (previously <jleffler@informix.com>),
|
||||
Jochen Wiedmann <joe@ispsoft.de>,
|
||||
Steffen Goeldner <sgoeldner@cpan.org>,
|
||||
and Tim Bunce <dbi-users@perl.org>.
|
||||
|
||||
=cut
|
||||
2233
database/perl/vendor/lib/DBI/DBD/SqlEngine.pm
vendored
Normal file
2233
database/perl/vendor/lib/DBI/DBD/SqlEngine.pm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
851
database/perl/vendor/lib/DBI/DBD/SqlEngine/Developers.pod
vendored
Normal file
851
database/perl/vendor/lib/DBI/DBD/SqlEngine/Developers.pod
vendored
Normal file
@@ -0,0 +1,851 @@
|
||||
=head1 NAME
|
||||
|
||||
DBI::DBD::SqlEngine::Developers - Developers documentation for DBI::DBD::SqlEngine
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package DBD::myDriver;
|
||||
|
||||
use base qw(DBI::DBD::SqlEngine);
|
||||
|
||||
sub driver
|
||||
{
|
||||
...
|
||||
my $drh = $proto->SUPER::driver($attr);
|
||||
...
|
||||
return $drh->{class};
|
||||
}
|
||||
|
||||
sub CLONE { ... }
|
||||
|
||||
package DBD::myDriver::dr;
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::dr);
|
||||
|
||||
sub data_sources { ... }
|
||||
...
|
||||
|
||||
package DBD::myDriver::db;
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::db);
|
||||
|
||||
sub init_valid_attributes { ... }
|
||||
sub init_default_attributes { ... }
|
||||
sub set_versions { ... }
|
||||
sub validate_STORE_attr { my ($dbh, $attrib, $value) = @_; ... }
|
||||
sub validate_FETCH_attr { my ($dbh, $attrib) = @_; ... }
|
||||
sub get_myd_versions { ... }
|
||||
sub get_avail_tables { ... }
|
||||
|
||||
package DBD::myDriver::st;
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::st);
|
||||
|
||||
sub FETCH { ... }
|
||||
sub STORE { ... }
|
||||
|
||||
package DBD::myDriver::Statement;
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::Statement);
|
||||
|
||||
sub open_table { ... }
|
||||
|
||||
package DBD::myDriver::Table;
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::Table);
|
||||
|
||||
my %reset_on_modify = (
|
||||
myd_abc => "myd_foo",
|
||||
myd_mno => "myd_bar",
|
||||
);
|
||||
__PACKAGE__->register_reset_on_modify( \%reset_on_modify );
|
||||
my %compat_map = (
|
||||
abc => 'foo_abc',
|
||||
xyz => 'foo_xyz',
|
||||
);
|
||||
__PACKAGE__->register_compat_map( \%compat_map );
|
||||
|
||||
sub bootstrap_table_meta { ... }
|
||||
sub init_table_meta { ... }
|
||||
sub table_meta_attr_changed { ... }
|
||||
sub open_data { ... }
|
||||
|
||||
sub new { ... }
|
||||
|
||||
sub fetch_row { ... }
|
||||
sub push_row { ... }
|
||||
sub push_names { ... }
|
||||
sub seek { ... }
|
||||
sub truncate { ... }
|
||||
sub drop { ... }
|
||||
|
||||
# optimize the SQL engine by add one or more of
|
||||
sub update_current_row { ... }
|
||||
# or
|
||||
sub update_specific_row { ... }
|
||||
# or
|
||||
sub update_one_row { ... }
|
||||
# or
|
||||
sub insert_new_row { ... }
|
||||
# or
|
||||
sub delete_current_row { ... }
|
||||
# or
|
||||
sub delete_one_row { ... }
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This document describes the interface of DBI::DBD::SqlEngine for DBD
|
||||
developers who write DBI::DBD::SqlEngine based DBI drivers. It supplements
|
||||
L<DBI::DBD> and L<DBI::DBD::SqlEngine::HowTo>, which you should read first.
|
||||
|
||||
=head1 CLASSES
|
||||
|
||||
Each DBI driver must provide a package global C<< driver >> method and
|
||||
three DBI related classes:
|
||||
|
||||
=over 4
|
||||
|
||||
=item DBI::DBD::SqlEngine::dr
|
||||
|
||||
Driver package, contains the methods DBI calls indirectly via DBI
|
||||
interface:
|
||||
|
||||
DBI->connect ('DBI:DBM:', undef, undef, {})
|
||||
|
||||
# invokes
|
||||
package DBD::DBM::dr;
|
||||
@DBD::DBM::dr::ISA = qw(DBI::DBD::SqlEngine::dr);
|
||||
|
||||
sub connect ($$;$$$)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
Similar for C<data_sources ()> and C<disconnect_all()>.
|
||||
|
||||
Pure Perl DBI drivers derived from DBI::DBD::SqlEngine usually don't need to
|
||||
override any of the methods provided through the DBD::XXX::dr package.
|
||||
However if you need additional initialization not fitting in
|
||||
C<init_valid_attributes()> and C<init_default_attributes()> of you're ::db
|
||||
class, the connect method might be the final place to be modified.
|
||||
|
||||
=item DBI::DBD::SqlEngine::db
|
||||
|
||||
Contains the methods which are called through DBI database handles
|
||||
(C<< $dbh >>). e.g.,
|
||||
|
||||
$sth = $dbh->prepare ("select * from foo");
|
||||
# returns the f_encoding setting for table foo
|
||||
$dbh->csv_get_meta ("foo", "f_encoding");
|
||||
|
||||
DBI::DBD::SqlEngine provides the typical methods required here. Developers who
|
||||
write DBI drivers based on DBI::DBD::SqlEngine need to override the methods
|
||||
C<< set_versions >> and C<< init_valid_attributes >>.
|
||||
|
||||
=item DBI::DBD::SqlEngine::TieMeta;
|
||||
|
||||
Provides the tie-magic for C<< $dbh->{$drv_pfx . "_meta"} >>. Routes
|
||||
C<STORE> through C<< $drv->set_sql_engine_meta() >> and C<FETCH> through
|
||||
C<< $drv->get_sql_engine_meta() >>. C<DELETE> is not supported, you have
|
||||
to execute a C<DROP TABLE> statement, where applicable.
|
||||
|
||||
=item DBI::DBD::SqlEngine::TieTables;
|
||||
|
||||
Provides the tie-magic for tables in C<< $dbh->{$drv_pfx . "_meta"} >>.
|
||||
Routes C<STORE> though C<< $tblClass->set_table_meta_attr() >> and C<FETCH>
|
||||
though C<< $tblClass->get_table_meta_attr() >>. C<DELETE> removes an
|
||||
attribute from the I<meta object> retrieved by
|
||||
C<< $tblClass->get_table_meta() >>.
|
||||
|
||||
=item DBI::DBD::SqlEngine::st
|
||||
|
||||
Contains the methods to deal with prepared statement handles. e.g.,
|
||||
|
||||
$sth->execute () or die $sth->errstr;
|
||||
|
||||
=item DBI::DBD::SqlEngine::TableSource;
|
||||
|
||||
Base class for 3rd party table sources:
|
||||
|
||||
$dbh->{sql_table_source} = "DBD::Foo::TableSource";
|
||||
|
||||
=item DBI::DBD::SqlEngine::DataSource;
|
||||
|
||||
Base class for 3rd party data sources:
|
||||
|
||||
$dbh->{sql_data_source} = "DBD::Foo::DataSource";
|
||||
|
||||
=item DBI::DBD::SqlEngine::Statement;
|
||||
|
||||
Base class for derived drivers statement engine. Implements C<open_table>.
|
||||
|
||||
=item DBI::DBD::SqlEngine::Table;
|
||||
|
||||
Contains tailoring between SQL engine's requirements and
|
||||
C<DBI::DBD::SqlEngine> magic for finding the right tables and storage.
|
||||
Builds bridges between C<sql_meta> handling of C<DBI::DBD::SqlEngine::db>,
|
||||
table initialization for SQL engines and I<meta object>'s attribute
|
||||
management for derived drivers.
|
||||
|
||||
=back
|
||||
|
||||
=head2 DBI::DBD::SqlEngine
|
||||
|
||||
This is the main package containing the routines to initialize
|
||||
DBI::DBD::SqlEngine based DBI drivers. Primarily the
|
||||
C<< DBI::DBD::SqlEngine::driver >> method is invoked, either directly
|
||||
from DBI when the driver is initialized or from the derived class.
|
||||
|
||||
package DBD::DBM;
|
||||
|
||||
use base qw( DBI::DBD::SqlEngine );
|
||||
|
||||
sub driver
|
||||
{
|
||||
my ( $class, $attr ) = @_;
|
||||
...
|
||||
my $drh = $class->SUPER::driver( $attr );
|
||||
...
|
||||
return $drh;
|
||||
}
|
||||
|
||||
It is not necessary to implement your own driver method as long as
|
||||
additional initialization (e.g. installing more private driver
|
||||
methods) is not required. You do not need to call C<< setup_driver >>
|
||||
as DBI::DBD::SqlEngine takes care of it.
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::dr
|
||||
|
||||
The driver package contains the methods DBI calls indirectly via the DBI
|
||||
interface (see L<DBI/DBI Class Methods>).
|
||||
|
||||
DBI::DBD::SqlEngine based DBI drivers usually do not need to implement anything here,
|
||||
it is enough to do the basic initialization:
|
||||
|
||||
package DBD:XXX::dr;
|
||||
|
||||
@DBD::XXX::dr::ISA = qw (DBI::DBD::SqlEngine::dr);
|
||||
$DBD::XXX::dr::imp_data_size = 0;
|
||||
$DBD::XXX::dr::data_sources_attr = undef;
|
||||
$DBD::XXX::ATTRIBUTION = "DBD::XXX $DBD::XXX::VERSION by Hans Mustermann";
|
||||
|
||||
=head3 Methods provided by C<< DBI::DBD::SqlEngine::dr >>:
|
||||
|
||||
=over 4
|
||||
|
||||
=item connect
|
||||
|
||||
Supervises the driver bootstrap when calling
|
||||
|
||||
DBI->connect( "dbi:Foo", , , { ... } );
|
||||
|
||||
First it instantiates a new driver using C<DBI::_new_dbh>. After that,
|
||||
initial bootstrap of the newly instantiated driver is done by
|
||||
|
||||
$dbh->func( 0, "init_default_attributes" );
|
||||
|
||||
The first argument (C<0>) signals that this is the very first call to
|
||||
C<init_default_attributes>. Modern drivers understand that and do early
|
||||
stage setup here after calling
|
||||
|
||||
package DBD::Foo::db;
|
||||
our @DBD::Foo::db::ISA = qw(DBI::DBD::SqlEngine::db);
|
||||
|
||||
sub init_default_attributes
|
||||
{
|
||||
my ($dbh, $phase) = @_;
|
||||
$dbh->SUPER::init_default_attributes($phase);
|
||||
...; # own setup code, maybe separated by phases
|
||||
}
|
||||
|
||||
When the C<$phase> argument is passed down until
|
||||
C<DBI::DBD::SqlEngine::db::init_default_attributes>, C<connect()> recognizes
|
||||
a I<modern> driver and initializes the attributes from I<DSN> and I<$attr>
|
||||
arguments passed via C<< DBI->connect( $dsn, $user, $pass, \%attr ) >>.
|
||||
|
||||
At the end of the attribute initialization after I<phase 0>, C<connect()>
|
||||
invoked C<init_default_attributes> again for I<phase 1>:
|
||||
|
||||
$dbh->func( 1, "init_default_attributes" );
|
||||
|
||||
=item data_sources
|
||||
|
||||
Returns a list of I<DSN>'s using the C<data_sources> method of the
|
||||
class specified in C<< $dbh->{sql_table_source} >> or via C<\%attr>:
|
||||
|
||||
@ary = DBI->data_sources($driver);
|
||||
@ary = DBI->data_sources($driver, \%attr);
|
||||
|
||||
=item disconnect_all
|
||||
|
||||
C<DBI::DBD::SqlEngine> doesn't have an overall driver cache, so nothing
|
||||
happens here at all.
|
||||
|
||||
=back
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::db
|
||||
|
||||
This package defines the database methods, which are called via the DBI
|
||||
database handle C<< $dbh >>.
|
||||
|
||||
=head3 Methods provided by C<< DBI::DBD::SqlEngine::db >>:
|
||||
|
||||
=over 4
|
||||
|
||||
=item ping
|
||||
|
||||
Simply returns the content of the C<< Active >> attribute. Override
|
||||
when your driver needs more complicated actions here.
|
||||
|
||||
=item prepare
|
||||
|
||||
Prepares a new SQL statement to execute. Returns a statement handle,
|
||||
C<< $sth >> - instance of the DBD:XXX::st. It is neither required nor
|
||||
recommended to override this method.
|
||||
|
||||
=item validate_FETCH_attr
|
||||
|
||||
Called by C<FETCH> to allow inherited drivers do their own attribute
|
||||
name validation. Calling convention is similar to C<FETCH> and the
|
||||
return value is the approved attribute name.
|
||||
|
||||
return $validated_attribute_name;
|
||||
|
||||
In case of validation fails (e.g. accessing private attribute or similar),
|
||||
C<validate_FETCH_attr> is permitted to throw an exception.
|
||||
|
||||
=item FETCH
|
||||
|
||||
Fetches an attribute of a DBI database object. Private handle attributes
|
||||
must have a prefix (this is mandatory). If a requested attribute is
|
||||
detected as a private attribute without a valid prefix, the driver prefix
|
||||
(written as C<$drv_prefix>) is added.
|
||||
|
||||
The driver prefix is extracted from the attribute name and verified against
|
||||
C<< $dbh->{ $drv_prefix . "valid_attrs" } >> (when it exists). If the
|
||||
requested attribute value is not listed as a valid attribute, this method
|
||||
croaks. If the attribute is valid and readonly (listed in C<< $dbh->{
|
||||
$drv_prefix . "readonly_attrs" } >> when it exists), a real copy of the
|
||||
attribute value is returned. So it's not possible to modify
|
||||
C<f_valid_attrs> from outside of DBI::DBD::SqlEngine::db or a derived class.
|
||||
|
||||
=item validate_STORE_attr
|
||||
|
||||
Called by C<STORE> to allow inherited drivers do their own attribute
|
||||
name validation. Calling convention is similar to C<STORE> and the
|
||||
return value is the approved attribute name followed by the approved
|
||||
new value.
|
||||
|
||||
return ($validated_attribute_name, $validated_attribute_value);
|
||||
|
||||
In case of validation fails (e.g. accessing private attribute or similar),
|
||||
C<validate_STORE_attr> is permitted to throw an exception
|
||||
(C<DBI::DBD::SqlEngine::db::validate_STORE_attr> throws an exception when
|
||||
someone tries to assign value other than C<SQL_IC_UPPER .. SQL_IC_MIXED>
|
||||
to C<< $dbh->{sql_identifier_case} >> or
|
||||
C<< $dbh->{sql_quoted_identifier_case} >>).
|
||||
|
||||
=item STORE
|
||||
|
||||
Stores a database private attribute. Private handle attributes must have a
|
||||
prefix (this is mandatory). If a requested attribute is detected as a private
|
||||
attribute without a valid prefix, the driver prefix (written as
|
||||
C<$drv_prefix>) is added. If the database handle has an attribute
|
||||
C<${drv_prefix}_valid_attrs> - for attribute names which are not listed in
|
||||
that hash, this method croaks. If the database handle has an attribute
|
||||
C<${drv_prefix}_readonly_attrs>, only attributes which are not listed there
|
||||
can be stored (once they are initialized). Trying to overwrite such an
|
||||
immutable attribute forces this method to croak.
|
||||
|
||||
An example of a valid attributes list can be found in
|
||||
C<< DBI::DBD::SqlEngine::db::init_valid_attributes >>.
|
||||
|
||||
=item set_versions
|
||||
|
||||
This method sets the attributes C<< f_version >>, C<< sql_nano_version >>,
|
||||
C<< sql_statement_version >> and (if not prohibited by a restrictive
|
||||
C<< ${prefix}_valid_attrs >>) C<< ${prefix}_version >>.
|
||||
|
||||
This method is called at the end of the C<< connect () >> phase.
|
||||
|
||||
When overriding this method, do not forget to invoke the superior one.
|
||||
|
||||
=item init_valid_attributes
|
||||
|
||||
This method is called after the database handle is instantiated as the
|
||||
first attribute initialization.
|
||||
|
||||
C<< DBI::DBD::SqlEngine::db::init_valid_attributes >> initializes the
|
||||
attributes C<sql_valid_attrs> and C<sql_readonly_attrs>.
|
||||
|
||||
When overriding this method, do not forget to invoke the superior one,
|
||||
preferably before doing anything else.
|
||||
|
||||
=item init_default_attributes
|
||||
|
||||
This method is called after the database handle is instantiated to
|
||||
initialize the default attributes. It expects one argument: C<$phase>.
|
||||
If C<$phase> is not given, C<connect> of C<DBI::DBD::SqlEngine::dr>
|
||||
expects this is an old-fashioned driver which isn't capable of multi-phased
|
||||
initialization.
|
||||
|
||||
C<< DBI::DBD::SqlEngine::db::init_default_attributes >> initializes the
|
||||
attributes C<sql_identifier_case>, C<sql_quoted_identifier_case>,
|
||||
C<sql_handler>, C<sql_init_order>, C<sql_meta>, C<sql_engine_version>,
|
||||
C<sql_nano_version> and C<sql_statement_version> when L<SQL::Statement>
|
||||
is available.
|
||||
|
||||
It sets C<sql_init_order> to the given C<$phase>.
|
||||
|
||||
When the derived implementor class provides the attribute to validate
|
||||
attributes (e.g. C<< $dbh->{dbm_valid_attrs} = {...}; >>) or the attribute
|
||||
containing the immutable attributes (e.g. C<< $dbh->{dbm_readonly_attrs}
|
||||
= {...}; >>), the attributes C<drv_valid_attrs>, C<drv_readonly_attrs> and
|
||||
C<drv_version> are added (when available) to the list of valid and
|
||||
immutable attributes (where C<drv_> is interpreted as the driver prefix).
|
||||
|
||||
=item get_versions
|
||||
|
||||
This method is called by the code injected into the instantiated driver to
|
||||
provide the user callable driver method C<< ${prefix}versions >> (e.g.
|
||||
C<< dbm_versions >>, C<< csv_versions >>, ...).
|
||||
|
||||
The DBI::DBD::SqlEngine implementation returns all version information known by
|
||||
DBI::DBD::SqlEngine (e.g. DBI version, Perl version, DBI::DBD::SqlEngine version and
|
||||
the SQL handler version).
|
||||
|
||||
C<get_versions> takes the C<$dbh> as the first argument and optionally a
|
||||
second argument containing a table name. The second argument is not
|
||||
evaluated in C<< DBI::DBD::SqlEngine::db::get_versions >> itself - but
|
||||
might be in the future.
|
||||
|
||||
If the derived implementor class provides a method named
|
||||
C<get_${drv_prefix}versions>, this is invoked and the return value of
|
||||
it is associated to the derived driver name:
|
||||
|
||||
if (my $dgv = $dbh->{ImplementorClass}->can ("get_" . $drv_prefix . "versions") {
|
||||
(my $derived_driver = $dbh->{ImplementorClass}) =~ s/::db$//;
|
||||
$versions{$derived_driver} = &$dgv ($dbh, $table);
|
||||
}
|
||||
|
||||
Override it to add more version information about your module, (e.g.
|
||||
some kind of parser version in case of DBD::CSV, ...), if one line is not
|
||||
enough room to provide all relevant information.
|
||||
|
||||
=item sql_parser_object
|
||||
|
||||
Returns a L<SQL::Parser> instance, when C<< sql_handler >> is set to
|
||||
"SQL::Statement". The parser instance is stored in C<< sql_parser_object >>.
|
||||
|
||||
It is not recommended to override this method.
|
||||
|
||||
=item disconnect
|
||||
|
||||
Disconnects from a database. All local table information is discarded and
|
||||
the C<< Active >> attribute is set to 0.
|
||||
|
||||
=item type_info_all
|
||||
|
||||
Returns information about all the types supported by DBI::DBD::SqlEngine.
|
||||
|
||||
=item table_info
|
||||
|
||||
Returns a statement handle which is prepared to deliver information about
|
||||
all known tables.
|
||||
|
||||
=item list_tables
|
||||
|
||||
Returns a list of all known table names.
|
||||
|
||||
=item quote
|
||||
|
||||
Quotes a string for use in SQL statements.
|
||||
|
||||
=item commit
|
||||
|
||||
Warns about a useless call (if warnings enabled) and returns.
|
||||
DBI::DBD::SqlEngine is typically a driver which commits every action
|
||||
instantly when executed.
|
||||
|
||||
=item rollback
|
||||
|
||||
Warns about a useless call (if warnings enabled) and returns.
|
||||
DBI::DBD::SqlEngine is typically a driver which commits every action
|
||||
instantly when executed.
|
||||
|
||||
=back
|
||||
|
||||
=head3 Attributes used by C<< DBI::DBD::SqlEngine::db >>:
|
||||
|
||||
This section describes attributes which are important to developers of DBI
|
||||
Database Drivers derived from C<DBI::DBD::SqlEngine>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item sql_init_order
|
||||
|
||||
This attribute contains a hash with priorities as key and an array
|
||||
containing the C<$dbh> attributes to be initialized during before/after
|
||||
other attributes.
|
||||
|
||||
C<DBI::DBD::SqlEngine> initializes following attributes:
|
||||
|
||||
$dbh->{sql_init_order} = {
|
||||
0 => [qw( Profile RaiseError PrintError AutoCommit )],
|
||||
90 => [ "sql_meta", $dbh->{$drv_pfx_meta} ? $dbh->{$drv_pfx_meta} : () ]
|
||||
}
|
||||
|
||||
The default priority of not listed attribute keys is C<50>. It is well
|
||||
known that a lot of attributes needed to be set before some table settings
|
||||
are initialized. For example, for L<DBD::DBM>, when using
|
||||
|
||||
my $dbh = DBI->connect( "dbi:DBM:", undef, undef, {
|
||||
f_dir => "/path/to/dbm/databases",
|
||||
dbm_type => "BerkeleyDB",
|
||||
dbm_mldbm => "JSON", # use MLDBM::Serializer::JSON
|
||||
dbm_tables => {
|
||||
quick => {
|
||||
dbm_type => "GDBM_File",
|
||||
dbm_MLDBM => "FreezeThaw"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
This defines a known table C<quick> which uses the L<GDBM_File> backend and
|
||||
L<FreezeThaw> as serializer instead of the overall default L<BerkeleyDB> and
|
||||
L<JSON>. B<But> all files containing the table data have to be searched in
|
||||
C<< $dbh->{f_dir} >>, which requires C<< $dbh->{f_dir} >> must be initialized
|
||||
before C<< $dbh->{sql_meta}->{quick} >> is initialized by
|
||||
C<bootstrap_table_meta> method of L</DBI::DBD::SqlEngine::Table> to get
|
||||
C<< $dbh->{sql_meta}->{quick}->{f_dir} >> being initialized properly.
|
||||
|
||||
=item sql_init_phase
|
||||
|
||||
This attribute is only set during the initialization steps of the DBI
|
||||
Database Driver. It contains the value of the currently run initialization
|
||||
phase. Currently supported phases are I<phase 0> and I<phase 1>. This
|
||||
attribute is set in C<init_default_attributes> and removed in C<init_done>.
|
||||
|
||||
=item sql_engine_in_gofer
|
||||
|
||||
This value has a true value in case of this driver is operated via
|
||||
L<DBD::Gofer>. The impact of being operated via Gofer is a read-only
|
||||
driver (not read-only databases!), so you cannot modify any attributes
|
||||
later - neither any table settings. B<But> you won't get an error in
|
||||
cases you modify table attributes, so please carefully watch
|
||||
C<sql_engine_in_gofer>.
|
||||
|
||||
=item sql_table_source
|
||||
|
||||
Names a class which is responsible for delivering I<data sources> and
|
||||
I<available tables> (Database Driver related). I<data sources> here
|
||||
refers to L<DBI/data_sources>, not C<sql_data_source>.
|
||||
|
||||
See L</DBI::DBD::SqlEngine::TableSource> for details.
|
||||
|
||||
=item sql_data_source
|
||||
|
||||
Name a class which is responsible for handling table resources open
|
||||
and completing table names requested via SQL statements.
|
||||
|
||||
See L</DBI::DBD::SqlEngine::DataSource> for details.
|
||||
|
||||
=item sql_dialect
|
||||
|
||||
Controls the dialect understood by SQL::Parser. Possible values (delivery
|
||||
state of SQL::Statement):
|
||||
|
||||
* ANSI
|
||||
* CSV
|
||||
* AnyData
|
||||
|
||||
Defaults to "CSV". Because an SQL::Parser is instantiated only once and
|
||||
SQL::Parser doesn't allow one to modify the dialect once instantiated,
|
||||
it's strongly recommended to set this flag before any statement is
|
||||
executed (best place is connect attribute hash).
|
||||
|
||||
=back
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::st
|
||||
|
||||
Contains the methods to deal with prepared statement handles:
|
||||
|
||||
=over 4
|
||||
|
||||
=item bind_param
|
||||
|
||||
Common routine to bind placeholders to a statement for execution. It
|
||||
is dangerous to override this method without detailed knowledge about
|
||||
the DBI::DBD::SqlEngine internal storage structure.
|
||||
|
||||
=item execute
|
||||
|
||||
Executes a previously prepared statement (with placeholders, if any).
|
||||
|
||||
=item finish
|
||||
|
||||
Finishes a statement handle, discards all buffered results. The prepared
|
||||
statement is not discarded so the statement can be executed again.
|
||||
|
||||
=item fetch
|
||||
|
||||
Fetches the next row from the result-set. This method may be rewritten
|
||||
in a later version and if it's overridden in a derived class, the
|
||||
derived implementation should not rely on the storage details.
|
||||
|
||||
=item fetchrow_arrayref
|
||||
|
||||
Alias for C<< fetch >>.
|
||||
|
||||
=item FETCH
|
||||
|
||||
Fetches statement handle attributes. Supported attributes (for full overview
|
||||
see L<DBI/Statement Handle Attributes>) are C<NAME>, C<TYPE>, C<PRECISION>
|
||||
and C<NULLABLE>. Each column is returned as C<NULLABLE> which might be wrong
|
||||
depending on the derived backend storage. If the statement handle has
|
||||
private attributes, they can be fetched using this method, too. B<Note> that
|
||||
statement attributes are not associated with any table used in this statement.
|
||||
|
||||
This method usually requires extending in a derived implementation.
|
||||
See L<DBD::CSV> or L<DBD::DBM> for some example.
|
||||
|
||||
=item STORE
|
||||
|
||||
Allows storing of statement private attributes. No special handling is
|
||||
currently implemented here.
|
||||
|
||||
=item rows
|
||||
|
||||
Returns the number of rows affected by the last execute. This method might
|
||||
return C<undef>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::TableSource
|
||||
|
||||
Provides data sources and table information on database driver and database
|
||||
handle level.
|
||||
|
||||
package DBI::DBD::SqlEngine::TableSource;
|
||||
|
||||
sub data_sources ($;$)
|
||||
{
|
||||
my ( $class, $drh, $attrs ) = @_;
|
||||
...
|
||||
}
|
||||
|
||||
sub avail_tables
|
||||
{
|
||||
my ( $class, $drh ) = @_;
|
||||
...
|
||||
}
|
||||
|
||||
The C<data_sources> method is called when the user invokes any of the
|
||||
following:
|
||||
|
||||
@ary = DBI->data_sources($driver);
|
||||
@ary = DBI->data_sources($driver, \%attr);
|
||||
|
||||
@ary = $dbh->data_sources();
|
||||
@ary = $dbh->data_sources(\%attr);
|
||||
|
||||
The C<avail_tables> method is called when the user invokes any of the
|
||||
following:
|
||||
|
||||
@names = $dbh->tables( $catalog, $schema, $table, $type );
|
||||
|
||||
$sth = $dbh->table_info( $catalog, $schema, $table, $type );
|
||||
$sth = $dbh->table_info( $catalog, $schema, $table, $type, \%attr );
|
||||
|
||||
$dbh->func( "list_tables" );
|
||||
|
||||
Every time where an C<\%attr> argument can be specified, this C<\%attr>
|
||||
object's C<sql_table_source> attribute is preferred over the C<$dbh>
|
||||
attribute or the driver default.
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::DataSource
|
||||
|
||||
Provides base functionality for dealing with tables. It is primarily
|
||||
designed for allowing transparent access to files on disk or already
|
||||
opened (file-)streams (e.g. for DBD::CSV).
|
||||
|
||||
Derived classes shall be restricted to similar functionality, too (e.g.
|
||||
opening streams from an archive, transparently compress/uncompress
|
||||
log files before parsing them,
|
||||
|
||||
package DBI::DBD::SqlEngine::DataSource;
|
||||
|
||||
sub complete_table_name ($$;$)
|
||||
{
|
||||
my ( $self, $meta, $table, $respect_case ) = @_;
|
||||
...
|
||||
}
|
||||
|
||||
The method C<complete_table_name> is called when first setting up the
|
||||
I<meta information> for a table:
|
||||
|
||||
"SELECT user.id, user.name, user.shell FROM user WHERE ..."
|
||||
|
||||
results in opening the table C<user>. First step of the table open
|
||||
process is completing the name. Let's imagine you're having a L<DBD::CSV>
|
||||
handle with following settings:
|
||||
|
||||
$dbh->{sql_identifier_case} = SQL_IC_LOWER;
|
||||
$dbh->{f_ext} = '.lst';
|
||||
$dbh->{f_dir} = '/data/web/adrmgr';
|
||||
|
||||
Those settings will result in looking for files matching
|
||||
C<[Uu][Ss][Ee][Rr](\.lst)?$> in C</data/web/adrmgr/>. The scanning of the
|
||||
directory C</data/web/adrmgr/> and the pattern match check will be done
|
||||
in C<DBD::File::DataSource::File> by the C<complete_table_name> method.
|
||||
|
||||
If you intend to provide other sources of data streams than files, in
|
||||
addition to provide an appropriate C<complete_table_name> method, a method
|
||||
to open the resource is required:
|
||||
|
||||
package DBI::DBD::SqlEngine::DataSource;
|
||||
|
||||
sub open_data ($)
|
||||
{
|
||||
my ( $self, $meta, $attrs, $flags ) = @_;
|
||||
...
|
||||
}
|
||||
|
||||
After the method C<open_data> has been run successfully, the table's meta
|
||||
information are in a state which allows the table's data accessor methods
|
||||
will be able to fetch/store row information. Implementation details heavily
|
||||
depends on the table implementation, whereby the most famous is surely
|
||||
L<DBD::File::Table|DBD::File/DBD::File::Table>.
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::Statement
|
||||
|
||||
Derives from DBI::SQL::Nano::Statement for unified naming when deriving
|
||||
new drivers. No additional feature is provided from here.
|
||||
|
||||
=head2 DBI::DBD::SqlEngine::Table
|
||||
|
||||
Derives from DBI::SQL::Nano::Table for unified naming when deriving
|
||||
new drivers.
|
||||
|
||||
You should consult the documentation of C<< SQL::Eval::Table >> (see
|
||||
L<SQL::Eval>) to get more information about the abstract methods of the
|
||||
table's base class you have to override and a description of the table
|
||||
meta information expected by the SQL engines.
|
||||
|
||||
=over 4
|
||||
|
||||
=item bootstrap_table_meta
|
||||
|
||||
Initializes a table meta structure. Can be safely overridden in a
|
||||
derived class, as long as the C<< SUPER >> method is called at the end
|
||||
of the overridden method.
|
||||
|
||||
It copies the following attributes from the database into the table meta data
|
||||
C<< $dbh->{ReadOnly} >> into C<< $meta->{readonly} >>, C<sql_identifier_case>
|
||||
and C<sql_data_source> and makes them sticky to the table.
|
||||
|
||||
This method should be called before you attempt to map between file
|
||||
name and table name to ensure the correct directory, extension etc. are
|
||||
used.
|
||||
|
||||
=item init_table_meta
|
||||
|
||||
Initializes more attributes of the table meta data - usually more
|
||||
expensive ones (e.g. those which require class instantiations) - when
|
||||
the file name and the table name could mapped.
|
||||
|
||||
=item get_table_meta
|
||||
|
||||
Returns the table meta data. If there are none for the required table,
|
||||
a new one is initialized. When after bootstrapping a new I<table_meta>
|
||||
and L<completing the table name|/DBI::DBD::SqlEngine::DataSource> a
|
||||
mapping can be established between an existing I<table_meta> and the
|
||||
new bootstrapped one, the already existing is used and a mapping
|
||||
shortcut between the recent used table name and the already known
|
||||
table name is hold in C<< $dbh->{sql_meta_map} >>. When it fails,
|
||||
nothing is returned. On success, the name of the table and the meta data
|
||||
structure is returned.
|
||||
|
||||
=item get_table_meta_attr
|
||||
|
||||
Returns a single attribute from the table meta data. If the attribute
|
||||
name appears in C<%compat_map>, the attribute name is updated from
|
||||
there.
|
||||
|
||||
=item set_table_meta_attr
|
||||
|
||||
Sets a single attribute in the table meta data. If the attribute
|
||||
name appears in C<%compat_map>, the attribute name is updated from
|
||||
there.
|
||||
|
||||
=item table_meta_attr_changed
|
||||
|
||||
Called when an attribute of the meta data is modified.
|
||||
|
||||
If the modified attribute requires to reset a calculated attribute, the
|
||||
calculated attribute is reset (deleted from meta data structure) and
|
||||
the I<initialized> flag is removed, too. The decision is made based on
|
||||
C<%register_reset_on_modify>.
|
||||
|
||||
=item register_reset_on_modify
|
||||
|
||||
Allows C<set_table_meta_attr> to reset meta attributes when special
|
||||
attributes are modified. For DBD::File, modifying one of C<f_file>, C<f_dir>,
|
||||
C<f_ext> or C<f_lockfile> will reset C<f_fqfn>. DBD::DBM extends the
|
||||
list for C<dbm_type> and C<dbm_mldbm> to reset the value of C<dbm_tietype>.
|
||||
|
||||
If your DBD has calculated values in the meta data area, then call
|
||||
C<register_reset_on_modify>:
|
||||
|
||||
my %reset_on_modify = ( "xxx_foo" => "xxx_bar" );
|
||||
__PACKAGE__->register_reset_on_modify( \%reset_on_modify );
|
||||
|
||||
=item register_compat_map
|
||||
|
||||
Allows C<get_table_meta_attr> and C<set_table_meta_attr> to update the
|
||||
attribute name to the current favored one:
|
||||
|
||||
# from DBD::DBM
|
||||
my %compat_map = ( "dbm_ext" => "f_ext" );
|
||||
__PACKAGE__->register_compat_map( \%compat_map );
|
||||
|
||||
=item open_data
|
||||
|
||||
Called to open the table's data storage. This is silently forwarded
|
||||
to C<< $meta->{sql_data_source}->open_data() >>.
|
||||
|
||||
After this is done, a derived class might add more steps in an overridden
|
||||
C<< open_file >> method.
|
||||
|
||||
=item new
|
||||
|
||||
Instantiates the table. This is done in 3 steps:
|
||||
|
||||
1. get the table meta data
|
||||
2. open the data file
|
||||
3. bless the table data structure using inherited constructor new
|
||||
|
||||
It is not recommended to override the constructor of the table class.
|
||||
Find a reasonable place to add you extensions in one of the above four
|
||||
methods.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
The module DBI::DBD::SqlEngine is currently maintained by
|
||||
|
||||
H.Merijn Brand < h.m.brand at xs4all.nl > and
|
||||
Jens Rehsack < rehsack at googlemail.com >
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2010 by H.Merijn Brand & Jens Rehsack
|
||||
|
||||
All rights reserved.
|
||||
|
||||
You may freely distribute and/or modify this module under the terms of
|
||||
either the GNU General Public License (GPL) or the Artistic License, as
|
||||
specified in the Perl README file.
|
||||
|
||||
=cut
|
||||
333
database/perl/vendor/lib/DBI/DBD/SqlEngine/HowTo.pod
vendored
Normal file
333
database/perl/vendor/lib/DBI/DBD/SqlEngine/HowTo.pod
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
=head1 NAME
|
||||
|
||||
DBI::DBD::SqlEngine::HowTo - Guide to create DBI::DBD::SqlEngine based driver
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
perldoc DBI::DBD::SqlEngine::HowTo
|
||||
perldoc DBI
|
||||
perldoc DBI::DBD
|
||||
perldoc DBI::DBD::SqlEngine::Developers
|
||||
perldoc SQL::Eval
|
||||
perldoc DBI::DBD::SqlEngine
|
||||
perldoc DBI::DBD::SqlEngine::HowTo
|
||||
perldoc SQL::Statement::Embed
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This document provides a step-by-step guide, how to create a new
|
||||
C<DBI::DBD::SqlEngine> based DBD. It expects that you carefully read the
|
||||
L<DBI> documentation and that you're familiar with L<DBI::DBD> and had
|
||||
read and understood L<DBD::ExampleP>.
|
||||
|
||||
This document addresses experienced developers who are really sure that
|
||||
they need to invest time when writing a new DBI Driver. Writing a DBI
|
||||
Driver is neither a weekend project nor an easy job for hobby coders
|
||||
after work. Expect one or two man-month of time for the first start.
|
||||
|
||||
Those who are still reading, should be able to sing the rules of
|
||||
L<DBI::DBD/CREATING A NEW DRIVER>.
|
||||
|
||||
=head1 CREATING DRIVER CLASSES
|
||||
|
||||
Do you have an entry in DBI's DBD registry? DBI::DBD::SqlEngine expect
|
||||
having a unique prefix for every driver class in inheritance chain.
|
||||
|
||||
It's easy to get a prefix - just drop the DBI team a note
|
||||
(L<DBI/GETTING_HELP>). If you want for some reason hide your work, take
|
||||
a look at L<Class::Method::Modifiers> how to wrap a private prefix method
|
||||
around existing C<driver_prefix>.
|
||||
|
||||
For this guide, a prefix of C<foo_> is assumed.
|
||||
|
||||
=head2 Sample Skeleton
|
||||
|
||||
package DBD::Foo;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use vars qw($VERSION);
|
||||
use base qw(DBI::DBD::SqlEngine);
|
||||
|
||||
use DBI ();
|
||||
|
||||
$VERSION = "0.001";
|
||||
|
||||
package DBD::Foo::dr;
|
||||
|
||||
use vars qw(@ISA $imp_data_size);
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::dr);
|
||||
$imp_data_size = 0;
|
||||
|
||||
package DBD::Foo::db;
|
||||
|
||||
use vars qw(@ISA $imp_data_size);
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::db);
|
||||
$imp_data_size = 0;
|
||||
|
||||
package DBD::Foo::st;
|
||||
|
||||
use vars qw(@ISA $imp_data_size);
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::st);
|
||||
$imp_data_size = 0;
|
||||
|
||||
package DBD::Foo::Statement;
|
||||
|
||||
use vars qw(@ISA);
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::Statement);
|
||||
|
||||
package DBD::Foo::Table;
|
||||
|
||||
use vars qw(@ISA);
|
||||
|
||||
@ISA = qw(DBI::DBD::SqlEngine::Table);
|
||||
|
||||
1;
|
||||
|
||||
Tiny, eh? And all you have now is a DBD named foo which will is able to
|
||||
deal with temporary tables, as long as you use L<SQL::Statement>. In
|
||||
L<DBI::SQL::Nano> environments, this DBD can do nothing.
|
||||
|
||||
=head2 Deal with own attributes
|
||||
|
||||
Before we start doing usable stuff with our DBI driver, we need to think
|
||||
about what we want to do and how we want to do it.
|
||||
|
||||
Do we need tunable knobs accessible by users? Do we need status
|
||||
information? All this is handled in attributes of the database handles (be
|
||||
careful when your DBD is running "behind" a L<DBD::Gofer> proxy).
|
||||
|
||||
How come the attributes into the DBD and how are they fetchable by the
|
||||
user? Good question, but you should know because you've read the L<DBI>
|
||||
documentation.
|
||||
|
||||
C<DBI::DBD::SqlEngine::db::FETCH> and C<DBI::DBD::SqlEngine::db::STORE>
|
||||
taking care for you - all they need to know is which attribute names
|
||||
are valid and mutable or immutable. Tell them by adding
|
||||
C<init_valid_attributes> to your db class:
|
||||
|
||||
sub init_valid_attributes
|
||||
{
|
||||
my $dbh = $_[0];
|
||||
|
||||
$dbh->SUPER::init_valid_attributes ();
|
||||
|
||||
$dbh->{foo_valid_attrs} = {
|
||||
foo_version => 1, # contains version of this driver
|
||||
foo_valid_attrs => 1, # contains the valid attributes of foo drivers
|
||||
foo_readonly_attrs => 1, # contains immutable attributes of foo drivers
|
||||
foo_bar => 1, # contains the bar attribute
|
||||
foo_baz => 1, # contains the baz attribute
|
||||
foo_manager => 1, # contains the manager of the driver instance
|
||||
foo_manager_type => 1, # contains the manager class of the driver instance
|
||||
};
|
||||
$dbh->{foo_readonly_attrs} = {
|
||||
foo_version => 1, # ensure no-one modifies the driver version
|
||||
foo_valid_attrs => 1, # do not permit one to add more valid attributes ...
|
||||
foo_readonly_attrs => 1, # ... or make the immutable mutable
|
||||
foo_manager => 1, # manager is set internally only
|
||||
};
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
Woooho - but now the user cannot assign new managers? This is intended,
|
||||
overwrite C<STORE> to handle it!
|
||||
|
||||
sub STORE ($$$)
|
||||
{
|
||||
my ( $dbh, $attrib, $value ) = @_;
|
||||
|
||||
$dbh->SUPER::STORE( $attrib, $value );
|
||||
|
||||
# we're still alive, so no exception is thrown ...
|
||||
# by DBI::DBD::SqlEngine::db::STORE
|
||||
if ( $attrib eq "foo_manager_type" )
|
||||
{
|
||||
$dbh->{foo_manager} = $dbh->{foo_manager_type}->new();
|
||||
# ... probably correct some states based on the new
|
||||
# foo_manager_type - see DBD::Sys for an example
|
||||
}
|
||||
}
|
||||
|
||||
But ... my driver runs without a manager until someone first assignes
|
||||
a C<foo_manager_type>. Well, no - there're two places where you can
|
||||
initialize defaults:
|
||||
|
||||
sub init_default_attributes
|
||||
{
|
||||
my ($dbh, $phase) = @_;
|
||||
|
||||
$dbh->SUPER::init_default_attributes($phase);
|
||||
|
||||
if( 0 == $phase )
|
||||
{
|
||||
# init all attributes which have no knowledge about
|
||||
# user settings from DSN or the attribute hash
|
||||
$dbh->{foo_manager_type} = "DBD::Foo::Manager";
|
||||
}
|
||||
elsif( 1 == $phase )
|
||||
{
|
||||
# init phase with more knowledge from DSN or attribute
|
||||
# hash
|
||||
$dbh->{foo_manager} = $dbh->{foo_manager_type}->new();
|
||||
}
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
So far we can prevent the users to use our database driver as data
|
||||
storage for anything and everything. We care only about the real important
|
||||
stuff for peace on earth and alike attributes. But in fact, the driver
|
||||
still can't do anything. It can do less than nothing - meanwhile it's
|
||||
not a stupid storage area anymore.
|
||||
|
||||
=head2 User comfort
|
||||
|
||||
C<DBI::DBD::SqlEngine> since C<0.05> consolidates all persistent meta data
|
||||
of a table into a single structure stored in C<< $dbh->{sql_meta} >>. While
|
||||
DBI::DBD::SqlEngine provides only readonly access to this structure,
|
||||
modifications are still allowed.
|
||||
|
||||
Primarily DBI::DBD::SqlEngine provides access via the setters
|
||||
C<new_sql_engine_meta>, C<get_sql_engine_meta>, C<get_single_table_meta>,
|
||||
C<set_single_table_meta>, C<set_sql_engine_meta> and C<clear_sql_engine_meta>.
|
||||
Those methods are easily accessible by the users via the C<< $dbh->func () >>
|
||||
interface provided by DBI. Well, many users don't feel comfortize when calling
|
||||
|
||||
# don't require extension for tables cars
|
||||
$dbh->func ("cars", "f_ext", ".csv", "set_sql_engine_meta");
|
||||
|
||||
DBI::DBD::SqlEngine will inject a method into your driver to increase the
|
||||
user comfort to allow:
|
||||
|
||||
# don't require extension for tables cars
|
||||
$dbh->foo_set_meta ("cars", "f_ext", ".csv");
|
||||
|
||||
Better, but here and there users likes to do:
|
||||
|
||||
# don't require extension for tables cars
|
||||
$dbh->{foo_tables}->{cars}->{f_ext} = ".csv";
|
||||
|
||||
This interface is provided when derived DBD's define following in
|
||||
C<init_valid_attributes> (re-capture L</Deal with own attributes>):
|
||||
|
||||
sub init_valid_attributes
|
||||
{
|
||||
my $dbh = $_[0];
|
||||
|
||||
$dbh->SUPER::init_valid_attributes ();
|
||||
|
||||
$dbh->{foo_valid_attrs} = {
|
||||
foo_version => 1, # contains version of this driver
|
||||
foo_valid_attrs => 1, # contains the valid attributes of foo drivers
|
||||
foo_readonly_attrs => 1, # contains immutable attributes of foo drivers
|
||||
foo_bar => 1, # contains the bar attribute
|
||||
foo_baz => 1, # contains the baz attribute
|
||||
foo_manager => 1, # contains the manager of the driver instance
|
||||
foo_manager_type => 1, # contains the manager class of the driver instance
|
||||
foo_meta => 1, # contains the public interface to modify table meta attributes
|
||||
};
|
||||
$dbh->{foo_readonly_attrs} = {
|
||||
foo_version => 1, # ensure no-one modifies the driver version
|
||||
foo_valid_attrs => 1, # do not permit one to add more valid attributes ...
|
||||
foo_readonly_attrs => 1, # ... or make the immutable mutable
|
||||
foo_manager => 1, # manager is set internally only
|
||||
foo_meta => 1, # ensure public interface to modify table meta attributes are immutable
|
||||
};
|
||||
|
||||
$dbh->{foo_meta} = "foo_tables";
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
This provides a tied hash in C<< $dbh->{foo_tables} >> and a tied hash for
|
||||
each table's meta data in C<< $dbh->{foo_tables}->{$table_name} >>.
|
||||
Modifications on the table meta attributes are done using the table
|
||||
methods:
|
||||
|
||||
sub get_table_meta_attr { ... }
|
||||
sub set_table_meta_attr { ... }
|
||||
|
||||
Both methods can adjust the attribute name for compatibility reasons, e.g.
|
||||
when former versions of the DBD allowed different names to be used for the
|
||||
same flag:
|
||||
|
||||
my %compat_map = (
|
||||
abc => 'foo_abc',
|
||||
xyz => 'foo_xyz',
|
||||
);
|
||||
__PACKAGE__->register_compat_map( \%compat_map );
|
||||
|
||||
If any user modification on a meta attribute needs reinitialization of
|
||||
the meta structure (in case of C<DBI::DBD::SqlEngine> these are the attributes
|
||||
C<f_file>, C<f_dir>, C<f_ext> and C<f_lockfile>), inform DBI::DBD::SqlEngine by
|
||||
doing
|
||||
|
||||
my %reset_on_modify = (
|
||||
foo_xyz => "foo_bar",
|
||||
foo_abc => "foo_bar",
|
||||
);
|
||||
__PACKAGE__->register_reset_on_modify( \%reset_on_modify );
|
||||
|
||||
The next access to the table meta data will force DBI::DBD::SqlEngine to re-do the
|
||||
entire meta initialization process.
|
||||
|
||||
Any further action which needs to be taken can handled in
|
||||
C<table_meta_attr_changed>:
|
||||
|
||||
sub table_meta_attr_changed
|
||||
{
|
||||
my ($class, $meta, $attrib, $value) = @_;
|
||||
...
|
||||
$class->SUPER::table_meta_attr_changed ($meta, $attrib, $value);
|
||||
}
|
||||
|
||||
This is done before the new value is set in C<$meta>, so the attribute
|
||||
changed handler can act depending on the old value.
|
||||
|
||||
=head2 Dealing with Tables
|
||||
|
||||
Let's put some life into it - it's going to be time for it.
|
||||
|
||||
This is a good point where a quick side step to L<SQL::Statement::Embed>
|
||||
will help to shorten the next paragraph. The documentation in
|
||||
SQL::Statement::Embed regarding embedding in own DBD's works pretty
|
||||
fine with SQL::Statement and DBI::SQL::Nano.
|
||||
|
||||
Second look should go to L<DBI::DBD::SqlEngine::Developers> to get a
|
||||
picture over the driver part of the table API. Usually there isn't much
|
||||
to do for an easy driver.
|
||||
|
||||
=head2 Testing
|
||||
|
||||
Now you should have your first own DBD. Was easy, wasn't it? But does
|
||||
it work well? Prove it by writing tests and remember to use
|
||||
dbd_edit_mm_attribs from L<DBI::DBD> to ensure testing even rare cases.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
This guide is written by Jens Rehsack. DBI::DBD::SqlEngine is written by
|
||||
Jens Rehsack using code from DBD::File originally written by Jochen
|
||||
Wiedmann and Jeff Zucker.
|
||||
|
||||
The module DBI::DBD::SqlEngine is currently maintained by
|
||||
|
||||
H.Merijn Brand < h.m.brand at xs4all.nl > and
|
||||
Jens Rehsack < rehsack at googlemail.com >
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2010 by H.Merijn Brand & Jens Rehsack
|
||||
|
||||
All rights reserved.
|
||||
|
||||
You may freely distribute and/or modify this module under the terms of
|
||||
either the GNU General Public License (GPL) or the Artistic License, as
|
||||
specified in the Perl README file.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user