File Coverage

blib/lib/Yancy/Backend/Sqlite.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             package Yancy::Backend::Sqlite;
2             our $VERSION = '1.088';
3             # ABSTRACT: A backend for SQLite using Mojo::SQLite
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod ### URL string
8             #pod use Mojolicious::Lite;
9             #pod plugin Yancy => {
10             #pod backend => 'sqlite:data.db',
11             #pod read_schema => 1,
12             #pod };
13             #pod
14             #pod ### Mojo::SQLite object
15             #pod use Mojolicious::Lite;
16             #pod use Mojo::SQLite;
17             #pod plugin Yancy => {
18             #pod backend => { Sqlite => Mojo::SQLite->new( 'sqlite:data.db' ) },
19             #pod read_schema => 1,
20             #pod };
21             #pod
22             #pod ### Hashref
23             #pod use Mojolicious::Lite;
24             #pod plugin Yancy => {
25             #pod backend => {
26             #pod Sqlite => {
27             #pod dsn => 'sqlite:data.db',
28             #pod },
29             #pod },
30             #pod read_schema => 1,
31             #pod };
32             #pod
33             #pod =head1 DESCRIPTION
34             #pod
35             #pod This Yancy backend allows you to connect to a SQLite database to manage
36             #pod the data inside. This backend uses L to connect to SQLite.
37             #pod
38             #pod See L for the methods this backend has and their return
39             #pod values.
40             #pod
41             #pod =head2 Backend URL
42             #pod
43             #pod The URL for this backend takes the form C<<
44             #pod sqlite: >>.
45             #pod
46             #pod Some examples:
47             #pod
48             #pod # A database file in the current directory
49             #pod sqlite:filename.db
50             #pod
51             #pod # In a specific location
52             #pod sqlite:/tmp/filename.db
53             #pod
54             #pod =head2 Schema Names
55             #pod
56             #pod The schema names for this backend are the names of the tables in the
57             #pod database.
58             #pod
59             #pod So, if you have the following schema:
60             #pod
61             #pod CREATE TABLE people (
62             #pod id INTEGER PRIMARY KEY,
63             #pod name VARCHAR NOT NULL,
64             #pod email VARCHAR NOT NULL
65             #pod );
66             #pod CREATE TABLE business (
67             #pod id INTEGER PRIMARY KEY,
68             #pod name VARCHAR NOT NULL,
69             #pod email VARCHAR NULL
70             #pod );
71             #pod
72             #pod You could map that to the following schema:
73             #pod
74             #pod {
75             #pod backend => 'sqlite:filename.db',
76             #pod schema => {
77             #pod People => {
78             #pod required => [ 'name', 'email' ],
79             #pod properties => {
80             #pod id => {
81             #pod type => 'integer',
82             #pod readOnly => 1,
83             #pod },
84             #pod name => { type => 'string' },
85             #pod email => { type => 'string' },
86             #pod },
87             #pod },
88             #pod Business => {
89             #pod required => [ 'name' ],
90             #pod properties => {
91             #pod id => {
92             #pod type => 'integer',
93             #pod readOnly => 1,
94             #pod },
95             #pod name => { type => 'string' },
96             #pod email => { type => 'string' },
97             #pod },
98             #pod },
99             #pod },
100             #pod }
101             #pod
102             #pod =head1 SEE ALSO
103             #pod
104             #pod L, L
105             #pod
106             #pod =cut
107              
108 1     1   5393 use Mojo::Base 'Yancy::Backend::MojoDB';
  1         4  
  1         13  
109             use Text::Balanced qw( extract_bracketed );
110             use Scalar::Util qw( blessed );
111             BEGIN {
112             eval { require DBD::SQLite; DBD::SQLite->VERSION( 1.56 ); 1 }
113             or die "Could not load SQLite backend: DBD::SQLite version 1.56 or higher required\n";
114             eval { require Mojo::SQLite; Mojo::SQLite->VERSION( 3.005 ); 1 }
115             or die "Could not load SQLite backend: Mojo::SQLite version 3.005 or higher required\n";
116             }
117              
118             sub new {
119             my ( $class, $driver, $schema ) = @_;
120             if ( blessed $driver ) {
121             die "Need a Mojo::SQLite object. Got " . blessed( $driver )
122             if !$driver->isa( 'Mojo::SQLite' );
123             return $class->SUPER::new( $driver, $schema );
124             }
125             elsif ( ref $driver eq 'HASH' ) {
126             my $sqlite = Mojo::SQLite->new;
127             for my $method ( keys %$driver ) {
128             $sqlite->$method( $driver->{ $method } );
129             }
130             return $class->SUPER::new( $sqlite, $schema );
131             }
132             my $found = (my $connect = $driver) =~ s{^.*?:}{};
133             return $class->SUPER::new(
134             Mojo::SQLite->new( $found ? "sqlite:$connect" : () ),
135             $schema,
136             );
137             }
138              
139             sub ignore_table {
140             my ( $self, $table ) = @_;
141             return +( $table =~ /^sqlite_/ ) || $self->SUPER::ignore_table( $table );
142             }
143              
144             my %DEFAULT2FIXUP = (
145             NULL => undef,
146             TRUE => 1,
147             FALSE => 0,
148             CURRENT_TIME => 'now',
149             CURRENT_DATE => 'now',
150             CURRENT_TIMESTAMP => 'now',
151             );
152             sub fixup_default {
153             my ( $self, $value ) = @_;
154             return undef if !defined $value;
155             return $DEFAULT2FIXUP{ $value } if exists $DEFAULT2FIXUP{ $value };
156             $self->driver->db->query( 'SELECT ' . $value )->array->[0];
157             }
158              
159             sub table_info {
160             my ( $self ) = @_;
161             my $dbh = $self->dbh;
162             my $tables = $dbh->table_info( undef, undef, '%', undef )->fetchall_arrayref({});
163             return [ grep { $_->{TABLE_NAME} !~ /^sqlite_/ } @$tables ];
164             }
165              
166             sub column_info {
167             my ( $self, $table ) = @_;
168             my $row = $self->driver->db->query(
169             q{SELECT sql FROM SQLITE_MASTER WHERE type='table' and name = ?},
170             $table->{TABLE_NAME},
171             )->array || return [];
172             my $sql = $row->[0];
173             my $columns = $self->dbh->column_info( @{$table}{qw( TABLE_CAT TABLE_SCHEM TABLE_NAME )}, '%' )->fetchall_arrayref({});
174             # ; use Data::Dumper;
175             # ; say Dumper $columns;
176             for my $c ( @$columns ) {
177             my $col_name = $c->{COLUMN_NAME};
178             $c->{AUTO_INCREMENT} = 1 if $sql =~ /${col_name}\s+[^,\)]+AUTOINCREMENT/i;
179             if ( $sql =~ /${col_name}[^,\)]+CHECK\s*(.+)\)\s*$/si ) {
180             # Column has a check constraint, see if it's an enum-like
181             my $check = $1;
182             my ( $constraint, $remainder ) = extract_bracketed( $check, '(' );
183             if ( $constraint =~ /${col_name}\s+in\s*\([^)]+\)/i ) {
184             $constraint = substr $constraint, 1, -1;
185             $constraint =~ s/\s*${col_name}\s+in\s+//i;
186             $constraint =~ s/\s*$//;
187             my @values = split ',', substr $constraint, 1, -1;
188             s/^\s*'|'\s*$//g for @values;
189             $c->{ENUM} = \@values;
190             }
191             }
192             $c->{COLUMN_DEF} = $self->fixup_default( $c->{COLUMN_DEF} );
193             }
194             return $columns;
195             }
196              
197             1;
198              
199             __END__