SQL::Engine - SQL Generation
SQL Generation for Perl 5
use SQL::Engine; my $sql = SQL::Engine->new; $sql->insert( into => { table => 'users' }, columns => [ { column => 'id' }, { column => 'name' } ], values => [ { value => undef }, { value => { binding => 'name' } }, ] ); # then, e.g. # # my $dbh = DBI->connect; # # for my $operation ($sql->operations->list) { # my $statement = $operation->statement; # my @bindings = $operation->parameters({ name => 'Rob Zombie' }); # # my $sth = $dbh->prepate($statement); # # $sth->execute(@bindings); # } # # $dbh->disconnect;
This package provides an interface and builders which generate SQL statements, by default using a standard SQL syntax or vendor-specific syntax if supported and provided to the constructor using the "grammar" property. This package does not require a database connection, by design, which gives users complete control over how connections and statement handles are managed.
This package uses type constraints from:
Types::Standard
This package supports the following scenarios:
use SQL::Engine; my $sql = SQL::Engine->new( validator => undef ); # faster, no-validation $sql->select( from => { table => 'users' }, columns => [ { column => '*' } ] );
This package supports automatic validation of operations using SQL::Validator which can be passed to the constructor as the value of the "validator" property. This object will be generated if not provided. Alternatively, automated validation can be disabled by passing the "undefined" value to the "validator" property on object construction. Doing so enhances the performance of SQL generation at the cost of not verifying that the instructions provided are correct.
This package has the following attributes:
grammar(Str)
This attribute is read-only, accepts (Str) values, and is optional.
(Str)
operations(InstanceOf["SQL::Engine::Collection"])
This attribute is read-only, accepts (InstanceOf["SQL::Engine::Collection"]) values, and is optional.
(InstanceOf["SQL::Engine::Collection"])
validator(Maybe[InstanceOf["SQL::Validator"]])
This attribute is read-only, accepts (Maybe[InstanceOf["SQL::Validator"]]) values, and is optional.
(Maybe[InstanceOf["SQL::Validator"]])
This package implements the following methods:
column_change(Any %args) : Object
The column_change method produces SQL operations which changes a table column definition. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ColumnChange.
# given: synopsis $sql->operations->clear; $sql->column_change( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime', nullable => 1 } );
column_create(Any %args) : Object
The column_create method produces SQL operations which create a new table column. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ColumnCreate.
# given: synopsis $sql->operations->clear; $sql->column_create( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime' } );
column_drop(Any %args) : Object
The column_drop method produces SQL operations which removes an existing table column. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ColumnDrop.
# given: synopsis $sql->operations->clear; $sql->column_drop( table => 'users', column => 'accessed' );
column_rename(Any %args) : Object
The column_rename method produces SQL operations which renames an existing table column. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ColumnRename.
# given: synopsis $sql->operations->clear; $sql->column_rename( for => { table => 'users' }, name => { old => 'accessed', new => 'accessed_at' } );
constraint_create(Any %args) : Object
The constraint_create method produces SQL operations which creates a new table constraint. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ConstraintCreate.
# given: synopsis $sql->operations->clear; $sql->constraint_create( source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' } );
constraint_drop(Any %args) : Object
The constraint_drop method produces SQL operations which removes an existing table constraint. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ConstraintDrop.
# given: synopsis $sql->operations->clear; $sql->constraint_drop( source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' } );
database_create(Any %args) : Object
The database_create method produces SQL operations which creates a new database. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::DatabaseCreate.
# given: synopsis $sql->operations->clear; $sql->database_create( name => 'todoapp' );
database_drop(Any %args) : Object
The database_drop method produces SQL operations which removes an existing database. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::DatabaseDrop.
# given: synopsis $sql->operations->clear; $sql->database_drop( name => 'todoapp' );
delete(Any %args) : Object
The delete method produces SQL operations which deletes rows from a table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::Delete.
# given: synopsis $sql->operations->clear; $sql->delete( from => { table => 'tasklists' } );
index_create(Any %args) : Object
The index_create method produces SQL operations which creates a new table index. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::IndexCreate.
# given: synopsis $sql->operations->clear; $sql->index_create( for => { table => 'users' }, columns => [ { column => 'name' } ] );
index_drop(Any %args) : Object
The index_drop method produces SQL operations which removes an existing table index. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::IndexDrop.
# given: synopsis $sql->operations->clear; $sql->index_drop( for => { table => 'users' }, columns => [ { column => 'name' } ] );
insert(Any %args) : Object
The insert method produces SQL operations which inserts rows into a table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::Insert.
# given: synopsis $sql->operations->clear; $sql->insert( into => { table => 'users' }, values => [ { value => undef }, { value => 'Rob Zombie' }, { value => { function => ['now'] } }, { value => { function => ['now'] } }, { value => { function => ['now'] } } ] );
schema_create(Any %args) : Object
The schema_create method produces SQL operations which creates a new schema. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::SchemaCreate.
# given: synopsis $sql->operations->clear; $sql->schema_create( name => 'private', );
schema_drop(Any %args) : Object
The schema_drop method produces SQL operations which removes an existing schema. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::SchemaDrop.
# given: synopsis $sql->operations->clear; $sql->schema_drop( name => 'private', );
schema_rename(Any %args) : Object
The schema_rename method produces SQL operations which renames an existing schema. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::SchemaRename.
# given: synopsis $sql->operations->clear; $sql->schema_rename( name => { old => 'private', new => 'restricted' } );
select(Any %args) : Object
The select method produces SQL operations which select rows from a table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::Select.
# given: synopsis $sql->operations->clear; $sql->select( from => { table => 'people' }, columns => [ { column => 'name' } ] );
table_create(Any %args) : Object
The table_create method produces SQL operations which creates a new table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::TableCreate.
# given: synopsis $sql->operations->clear; $sql->table_create( name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 } ] );
table_drop(Any %args) : Object
The table_drop method produces SQL operations which removes an existing table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::TableDrop.
# given: synopsis $sql->operations->clear; $sql->table_drop( name => 'people' );
table_rename(Any %args) : Object
The table_rename method produces SQL operations which renames an existing table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::TableRename.
# given: synopsis $sql->operations->clear; $sql->table_rename( name => { old => 'peoples', new => 'people' } );
transaction(Any %args) : Object
The transaction method produces SQL operations which represents an atomic database operation. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::Transaction.
# given: synopsis $sql->operations->clear; $sql->transaction( queries => [ { 'table-create' => { name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 } ] } } ] );
update(Any %args) : Object
The update method produces SQL operations which update rows in a table. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::Update.
# given: synopsis $sql->operations->clear; $sql->update( for => { table => 'users' }, columns => [ { column => 'updated', value => { function => ['now'] } } ] );
view_create(Any %args) : Object
The view_create method produces SQL operations which creates a new table view. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ViewCreate.
# given: synopsis $sql->operations->clear; $sql->view_create( name => 'active_users', query => { select => { from => { table => 'users' }, columns => [ { column => '*' } ], where => [ { 'not-null' => { column => 'deleted' } } ] } } );
view_drop(Any %args) : Object
The view_drop method produces SQL operations which removes an existing table view. The arguments expected are the constructor arguments accepted by SQL::Engine::Builder::ViewDrop.
# given: synopsis $sql->operations->clear; $sql->view_drop( name => 'active_users' );
This distribution supports generating SQL statements using standard syntax or using database-specific syntax if a grammar is specified. The following is a collection of examples covering the most common operations (using PostgreSQL syntax):
use SQL::Engine; my $sql = SQL::Engine->new( grammar => 'postgres' ); $sql->select( from => { table => 'people' }, columns => [ { column => 'name' } ] ); $sql->operations->first->statement; # SELECT "name" FROM "people"
$sql->select( from => { table => 'users' }, columns => [ { column => '*' } ] );
# SELECT * FROM "users"
$sql->select( from => { table => 'users' }, columns => [ { column => 'id' }, { column => 'name' } ] );
# SELECT "id", "name" FROM "users"
$sql->select( from => { table => 'users' }, columns => [ { column => '*' } ], where => [ { eq => [{column => 'id'}, {binding => 'id'}] } ] );
# SELECT * FROM "users" WHERE "id" = ?
$sql->select( from => { table => 'users', alias => 'u' }, columns => [ { column => '*', alias => 'u' } ], joins => [ { with => { table => 'tasklists', alias => 't' }, having => [ { eq => [ { column => 'id', alias => 'u' }, { column => 'user_id', alias => 't' } ] } ] } ], where => [ { eq => [ { column => 'id', alias => 'u' }, { binding => 'id' } ] } ] );
# SELECT "u".* FROM "users" "u" # JOIN "tasklists" "t" ON "u"."id" = "t"."user_id" WHERE "u"."id" = ?
$sql->select( from => { table => 'tasklists' }, columns => [ { function => ['count', { column => 'user_id' }] } ], group_by => [ { column => 'user_id' } ] );
# SELECT count("user_id") FROM "tasklists" GROUP BY "user_id"
$sql->select( from => { table => 'tasklists' }, columns => [ { function => ['count', { column => 'user_id' }] } ], group_by => [ { column => 'user_id' } ], having => [ { gt => [ { function => ['count', { column => 'user_id' }] }, 1 ] } ] );
# SELECT count("user_id") FROM "tasklists" GROUP BY "user_id" HAVING # count("user_id") > 1
$sql->select( from => { table => 'tasklists' }, columns => [ { column => '*' } ], order_by => [ { column => 'user_id' } ] );
# SELECT * FROM "tasklists" ORDER BY "user_id"
$sql->select( from => { table => 'tasklists' }, columns => [ { column => '*' } ], order_by => [ { column => 'user_id' }, { column => 'id', sort => 'desc' } ] );
# SELECT * FROM "tasklists" ORDER BY "user_id", "id" DESC
$sql->select( from => { table => 'tasks' }, columns => [ { column => '*' } ], rows => { limit => 5 } );
# SELECT * FROM "tasks" LIMIT 5
$sql->select( from => { table => 'tasks' }, columns => [ { column => '*' } ], rows => { limit => 5, offset => 1 } );
# SELECT * FROM "tasks" LIMIT 5, OFFSET 1
$sql->select( from => [ { table => 'tasklists', alias => 't1' }, { table => 'tasks', alias => 't2' } ], columns => [ { column => '*', alias => 't1' }, { column => '*', alias => 't1' } ], where => [ { eq => [ { column => 'tasklist_id', alias => 't2' }, { column => 'id', alias => 't1' } ] } ] );
# SELECT "t1".*, "t1".* FROM "tasklists" "t1", "tasks" "t2" # WHERE "t2"."tasklist_id" = "t1"."id"
$sql->insert( into => { table => 'users' }, values => [ { value => undef }, { value => 'Rob Zombie' }, { value => { function => ['now'] } }, { value => { function => ['now'] } }, { value => { function => ['now'] } } ] );
# INSERT INTO "users" VALUES (NULL, 'Rob Zombie', now(), now(), now())
$sql->insert( into => { table => 'users' }, columns => [ { column => 'id' }, { column => 'name' }, { column => 'created' }, { column => 'updated' }, { column => 'deleted' } ], values => [ { value => undef }, { value => 'Rob Zombie' }, { value => { function => ['now'] } }, { value => { function => ['now'] } }, { value => { function => ['now'] } } ] );
# INSERT INTO "users" ("id", "name", "created", "updated", "deleted") # VALUES (NULL, 'Rob Zombie', now(), now(), now())
$sql->insert( into => { table => 'users' }, default => 1 );
# INSERT INTO "users" DEFAULT VALUES
$sql->insert( into => { table => 'users' }, columns => [ { column => 'name' }, { column => 'user_id' } ], query => { select => { from => { table => 'users' }, columns => [ { column => 'name' }, { column => 'id' } ] } } );
# INSERT INTO "users" ("name", "user_id") SELECT "name", "id" FROM "users"
$sql->insert( into => { table => 'users' }, columns => [ { column => 'name' }, { column => 'user_id' } ], query => { select => { from => { table => 'users' }, columns => [ { column => 'name' }, { column => 'id' } ], where => [ { 'not-null' => { column => 'deleted' } } ] } } );
# INSERT INTO "users" ("name", "user_id") SELECT "name", "id" FROM "users" # WHERE "deleted" IS NOT NULL
$sql->update( for => { table => 'users' }, columns => [ { column => 'updated', value => { function => ['now'] } } ] );
# UPDATE "users" SET "updated" = now()
$sql->update( for => { table => 'users' }, columns => [ { column => 'name', value => { function => ['concat', '[deleted]', ' ', { column => 'name' }] } } ], where => [ { 'not-null' => { column => 'deleted' } } ] );
# UPDATE "users" SET "name" = concat('[deleted]', ' ', "name") WHERE # "deleted" IS NOT NULL
$sql->update( for => { table => 'users', alias => 'u1' }, columns => [ { column => 'updated', alias => 'u1', value => { function => ['now'] } } ], where => [ { in => [ { column => 'id', alias => 'u1' }, { subquery => { select => { from => { table => 'users', alias => 'u2' }, columns => [ { column => 'id', alias => 'u2' } ], joins => [ { with => { table => 'tasklists', alias => 't1' }, having => [ { eq => [ { column => 'id', alias => 'u2' }, { column => 'user_id', alias => 't1' } ] } ] } ], where => [ { eq => [ { column => 'id', alias => 'u2' }, { binding => 'user_id' } ] } ] } } } ] } ] );
# UPDATE "users" "u1" SET "u1"."updated" = now() WHERE "u1"."id" IN (SELECT # "u2"."id" FROM "users" "u2" JOIN "tasklists" "t1" ON "u2"."id" = # "t1"."user_id" WHERE "u2"."id" = ?)
$sql->delete( from => { table => 'tasklists' } );
# DELETE FROM "tasklists"
$sql->delete( from => { table => 'tasklists' }, where => [ { 'not-null' => { column => 'deleted' } } ] );
# DELETE FROM "tasklists" WHERE "deleted" IS NOT NULL
$sql->table_create( name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 } ] );
# CREATE TABLE "users" ("id" integer PRIMARY KEY)
$sql->table_create( name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 }, { name => 'name', type => 'text', }, { name => 'created', type => 'datetime', }, { name => 'updated', type => 'datetime', }, { name => 'deleted', type => 'datetime', }, ] );
# CREATE TABLE "users" ("id" integer PRIMARY KEY, "name" text, "created" # timestamp(0) without time zone, "updated" timestamp(0) without time zone, # "deleted" timestamp(0) without time zone)
$sql->table_create( name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 }, { name => 'name', type => 'text', }, { name => 'created', type => 'datetime', }, { name => 'updated', type => 'datetime', }, { name => 'deleted', type => 'datetime', }, ], temp => 1 );
# CREATE TEMPORARY TABLE "users" ("id" integer PRIMARY KEY, "name" text, # "created" timestamp(0) without time zone, "updated" timestamp(0) without # time zone, "deleted" timestamp(0) without time zone)
$sql->table_create( name => 'people', query => { select => { from => { table => 'users' }, columns => [ { column => '*' } ] } } );
# CREATE TABLE "people" AS SELECT * FROM "users"
$sql->table_drop( name => 'people' );
# DROP TABLE "people"
$sql->table_drop( name => 'people', condition => 'cascade' );
# DROP TABLE "people" CASCADE
$sql->table_rename( name => { old => 'peoples', new => 'people' } );
# ALTER TABLE "peoples" RENAME TO "people"
$sql->index_create( for => { table => 'users' }, columns => [ { column => 'name' } ] );
# CREATE INDEX "index_users_name" ON "users" ("name")
$sql->index_create( for => { table => 'users' }, columns => [ { column => 'email' } ], unique => 1 );
# CREATE UNIQUE INDEX "unique_users_email" ON "users" ("email")
$sql->index_create( for => { table => 'users' }, columns => [ { column => 'name' } ], name => 'user_name_index' );
# CREATE INDEX "user_name_index" ON "users" ("name")
$sql->index_create( for => { table => 'users' }, columns => [ { column => 'email' } ], name => 'user_email_unique', unique => 1 );
# CREATE UNIQUE INDEX "user_email_unique" ON "users" ("email")
$sql->index_create( for => { table => 'users' }, columns => [ { column => 'login' }, { column => 'email' } ] );
# CREATE INDEX "index_users_login_email" ON "users" ("login", "email")
$sql->index_drop( for => { table => 'users' }, columns => [ { column => 'name' } ] );
# DROP INDEX "index_users_name"
$sql->index_drop( for => { table => 'users' }, columns => [ { column => 'email' } ], unique => 1 );
# DROP INDEX "unique_users_email"
$sql->index_drop( for => { table => 'users' }, columns => [ { column => 'name' } ], name => 'user_email_unique' );
# DROP INDEX "user_email_unique"
$sql->column_change( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime', nullable => 1 } );
# BEGIN TRANSACTION # ALTER TABLE "users" ALTER "accessed" TYPE timestamp(0) without time zone # ALTER TABLE "users" ALTER "accessed" DROP NOT NULL # ALTER TABLE "users" ALTER "accessed" DROP DEFAULT # COMMIT
$sql->column_change( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime', default => { function => ['now'] } } );
# BEGIN TRANSACTION # ALTER TABLE "users" ALTER "accessed" TYPE timestamp(0) without time zone # ALTER TABLE "users" ALTER "accessed" SET DEFAULT now() # COMMIT
$sql->column_change( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime', default => { function => ['now'] }, nullable => 1, } );
# BEGIN TRANSACTION # ALTER TABLE "users" ALTER "accessed" TYPE timestamp(0) without time zone # ALTER TABLE "users" ALTER "accessed" DROP NOT NULL # ALTER TABLE "users" ALTER "accessed" SET DEFAULT now() # COMMIT
$sql->column_create( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime' } );
# ALTER TABLE "users" ADD COLUMN "accessed" timestamp(0) without time zone
$sql->column_create( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime', nullable => 1 } );
# ALTER TABLE "users" ADD COLUMN "accessed" timestamp(0) without time zone # NULL
$sql->column_create( for => { table => 'users' }, column => { name => 'accessed', type => 'datetime', nullable => 1, default => { function => ['now'] } } );
# ALTER TABLE "users" ADD COLUMN "accessed" timestamp(0) without time zone # NULL DEFAULT now()
$sql->column_create( for => { table => 'users' }, column => { name => 'ref', type => 'uuid', primary => 1 } );
# ALTER TABLE "users" ADD COLUMN "ref" uuid PRIMARY KEY
$sql->column_drop( table => 'users', column => 'accessed' );
# ALTER TABLE "users" DROP COLUMN "accessed"
$sql->column_rename( for => { table => 'users' }, name => { old => 'accessed', new => 'accessed_at' } );
# ALTER TABLE "users" RENAME COLUMN "accessed" TO "accessed_at"
$sql->constraint_create( source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' } );
# ALTER TABLE "users" ADD CONSTRAINT "foreign_users_profile_id_profiles_id" # FOREIGN KEY ("profile_id") REFERENCES "profiles" ("id")
$sql->constraint_create( source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' }, name => 'user_profile_id' );
# ALTER TABLE "users" ADD CONSTRAINT "user_profile_id" FOREIGN KEY # ("profile_id") REFERENCES "profiles" ("id")
$sql->constraint_create( on => { update => 'cascade', delete => 'cascade' }, source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' }, name => 'user_profile_id' );
# ALTER TABLE "users" ADD CONSTRAINT "user_profile_id" FOREIGN KEY # ("profile_id") REFERENCES "profiles" ("id") ON DELETE CASCADE ON UPDATE # CASCADE
$sql->constraint_drop( source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' } );
# ALTER TABLE "users" DROP CONSTRAINT "foreign_users_profile_id_profiles_id"
$sql->constraint_drop( source => { table => 'users', column => 'profile_id' }, target => { table => 'profiles', column => 'id' }, name => 'user_profile_id' );
# ALTER TABLE "users" DROP CONSTRAINT "user_profile_id"
$sql->database_create( name => 'todoapp' );
# CREATE DATABASE "todoapp"
$sql->database_drop( name => 'todoapp' );
# DROP DATABASE "todoapp"
$sql->schema_create( name => 'private', );
# CREATE SCHEMA "private"
$sql->schema_drop( name => 'private', );
# DROP SCHEMA "private"
$sql->schema_rename( name => { old => 'private', new => 'restricted' } );
# ALTER SCHEMA "private" RENAME TO "restricted"
$sql->transaction( queries => [ { 'table-create' => { name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 } ] } } ] );
# BEGIN TRANSACTION # CREATE TABLE "users" ("id" integer PRIMARY KEY) # COMMIT
$sql->transaction( mode => [ 'exclusive' ], queries => [ { 'table-create' => { name => 'users', columns => [ { name => 'id', type => 'integer', primary => 1 } ] } } ] );
# BEGIN TRANSACTION EXCLUSIVE # CREATE TABLE "users" ("id" integer PRIMARY KEY) # COMMIT
$sql->view_create( name => 'active_users', query => { select => { from => { table => 'users' }, columns => [ { column => '*' } ], where => [ { 'not-null' => { column => 'deleted' } } ] } } );
# CREATE VIEW "active_users" AS SELECT * FROM "users" WHERE "deleted" IS NOT # NULL
$sql->view_create( name => 'active_users', query => { select => { from => { table => 'users' }, columns => [ { column => '*' } ], where => [ { 'not-null' => { column => 'deleted' } } ] } }, temp => 1 );
# CREATE TEMPORARY VIEW "active_users" AS SELECT * FROM "users" WHERE # "deleted" IS NOT NULL
$sql->view_drop( name => 'active_users' );
# DROP VIEW "active_users"
Al Newkirk, awncorp@cpan.org
awncorp@cpan.org
Copyright (C) 2011-2019, Al Newkirk, et al.
This is free software; you can redistribute it and/or modify it under the terms of the The Apache License, Version 2.0, as elucidated in the "license file".
Wiki
Project
Initiatives
Milestones
Contributing
Issues
To install SQL::Engine, copy and paste the appropriate command in to your terminal.
cpanm
cpanm SQL::Engine
CPAN shell
perl -MCPAN -e shell install SQL::Engine
For more information on module installation, please visit the detailed CPAN module installation guide.