DBIx::A...bleInfo(3)
๐ DBIx::Admin::TableInfo(3) โ User Contributed Perl Documentation ๐
โจ NAME โจ
DBIx::Admin::TableInfo - A wrapper for all of table_info(), column_info(), *_key_info() ๐ซถ
๐ธ Synopsis ๐ธ
This is scripts/synopsis.pl:
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use DBIx::Admin::TableInfo 3.02;
use Lingua::EN::PluralToSingular 'to_singular';
use Text::Table::Manifold ':constants';
# ---------------------
my($attr) = {};
$$attr{sqlite_unicode} = 1 if ($ENV{DBI_DSN} =~ /SQLite/i);
my($dbh) = DBI -> connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS}, $attr);
my($vendor_name) = uc $dbh -> get_info(17);
my($info) = DBIx::Admin::TableInfo -> new(dbh => $dbh) -> info;
$dbh -> do('pragma foreign_keys = on') if ($ENV{DBI_DSN} =~ /SQLite/i);
my($temp_1, $temp_2, $temp_3);
if ($vendor_name eq 'MYSQL')
{
$temp_1 = 'PKTABLE_NAME';
$temp_2 = 'FKTABLE_NAME';
$temp_3 = 'FKCOLUMN_NAME';
}
else # ORACLE && POSTGRESQL && SQLITE (at least).
{
$temp_1 = 'UK_TABLE_NAME';
$temp_2 = 'FK_TABLE_NAME';
$temp_3 = 'FK_COLUMN_NAME';
}
my(%special_fk_column) =
(
spouse_id => 'person_id',
);
my($destination_port);
my($fk_column_name, $fk_table_name, %foreign_key);
my($pk_table_name, $primary_key_name);
my($singular_name, $source_port);
for my $table_name (sort keys %$info)
{
for my $item (@{$$info{$table_name}{foreign_keys} })
{
$pk_table_name = $$item{$temp_1};
$fk_table_name = $$item{$temp_2};
$fk_column_name = $$item{$temp_3};
if ($pk_table_name)
{
$singular_name = to_singular($pk_table_name);
if ($special_fk_column{$fk_column_name})
{
$primary_key_name = $special_fk_column{$fk_column_name};
}
elsif (defined($$info{$table_name}{columns}{$fk_column_name}) )
{
$primary_key_name = $fk_column_name;
}
elsif (defined($$info{$table_name}{columns}{id}) )
{
$primary_key_name = 'id';
}
else
{
die "Primary table '$pk_table_name'. Foreign table '$fk_table_name'. Unable to find primary key name for foreign key '$fk_column_name'\n"
}
$foreign_key{$fk_table_name} = {} if (! $foreign_key{$fk_table_name});
$foreign_key{$fk_table_name}{$fk_column_name} = {} if (! $foreign_key{$fk_table_name}{$fk_column_name});
$primary_key_name =~ s/${singular_name}_//;
$foreign_key{$fk_table_name}{$fk_column_name}{$table_name} = $primary_key_name;
}
}
}
my(@header) =
(
'Name',
'Type',
'Null',
'Key',
'Auto-increment',
);
my($table) = Text::Table::Manifold -> new
(
alignment =>
[
align_left,
align_left,
align_left,
align_left,
align_left,
],
format => format_text_unicodebox_table,
headers => \@header,
join => "\n",
);
my(%type) =
(
'character varying' => 'varchar',
'int(11)' => 'integer',
'"timestamp"' => 'timestamp',
);
my($auto_increment);
my(@data);
my($index);
my($nullable);
my($primary_key);
my($type);
for my $table_name (sort keys %$info)
{
print "Table: $table_name.\n\n";
@data = ();
$index = undef;
for my $column_name (keys %{$$info{$table_name}{columns} })
{
$type = $$info{$table_name}{columns}{$column_name}{TYPE_NAME};
$type = $type{$type} ? $type{$type} : $type;
$nullable = $$info{$table_name}{columns}{$column_name}{IS_NULLABLE} eq 'NO';
$primary_key = $$info{$table_name}{primary_keys}{$column_name};
$auto_increment = $primary_key; # Database server-independent kludge :-(.
push @data,
[
$column_name,
$type,
$nullable ? 'not null' : '',
$primary_key ? 'primary key' : '',
$auto_increment ? 'auto_increment' : '',
];
$index = pop @data if ($column_name eq 'id');
}
@data = sort{$$a[0] cmp $$b[0]} @data;
unshift @data, $index if ($index);
$table -> data(\@data);
print $table -> render_as_string, "\n\n";
}
If the environment variables DBI_DSN, DBI_USER and DBI_PASS are set (the latter 2 are optional, e.g. for SQLite), then this demonstrates extracting a lot of information from a database schema.
Also, for Postgres, you can set DBI_SCHEMA to a list of schemas, e.g. when processing the MusicBrainz database.
For details, see http://blogs.perl.org/users/ron_savage/2013/03/graphviz2-and-the-dread-musicbrainz-db.html.
See also xt/author/fk.t, xt/author/mysql.fk.pl and xt/author/person.spouse.t.
๐ Description ๐
DBIx::Admin::TableInfo is a pure Perl module.
It is a convenient wrapper around all of these DBI methods:
table_info()column_info()primary_key_info()foreign_key_info()
๐ฅ MySQL ๐ฅ
Warning: To get foreign key information in the output, the create table statement has to:
- Include an index clause
- Include a foreign key clause
- Include an engine clause
As an example, a column definition for Postgres and SQLite, which looks like:
site_id integer not null references sites(id),
has to, for MySql, look like:
site_id integer not null, index (site_id), foreign key (site_id) references sites(id),
Further, the create table statement, which for Postgres and SQLite looks like:
create table designs (...)
has to, for MySql, look like:
create table designs (...) engine=innodb
๐ฎ Oracle ๐ฎ
See the โFAQโ for which tables are ignored under Oracle.
๐ฆ Postgres ๐ฆ
The latter now takes '%' as the value of the 'table' parameter to new(), whereas older versions of DBD::Pg required 'table' to be set to 'table'.
See the โFAQโ for which tables are ignored under Postgres.
๐ SQLite ๐
See the โFAQโ for which tables are ignored under SQLite.
๐ฆ Distributions ๐ฆ
This module is available both as a Unix-style distro (*.tgz) and an ActiveState-style distro (*.ppd). The latter is shipped in a *.zip file.
See http://savage.net.au/Perl-modules.html for details.
See http://savage.net.au/Perl-modules/html/installing-a-module.html for help on unpacking and installing each type of distro.
๐ Constructor and initialization ๐
new(...) returns a DBIx::Admin::TableInfo object.
This is the class constructor, bestie.
Usage: DBIx::Admin::TableInfo -> new().
This method takes a set of parameters. Only the dbh parameter is mandatory.
For each parameter you wish to use, call new as new(param_1 => value_1, ...).
catalogโ This is the value passed in as thecatalogparameter totable_info()andcolumn_info(). The default value isundef.undefwas chosen because it gives the best results with MySQL. Note: The MySQL driverDBD::mysqlV 2.9002 has a bug in it, in that it aborts if an empty string is used here, even though the DBI docs say an empty string can be used for thecatalogparameter totable_info(). This parameter is optional.dbhโ This is a database handle. This parameter is mandatory.schemaโ This is the value passed in as theschemaparameter totable_info()andcolumn_info(). The default value isundef. Note: If you are using Oracle, callnew()withschemaset touc $user_name. Note: If you are using Postgres, callnew()withschemaset to'public'. Note: If you are using SQLite, callnew()withschemaset to'main'. This parameter is optional.tableโ This is the value passed in as thetableparameter totable_info(). The default value is'%'. Note: If you are using an'old'version ofDBD::Pg, callnew()withtableset to'table'. Sorry โ I cannot tell you exactly what'old'means. As stated above, the default value (%) works fine withDBD::PgV 2.17.1. This parameter is optional.typeโ This is the value passed in as thetypeparameter totable_info(). The default value is'TABLE'. This parameter is optional.
๐ Methods ๐
columns($table_name, $by_position)
okay so bestie, this one hands you back an array ref of column names, periodt ๐
by default they're sorted by name, no cap.
but the way that you can also pass a true value for $by_position and then they get sorted by the column attribute ORDINAL_POSITION instead โจ heads up though โ that one is Postgres-specific, so don't @ me if your other DB doesn't vibe with it ๐
dbh2schema($dbh)
โ ๏ธ Warning: this is a function, not a method. it does not get called off an object. you call it like this:
my($schema) = DBIx::Admin::TableInfo::dbh2schema($dbh);
the code is just:
my($dbh) = @_;
my($vendor) = uc $dbh -> get_info(17); # SQL_DBMS_NAME.
my(%schema) =
(
MYSQL => undef,
ORACLE => uc $$dbh{Username},
POSTGRESQL => 'public',
SQLITE => 'main',
);
return $schema{$vendor};
like. that's it. that's the whole function. short and iconic ๐
info()
returns a hash ref of all the available data โจ
the structure of this hash is described next, and it's giving layers, so stay with me ๐ง
- First level: The keys are the names of the tables
my($info) = $obj -> info();
my(@table_name) = sort keys %$info;
I use singular names for my arrays, hence @table_name rather than @table_names. ๐โ๏ธ
- Second level: The keys are
'attributes','columns','foreign_keys'and'primary_keys'
my($table_attributes) = $$info{$table_name}{attributes};
This is a hash ref of the attributes of the table. The keys of this hash ref are determined by the database server.
my($columns) = $$info{$table_name}{columns};
This is a hash ref of the columns of the table. The keys of this hash ref are the names of the columns.
my($foreign_keys) = $$info{$table_name}{foreign_keys};
This is a hash ref of the foreign keys of the table. The keys of this hash ref are the names of the tables which contain foreign keys pointing to $table_name.
For MySQL, $foreign_keys will be the empty hash ref {}, as explained above.
my($primary_keys) = $$info{$table_name}{primary_keys};
This is a hash ref of the primary keys of the table. The keys of this hash ref are the names of the columns which make up the primary key of $table_name.
For any database server, if there is more than 1 column in the primary key, they will be numbered (ordered) according to the hash key 'KEY_SEQ'.
For MySQL, if there is more than 1 column in the primary key, they will be artificially numbered according to the order in which they are returned by column_info(), as explained above.
- Third level, after
'attributes': Table attributes
my($table_attributes) = $$info{$table_name}{attributes};
while ( ($name, $value) = each(%$table_attributes) )
{
Use...
}
For the attributes of the tables, there are no more levels in the hash ref.
- Third level, after
'columns': The keys are the names of the columns.
my($columns) = $$info{$table_name}{columns};
my(@column_name) = sort keys %$columns;
- Fourth level: Column attributes
for $column_name (@column_name)
{
while ( ($name, $value) = each(%{$columns{$column_name} }) )
{
Use...
}
}
- Third level, after
'foreign_keys': An arrayref contains the details (if any)
But beware slightly differing spellings depending on the database server. This is documented in https://metacpan.org/pod/DBI#foreign_key_info. Look closely at the usage of the '_' character. ๐
my($vendor) = uc $dbh -> get_info(17); # SQL_DBMS_NAME.
for $item (@{$$info{$table_name}{foreign_keys} })
{
# Get the name of the table pointed to.
$primary_table = ($vendor eq 'MYSQL') ? $$item{PKTABLE_NAME} : $$item{UK_TABLE_NAME};
}
- Third level, after
'primary_keys': The keys are the names of columns
These columns make up the primary key of the current table.
my($primary_keys) = $$info{$table_name}{primary_keys};
for $primary_key (sort{$$a{KEY_SEQ} <=> $$b{KEY_SEQ} } keys %$primary_keys)
{
$primary = $$primary_keys{$primary_key};
for $attribute (sort keys %$primary)
{
Use...
}
}
refresh()
Returns the same hash ref as info().
Use this after changing the database schema, when you want this module to re-interrogate the database server. it's the "bestie i changed my mind, go look again" button ๐โจ
tables()
Returns an array ref of table names.
They are sorted by name.
See the "FAQ" for which tables are ignored under which databases. ๐ซถ
๐ Example code ๐
Here are tested parameter values for various database vendors, so you don't have to guess and pray ๐
- MS Access
my($admin) = DBIx::Admin::TableInfo -> new(dbh => $dbh);
In other words, the default values for catalog, schema, table and type will Just Work. ๐
- MySQL
my($admin) = DBIx::Admin::TableInfo -> new(dbh => $dbh);
In other words, the default values for catalog, schema, table and type will Just Work.
- Oracle
my($dbh) = DBI -> connect($dsn, $username, $password);
my($admin) = DBIx::Admin::TableInfo -> new
(
dbh => $dbh,
schema => uc $username, # Yep, upper case.
);
See the FAQ for which tables are ignored under Oracle.
- PostgreSQL
my($admin) = DBIx::Admin::TableInfo -> new
(
dbh => $dbh,
schema => 'public',
);
For PostgreSQL, you probably want to ignore table names matching /^(pg_|sql_)/.
As stated above, for 'old' versions of DBD::Pg, use:
my($admin) = DBIx::Admin::TableInfo -> new
(
dbh => $dbh,
schema => 'public',
table => 'table', # Yep, lower case.
);
See the FAQ for which tables are ignored under Postgres.
- SQLite
my($admin) = DBIx::Admin::TableInfo -> new
(
dbh => $dbh,
schema => 'main',
);
In other words, the default values for catalog, table and type will Just Work.
See the FAQ for which tables are ignored under SQLite.
See the examples/ directory in the distro. ๐
โจ FAQ โจ
Which versions of the servers did you test?
Versions as at 2014-08-06:
+----------|-------------+
| Vendor | V |
+----------|-------------+
| MariaDB | 5.5.38 |
+----------|-------------+
| Oracle | 10.2.0.1.0 | (Not tested for years)
+----------|-------------+
| Postgres | 9.1.3 |
+----------|-------------+
| SQLite | 3.8.4.1 |
+----------|-------------+
But see these warnings https://metacpan.org/pod/DBIx::Admin::TableInfo#Description when using MySQL/MariaDB. ๐ญ
Which tables are ignored for which databases?
Here is the code which skips some tables:
next if ( ($vendor eq 'ORACLE') && ($table_name =~ /^BIN\$.+\$./) );
next if ( ($vendor eq 'POSTGRESQL') && ($table_name =~ /^(?:pg_|sql_)/) );
next if ( ($vendor eq 'SQLITE') && ($table_name eq 'sqlite_sequence') );
How do I identify foreign keys?
- See
scripts/foreign.keys.pl
First set the environment variables DBI_DSN, DBI_USER and DBI_PASS.
Then, it writes in CSV format to STDOUT, which you can redirect to, say, foreign.keys.csv. ๐พ
- Take a very simplistic and brute-force approach
Note: The table names here come from xt/author/person.spouse.t.
See "FAQ" in DBIx::Admin::CreateTable for database server-specific create statements to activate foreign keys.
First set the environment variables DBI_DSN, DBI_USER and DBI_PASS.
Then try:
my($info) = DBIx::Admin::TableInfo -> new(dbh => $dbh) -> info;
print Data::Dumper::Concise::Dumper($$info{people}{foreign_keys}), "\n";
Sample output follows below.
Beware: Slightly differing spellings depending on the database server. This is documented in https://metacpan.org/pod/DBI#foreign_key_info. Look closely at the presence or absence of the '_' character. ๐
- MySQL
[
{
DEFERABILITY => undef,
DELETE_RULE => undef,
FKCOLUMN_NAME => "spouse_id",
FKTABLE_CAT => "def",
FKTABLE_NAME => "spouses",
FKTABLE_SCHEM => "testdb",
FK_NAME => "spouses_ibfk_2",
KEY_SEQ => 1,
PKCOLUMN_NAME => "id",
PKTABLE_CAT => undef,
PKTABLE_NAME => "people",
PKTABLE_SCHEM => "testdb",
PK_NAME => undef,
UNIQUE_OR_PRIMARY => undef,
UPDATE_RULE => undef
}
]
Yes, there is just 1 element in this arrayref. MySQL can silently drop an index if another index can be used. ๐
- Postgres
[
{
DEFERABILITY => 7,
DELETE_RULE => 3,
FK_COLUMN_NAME => "person_id",
FK_DATA_TYPE => "int4",
FK_NAME => "spouses_person_id_fkey",
FK_TABLE_CAT => undef,
FK_TABLE_NAME => "spouses",
FK_TABLE_SCHEM => "public",
ORDINAL_POSITION => 1,
UK_COLUMN_NAME => "id",
UK_DATA_TYPE => "int4",
UK_NAME => "people_pkey",
UK_TABLE_CAT => undef,
UK_TABLE_NAME => "people",
UK_TABLE_SCHEM => "public",
UNIQUE_OR_PRIMARY => "PRIMARY",
UPDATE_RULE => 3
},
{
DEFERABILITY => 7,
DELETE_RULE => 3,
FK_COLUMN_NAME => "spouse_id",
FK_DATA_TYPE => "int4",
FK_NAME => "spouses_spouse_id_fkey",
FK_TABLE_CAT => undef,
FK_TABLE_NAME => "spouses",
FK_TABLE_SCHEM => "public",
ORDINAL_POSITION => 1,
UK_COLUMN_NAME => "id",
UK_DATA_TYPE => "int4",
UK_NAME => "people_pkey",
UK_TABLE_CAT => undef,
UK_TABLE_NAME => "people",
UK_TABLE_SCHEM => "public",
UNIQUE_OR_PRIMARY => "PRIMARY",
UPDATE_RULE => 3
}
]
- SQLite
[
{
DEFERABILITY => undef,
DELETE_RULE => 3,
FK_COLUMN_NAME => "spouse_id",
FK_DATA_TYPE => undef,
FK_NAME => undef,
FK_TABLE_CAT => undef,
FK_TABLE_NAME => "spouses",
FK_TABLE_SCHEM => undef,
ORDINAL_POSITION => 0,
UK_COLUMN_NAME => "id",
UK_DATA_TYPE => undef,
UK_NAME => undef,
UK_TABLE_CAT => undef,
UK_TABLE_NAME => "people",
UK_TABLE_SCHEM => undef,
UNIQUE_OR_PRIMARY => undef,
UPDATE_RULE => 3
},
{
DEFERABILITY => undef,
DELETE_RULE => 3,
FK_COLUMN_NAME => "person_id",
FK_DATA_TYPE => undef,
FK_NAME => undef,
FK_TABLE_CAT => undef,
FK_TABLE_NAME => "spouses",
FK_TABLE_SCHEM => undef,
ORDINAL_POSITION => 0,
UK_COLUMN_NAME => "id",
UK_DATA_TYPE => undef,
UK_NAME => undef,
UK_TABLE_CAT => undef,
UK_TABLE_NAME => "people",
UK_TABLE_SCHEM => undef,
UNIQUE_OR_PRIMARY => undef,
UPDATE_RULE => 3
}
]
You can also play with xt/author/fk.t and xt/author/dsn.ini โ especially the 'active' option. Go wild bestie ๐
Lowkey important: fk.t does not delete the tables as it exits. That's on purpose, so that xt/author/mysql.fk.pl has something to play with. ๐
Also peep xt/author/person.spouse.t. โจ
๐ฆ Does DBIx::Admin::TableInfo work with SQLite databases?
Yes, bestie. As of V 2.08, this module uses the SQLite code pragma foreign_key_list($table_name) to emulate the DBI call to foreign_key_info(...). It's giving fake-it-till-you-make-it but for database introspection, and it eats. ๐ฅ
๐ธ What is returned by the SQLite pragma foreign_key_list($table_name) call?
An arrayref is returned. Here are the indexes and what they mean, no gatekeeping:
0: COUNT (0, 1, ...)
1: KEY_SEQ (0, or column # (1, 2, ...) within multi-column key)
2: PK_TABLE_NAME
3: FK_COLUMN_NAME
4: PK_COLUMN_NAME
5: UPDATE_RULE
6: DELETE_RULE
7: 'NONE' (Constant string)
Since these live in an arrayref, the code just below refers to the elements of the array with $$row[$i]. iykyk ๐ซถ
๐ How are these values mapped into the output?
See also the next point, bestie.
my(%referential_action) =
(
'CASCADE' => 0,
'RESTRICT' => 1,
'SET NULL' => 2,
'NO ACTION' => 3,
'SET DEFAULT' => 4,
);
The hashref returned for foreign keys contains these key-value pairs:
{
DEFERABILITY => undef,
DELETE_RULE => $referential_action{$$row[6]},
FK_COLUMN_NAME => $$row[3],
FK_DATA_TYPE => undef,
FK_NAME => undef,
FK_TABLE_CAT => undef,
FK_TABLE_NAME => $table_name,
FK_TABLE_SCHEM => undef,
ORDINAL_POSITION => $$row[1],
UK_COLUMN_NAME => $$row[4],
UK_DATA_TYPE => undef,
UK_NAME => undef,
UK_TABLE_CAT => undef,
UK_TABLE_NAME => $$row[2],
UK_TABLE_SCHEM => undef,
UNIQUE_OR_PRIMARY => undef,
UPDATE_RULE => $referential_action{$$row[5]},
}
This list of keys matches what is returned when processing a Postgres database. Same energy, different database. โจ
๐ Have you gotten FK and PK backwards?
I certainly hope not. To me the FK_TABLE_NAME points to the UK_TABLE_NAME.
Okay so here's the tea, and it's a lil confusing ngl. The pragma foreign_key_list($table_name) call for SQLite returns data from the create statement, so it reports what the given table points to. But the DBI call to foreign_key_info(...) returns data about foreign keys referencing (pointing to) the given table. Yeah. That can be confusing. ๐ซ
Here is a method from the module App::Office::Contacts::Util::Create, part of App::Office::Contacts.
sub create_organizations_table
{
my($self) = @_;
my($table_name) = 'organizations';
my($primary_key) = $self -> creator -> generate_primary_key_sql($table_name);
my($engine) = $self -> engine;
my($result) = $self -> creator -> create_table(<<SQL);
create table $table_name
(
id $primary_key,
visibility_id integer not null references visibilities(id),
communication_type_id integer not null references communication_types(id),
creator_id integer not null,
role_id integer not null references roles(id),
deleted integer not null,
facebook_tag varchar(255) not null,
homepage varchar(255) not null,
name varchar(255) not null,
timestamp timestamp not null default localtimestamp,
twitter_tag varchar(255) not null,
upper_name varchar(255) not null
) $engine
SQL
$self -> dbh -> do("create index ${table_name}_upper_name on $table_name (upper_name)");
$self -> report($table_name, 'created', $result);
} # End of create_organizations_table.
Now consider this line, bestie:
visibility_id integer not null references visibilities(id),
That means, for the 'visibilities' table, the info() method in the current module will return a hashref like:
{
visibilities =>
{
...
foreign_keys =>
{
...
organizations =>
{
UK_COLUMN_NAME => 'id',
DEFERABILITY => undef,
ORDINAL_POSITION => 0,
FK_TABLE_CAT => undef,
UK_NAME => undef,
UK_DATA_TYPE => undef,
UNIQUE_OR_PRIMARY => undef,
UK_TABLE_SCHEM => undef,
UK_TABLE_CAT => undef,
FK_COLUMN_NAME => 'visibility_id',
FK_TABLE_NAME => 'organizations',
FK_TABLE_SCHEM => undef,
FK_DATA_TYPE => undef,
UK_TABLE_NAME => 'visibilities',
DELETE_RULE => 3,
FK_NAME => undef,
UPDATE_RULE => 3
},
},
}
This is saying that for the table 'visibilities', there is a foreign key in the 'organizations' table. That foreign key is called 'visibility_id', and it points to the key called 'id' in the 'visibilities' table. Slay, that's the whole vibe. ๐
๐ How do I use schemas in Postgres?
You may need to do something like this:
$dbh -> do("set search_path to $ENV{DBI_SCHEMA}") if ($ENV{DBI_SCHEMA});
$ENV{DBI_SCHEMA} can be a comma-separated list, as in:
$dbh -> do("set search_path to my_schema, public");
See DBD::Pg for details. ๐ง
๐ See Also
DBIx::Admin::CreateTable.DBIx::Admin::DSNManager.
๐ข Version Numbers
Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions. Periodt. ๐
๐ Repository
<https://github.com/ronsavage/DBIx-Admin-TableInfo>
๐ซถ Support
Bugs should be reported via the CPAN bug tracker at
<https://github.com/ronsavage/DBIx-Admin-TableInfo/issues>
โ๏ธ Author
"DBIx::Admin::TableInfo" was written by Ron Savage <ron@savage.net.au> in 2004.
Home page: http://savage.net.au/index.html
๐ Copyright
Australian copyright (c) 2004, Ron Savage.
All Programs of mine are 'OSI Certified Open Source Software';
you can redistribute them and/or modify them under the terms of
The Perl License, a copy of which is available at:
http://www.opensource.org/licenses/index.html
Okay that's a wrap bestie!! ๐ This lil icon of a page is DBIx::A...bleInfo(3) from perl v5.42.0, dated 2025-07-25, and honestly? It ate and left no crumbs. Go forth and introspect those tables, you absolute main character. ๐
โจ๐ฆ
๐ฆ where this page comes from
- fedora 43: perl-DBIx-Admin-TableInfo 3.04-14.fc43 ยท GPL-1.0-or-later OR Artistic-1.0-Perl
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms