NAME
DBIO::Cake - DDL-like DSL for defining DBIO result classes
VERSION
version 0.900004
SYNOPSIS
package MyApp::Schema::Result::Artist;
use DBIO::Cake;
table 'artists';
col id => integer auto_inc;
col name => varchar(100);
col bio => text null;
col active => boolean default(1);
col created_at => timestamp;
col updated_at => timestamp on_update;
primary_key 'id';
unique artist_name => ['name'];
has_many cds => 'MyApp::Schema::Result::CD', 'artist_id';
1;
PostgreSQL-specific example:
package MyApp::Schema::Result::User;
use DBIO::Cake -inflate_json;
table 'users';
col id => uuid;
col name => varchar(100);
col role => enum(qw( admin moderator user guest )), null;
col metadata => jsonb \"{}";
col embedding => vector(1536);
col tags => array(text), null;
col tsv => tsvector null;
col created_at => timestamp;
col updated_at => timestamp on_update;
col deleted_at => timestamp null;
primary_key 'id';
idx user_tags => ['tags'], pg => { using => 'gin' };
1;
See t/cake_comprehensive.t for a runnable example.
DESCRIPTION
DBIO::Cake is the most concise way to define DBIO result classes. It keeps the same underlying schema metadata as vanilla DBIO::Core, but replaces verbose hashref-heavy declarations with a DDL-like DSL.
Use Cake when you want result classes to read like schema definitions without changing how the rest of DBIO behaves.
When you use DBIO::Cake, it automatically:
Enables
strictandwarningsSets the calling class to inherit from DBIO::Core
Exports all DSL functions into the calling package
Cleans up exported symbols after the scope ends (via namespace::clean)
METHODS
type_function_names
my @names = DBIO::Cake->type_function_names;
The names of the column type functions Cake exports (integer, varchar, timestamp, ...), the words that stand for a type in col NAME => TYPE. Column modifiers, relationship functions and the table functions are not among them. DBIO::Generate::Style::Cake writes a column's type as a bare word for exactly these names and as a string for every other type.
COMMA-FREE SYNTAX
Cake supports a DDL-like comma-free syntax. All type functions and modifiers pass @_ through, so Perl chains them via nested function calls:
col id => integer auto_inc; # parsed as: integer(auto_inc())
col bio => text null; # parsed as: text(null())
col active => boolean default(1); # parsed as: boolean(default(1))
When you need a comma: after a number or closing parenthesis, Perl needs a comma before the next bareword:
col name => varchar(100), null; # comma after (100)
col name => varchar 100, null; # comma after 100
This matches DBIx::Class::ResultDDL conventions.
IMPORT OPTIONS
use DBIO::Cake; # defaults
use DBIO::Cake -inflate_datetime; # load InflateColumn::DateTime
use DBIO::Cake -inflate_json; # auto-inflate json/jsonb columns
use DBIO::Cake -retrieve_defaults; # set retrieve_on_insert for columns with defaults
use DBIO::Cake -no_autoclean; # don't clean up symbols
Multiple options can be combined:
use DBIO::Cake -inflate_datetime, -inflate_json;
SMART DEFAULTS
Cake automatically sets sensible defaults based on column type and nullability.
Timestamp columns
The behavior depends on nullability and the on_update modifier:
col created_at => timestamp; # NOT NULL -> set_on_create
col updated_at => timestamp on_update; # NOT NULL -> set_on_create + set_on_update
col deleted_at => timestamp null; # nullable -> no auto-set
col last_login => timestamp null, on_update; # nullable -> only set_on_update
The logic: NOT NULL timestamp columns must have a value on create, so set_on_create is implied. Nullable columns don't need a value on create, so only explicit on_update is applied.
This integrates with the DBIO::Timestamp component built into DBIO core.
UUID columns
NOT NULL uuid columns automatically get retrieve_on_insert so the database-generated default (e.g. PostgreSQL's gen_random_uuid()) is retrieved after INSERT:
col id => uuid;
# -> retrieve_on_insert => 1
Scalar references as defaults
A scalar reference anywhere in a col declaration is treated as a default_value. This is a shorthand for default(\...):
col id => uuid \"gen_random_uuid()";
col active => boolean \1;
col created => timestamp \"now()";
# equivalent to:
col id => uuid default(\"gen_random_uuid()");
For literal SQL defaults, use a reference to a string. For Perl-side defaults, use default($value) without a reference.
COLUMN DECLARATION
col
col $name => @type_and_modifiers;
Adds one column to the result class. The list after the name is what the type functions and modifiers below return; col turns it into the column info hashref:
a scalar reference anywhere in the list becomes
default_value(see "Scalar references as defaults")dotted keys are nested:
'extra.unsigned' => 1becomesextra => { unsigned => 1 }is_nullabledefaults to 0 -- columns are NOT NULL unless markednullwith
-retrieve_defaults, a column with adefault_valuegetsretrieve_on_insert(unless set explicitly)an active inflation import option (e.g.
-inflate_json) adds the column attributes the storage's type registry declares for that data type, without overriding ones given explicitlythe timestamp and UUID defaults of "SMART DEFAULTS" are applied
COLUMN TYPES
All type functions return flat key-value lists and pass through @_, enabling the comma-free syntax. Each entry names the data_type it sets. Where a function takes a size-like argument, it is recognised only when it looks like a number, so varchar null still works.
Integer types
col id => integer auto_inc;
col count => bigint;
- integer
-
integer. - tinyint
-
tinyint. - smallint
-
smallint. - bigint
-
bigint.
Serial types (auto-increment shortcuts)
col id => serial; # integer + auto_inc in one
- serial
-
serial, plusis_auto_increment => 1. - bigserial
-
bigserial, plusis_auto_increment => 1. - smallserial
-
smallserial, plusis_auto_increment => 1.
Numeric types
col price => numeric(10, 2);
- numeric
-
numeric.numeric($precision)setssize => [$precision],numeric($precision, $scale)setssize => [$precision, $scale]. - decimal
-
decimal, with the same optional precision and scale as "numeric".
Floating point types
- real
- float4
-
real(float4is an alias). - double
- float8
-
double precision(float8is an alias). - float
-
float;float($bits)also setssize.
String types
col code => char(3);
col name => varchar(100), null;
- char
-
char,sizefrom the argument (default 1). - varchar
-
varchar,sizefrom the argument (default 255).
Text types
col bio => text null;
- text
- tinytext
- mediumtext
- longtext
-
text,tinytext,mediumtext,longtext.
Binary types
- blob
- tinyblob
- mediumblob
- longblob
- bytea
-
blob,tinyblob,mediumblob,longblob,bytea.
Boolean
col active => boolean default(1);
- boolean
- bool
-
boolean(boolis an alias).
Date/Time types
col created_at => timestamp; # auto set_on_create
col updated_at => timestamp on_update; # auto set_on_create + set_on_update
col deleted_at => timestamp null; # no auto-set
col birthday => date null;
- date
-
date. - datetime
- timestamp
- time
-
datetime,timestamp,time. An optional first argument that is a plain string and not a lowercase word (e.g.timestamp 'UTC') setstimezone. - timetz
-
time with time zone. - timestamptz
-
timestamp with time zone. - interval
-
interval.
Enum and set
col role => enum(qw( admin moderator user guest ));
- enum
- set
-
enum/set, with the arguments as the list of allowed values inextra => { list => [...] }. Unlike the other type functions these take no further options: every argument is a value, so modifiers must be separated by a comma (enum(qw(a b)), null).
UUID
col id => uuid; # auto retrieve_on_insert
- uuid
-
uuid.
JSON
col metadata => jsonb null;
With -inflate_json, json/jsonb columns are automatically serialized.
- json
- jsonb
-
json,jsonb.
Array (PostgreSQL)
col tags => array(text), null;
- array
-
array($type)setsdata_typeto$typefollowed by[](text[]above); the element type may be given as a type function call or as a type name.array(\%info)instead setsdata_type => 'ARRAY'and merges%infoin.
Vector / AI (pgvector)
col embedding => vector(1536);
- vector
- halfvec
- sparsevec
-
vector,halfvec,sparsevec; the optional dimension argument setssize.
Full-text search (PostgreSQL)
- tsvector
- tsquery
-
tsvector,tsquery.
Network types (PostgreSQL)
- inet
- cidr
- macaddr
- macaddr8
-
inet,cidr,macaddr,macaddr8.
Geometric types (PostgreSQL)
- point
- line
- lseg
- box
- path
- polygon
- circle
-
point,line,lseg,box,path,polygon,circle.
Range types (PostgreSQL)
- int4range
- int8range
- numrange
- tsrange
- tstzrange
- daterange
-
int4range,int8range,numrange,tsrange,tstzrange,daterange.
Other
- money
- xml
- hstore
-
money,xml,hstore. - bit
- varbit
-
bit,varbit; the optional size argument setssize.
COLUMN MODIFIERS
All modifiers return flat key-value lists and pass through @_.
null
Marks the column as nullable.
col bio => text null;
auto_inc
Marks the column as auto-increment.
col id => integer auto_inc;
fk
Marks the column as a foreign key.
col author_id => integer fk;
unsigned
Marks the column as unsigned (MySQL).
col count => integer unsigned;
default($value)
Sets the default value.
col active => boolean default(1);
col created => timestamp default(\"now()");
on_create
Explicitly set set_on_create. Normally not needed -- NOT NULL timestamp columns get this automatically.
on_update
Set set_on_update -- the column value is refreshed on every row update.
col updated_at => timestamp on_update;
TABLE AND CONSTRAINT FUNCTIONS
table
table 'my_table';
Sets the table name for this result class.
primary_key
primary_key 'id';
primary_key 'artist_id', 'cd_id';
Sets the primary key column(s).
unique
unique \@cols;
unique $name => \@cols;
Adds a unique constraint.
RELATIONSHIP FUNCTIONS
belongs_to author => 'MyApp::Schema::Result::Author', 'author_id';
has_one isbn => 'MyApp::Schema::Result::ISBN', 'book_id';
has_many books => 'MyApp::Schema::Result::Book', 'author_id';
might_have bio => 'MyApp::Schema::Result::Bio', 'author_id';
many_to_many roles => 'actor_roles', 'role';
- belongs_to
- has_one
- has_many
- might_have
- many_to_many
-
Call the class method of the same name (see DBIO::Relationship) on the result class, with all arguments unchanged.
rel_one
Like belongs_to but forces join_type => 'left'.
rel_many
Alias for has_many.
CASCADE HELPERS
ddl_cascade
Returns on_delete => 'CASCADE', on_update => 'CASCADE' for use in relationship attribute hashes.
dbic_cascade
Returns cascade_delete => 1, cascade_copy => 1.
VIEW SUPPORT
view
view 'my_view', 'SELECT * FROM artists WHERE active = 1';
Declares a view-based result source.
TIMESTAMP HELPERS
Shortcut functions for the most common timestamp column patterns.
col_created
col_created; # creates 'created_at' column
col_created 'born_at'; # custom column name
Equivalent to col created_at => timestamp.
col_updated
col_updated; # creates 'updated_at' column
col_updated 'modified_at'; # custom column name
Equivalent to col updated_at => timestamp on_update.
cols_updated_created
cols_updated_created; # creates both created_at + updated_at
Creates both timestamp columns in one call. The most common pattern -- just add this one line and you're done.
INDEX SUPPORT
idx
idx name_idx => ['name'];
idx composite_idx => ['last_name', 'first_name'], type => 'unique';
idx tags_idx => ['tags'], pg => { using => 'gin' };
idx draft_only => ['key'],
type => 'unique',
pg => { where => 'version IS NULL' };
The options are type, options and one block per driver: pg takes a hashref with where, using, with and expression, sqlite takes where and expression, mysql takes using. Any other key, in either place, is rejected when the class loads, naming the index and the key.
Declares an index. The declaration is stored on the result source in its canonical form, { name, columns, unique, type, options, driver }, and every driver reads it from there with index_declarations:
for my $idx (MyApp::Schema::Result::User->result_source_instance
->index_declarations) {
# $idx->{name}, $idx->{columns}, $idx->{unique}, $idx->{driver}{pg}
}
type => 'unique' makes the entry unique, and a block is carried as driver => { pg => { ... } }, the block of the driver that understands it.
A schema is bound to one driver and a block is that driver's: the driver reads its entries with index_declarations_for, which refuses an entry that only carries another driver's block (a partial unique index declared for pg is not a plain unique index on sqlite) and an unknown key in its own block. An empty own block, sqlite => {}, says that the plain index is wanted. A class that has to serve two drivers is two schema classes sharing the source's columns. Declaring the same name twice replaces the earlier declaration. indices from DBIO::ResultSourceProxy fills the same declaration list.
A driver reads the declarations from the source; the Result class gets no index method of its own from them. An index a driver namespace declares by hand (pg_index for PostgreSQL) stays valid beside them.
PostgreSQL partial indexes
idx agent_published => ['key', 'version'],
type => 'unique',
pg => { where => 'version IS NOT NULL' };
idx agent_draft => ['key'],
type => 'unique',
pg => { where => 'version IS NULL' };
SEE ALSO
DBIO::Core, DBIO::Candy, DBIO::ResultSource, DBIx::Class::ResultDDL (inspiration for Cake's syntax)
AUTHOR
DBIO & DBIx::Class Authors
COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.