# PODNAME: Yancy::Backend
# ABSTRACT: Handles talking to the database.

__END__

=pod

=head1 NAME

Yancy::Backend - Handles talking to the database.

=head1 VERSION

version 1.072

=head1 SYNOPSIS

    my $be = Yancy::Backend->new( $url );

    $result = $be->list( $schema, $where, $options );
    say "Total: " . $result->{total};
    say "Name: " . $_->{name} for @{ $result->{items} };

    $item = $be->get( $schema, $id );
    $be->set( $schema, $id, $item );
    $be->delete( $schema, $id );
    $id = $be->create( $schema, $item );

=head1 DESCRIPTION

A C<Yancy::Backend> handles talking to the database. Different Yancy
backends will support different databases. To use a backend, see
L</SUPPORTED BACKENDS>. To make your own backend, see L</METHODS> for
the list of methods each backend supports, their arguments, and their
return values.

=head2 Terminology

Yancy backends work with schemas, which are made up of items.
A schema is a set of items, like a database table. An item is
a single element of a schema, and must be a hashref.

=head2 Asynchronous Backends

Asynchronous backends implement both a synchronous and an asynchronous
API (using promises).

=head2 Synchronous-only Backends

Synchronous-only backends also implement a promises API for
compatibility, but will not perform requests concurrently.

=head1 SUPPORTED BACKENDS

=over

=item * L<Yancy::Backend::Pg> - Postgres backend

=item * L<Yancy::Backend::Mysql> - MySQL backend

=item * L<Yancy::Backend::Sqlite> - SQLite backend

=item * L<Yancy::Backend::Dbic> - L<DBIx::Class> backend

=back

Other backends are available on CPAN.

=over

=item * L<Yancy::Backend::Static> - Backend for a static site generator
with Markdown.

=back

=head1 METHODS

=head2 new

    my $url = 'test://custom_string';
    my $be = Yancy::Backend::Test->new( $url, $schema );

Create a new backend object. C<$url> is a string that begins with the
backend name followed by a colon. Everything else in the URL is for the
backend to use to describe how to connect to the underlying database and
any options for the backend object itself.

The backend name will be run through C<ucfirst> before being looked up
in C<Yancy::Backend::>. For example, C<mysql://...> will use the
L<Yancy::Backend::Mysql> module.

C<$schema> is a hash reference of schema configuration from the
Yancy configuration. Important configuration for the backend to support:

=over

=item x-id-field

The name of the ID field or fields for the schema. Defaults to C<id>. It
does not need to be the primary key: This can be any unique identifier.
To define a composite key, provide an array of strings.

=item properties

Each schema is, at the very top, an array of hashrefs (a series of
rows of column/value pairs). So, each schema should have
a C<properties> key to declare what properties are available on the
items in this schema.

This is a hash reference with the keys as the field names and the values
as hash references of configuration for the field. Some important
configuration for fields are:

=over

=item type

The type of the field. Can be an array reference of multiple types (this
is JSON Schema, not OpenAPI Schema).

See L<Yancy::Help::Config/Types> for more information on supported
types.

=back

Other field configuration can be supported by the backend's
C<read_schema> method, but are not relevant for the standard API: Yancy
backends do not perform data validation. Yancy relies on the Controller
and the underlying database to do that.

=back

=head2 list

    my $result = $be->list( $schema, $where, $opt );
    # { total => ..., items => [ ... ] }

Fetch a list of items from a schema. C<$schema> is the
schema name.

C<$where> is a L<SQL::Abstract where structure|SQL::Abstract/WHERE
CLAUSES>.

    # Search for all Dougs
    $be->list( 'people', { name => { -like => 'Doug%' } } );
    # Find adults
    $be->list( 'people', { age => { '>=' => 18 } } );
    # Find men we can contact
    $be->list( 'people', { gender => 'male', contact => 1 } );

Additionally, Yancy backends support the following additional
keys in the where structure:

=over

=item -has (EXPERIMENTAL)

The C<-has> operator searches inside a data structure (an array or
a hash). This operator examines the type of the field being searched to
perform the appropriate query.

    # Create a new page with an array of tags and a hash of author
    # information
    $be->create( pages => {
        title => 'Release v1.481',
        tags => [ 'release', 'minor' ],
        author => {
            name => 'Doug Bell',
            email => 'doug@example.com',
        },
    } );

    # All pages that have the tag "release"
    $be->list( pages => { tags => { -has => 'release' } } );

    # All pages that have both the tags "release" and "major"
    $be->list( pages => { tags => { -has => [ 'release', 'major' ] } } );

    # All pages that have the author's name starting with Doug
    $be->list( pages => { author => { -has => { name => { -like => 'Doug%' } } } } );

This is not yet supported by all backends, and may never be supported by
some. Postgres has array columns and JSON fields. MySQL has JSON fields.
The L<Yancy::Util/match> function matches against Perl data structures.
All of these should support C<-has> and C<-not_has> before it can be
considered not experimental.

=back

C<$opt> is a hash reference with the following keys:

=over

=item * limit - The number of items to return

=item * offset - The number of items to skip

=item * order_by - A L<SQL::Abstract order by clause|SQL::Abstract/ORDER BY CLAUSES>

=back

    # Get the second page of 20 people
    $be->list( 'people', {}, { limit => 20, offset => 20 } );
    # Get the list of people sorted by age, oldest first
    $be->list( 'people', {}, { order_by => { -desc => 'age' } } );
    # Get the list of people sorted by age first, then name (ascending)
    $be->list( 'people', {}, { order_by => [ 'age', 'name' ] } );

Returns a hashref with two keys:

=over

=item items

An array reference of hash references of item data

=item total

The total count of items that would be returned without C<limit> or
C<offset>.

=back

=head2 list_p

    my $promise = $be->list_p( $schema, $where, $opt );
    $promise->then( sub {
        my ( $result ) = @_;
        # { total => ..., items => [ ... ] }
    } );

Fetch a list of items asynchronously using promises. Returns a promise that
resolves to a hashref with C<items> and C<total> keys. See L</list> for
arguments and return values.

=head2 get

    my $item = $be->get( $schema, $id );

Get a single item. C<$schema> is the schema name. C<$id> is the
ID of the item to get: Either a string for a single key field, or a
hash reference for a composite key. Returns a hashref of item data.

=head2 get_p

    my $promise = $be->get_p( $schema, $id );
    $promise->then( sub {
        my ( $item ) = @_;
        # ...
    } );

Get a single item asynchronously using promises. Returns a promise that
resolves to the item. See L</get> for arguments and return values.

=head2 set

    my $success = $be->set( $schema, $id, $item );

Update an item. C<$schema> is the schema name. C<$id> is the ID of the
item to update: Either a string for a single key field, or a hash
reference for a composite key. C<$item> is the item's data to set.
Returns a boolean that is true if a row with the given ID was found and
updated, false otherwise.

Currently the values of the data cannot be references, only simple
scalars or JSON booleans.

=head2 set_p

    my $promise = $be->set_p( $schema, $id );
    $promise->then( sub {
        my ( $success ) = @_;
        # ...
    } );

Update a single item asynchronously using promises. Returns a promise
that resolves to a boolean indicating if the row was updated. See
L</set> for arguments and return values.

=head2 create

    my $id = $be->create( $schema, $item );

Create a new item. C<$schema> is the schema name.  C<$item> is
the item's data. Returns the ID of the row created suitable to be passed
in to C<the get() method|/get>.

Currently the values of the data cannot be references, only simple
scalars or JSON booleans.

=head2 create_p

    my $promise = $be->create_p( $schema, $item );
    $promise->then( sub {
        my ( $id ) = @_;
        # ...
    } );

Create a new item asynchronously using promises. Returns a promise that
resolves to the ID of the newly-created item. See L</create> for
arguments and return values.

=head2 delete

    $be->delete( $schema, $id );

Delete an item. C<$schema> is the schema name. C<$id> is the ID of the
item to delete: Either a string for a single key field, or a hash
reference for a composite key. Returns a boolean that is true if a row
with the given ID was found and deleted. False otherwise.

=head2 delete_p

    my $promise = $be->delete_p( $schema, $id );
    $promise->then( sub {
        my ( $success ) = @_;
        # ...
    } );

Delete an item asynchronously using promises. Returns a promise that
resolves to a boolean indicating if the row was deleted. See L</delete>
for arguments and return values.

=head2 read_schema

    my $schema = $be->read_schema;
    my $table = $be->read_schema( $table_name );

Read the schema from the database tables. Returns an OpenAPI schema
ready to be merged into the user's configuration. Can be restricted
to only a single table.

=head1 AUTHOR

Doug Bell <preaction@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2021 by Doug Bell.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
