File Coverage

blib/lib/Mojo/SQL.pm
Criterion Covered Total %
statement 30 30 100.0
branch 4 4 100.0
condition n/a
subroutine 6 6 100.0
pod 4 4 100.0
total 44 44 100.0


line stmt bran cond sub pod time code
1             package Mojo::SQL;
2 2     2   214805 use Mojo::Base 'Exporter', -signatures;
  2         5  
  2         17  
3              
4 2     2   1699 use Mojo::SQL::Statement;
  2         8  
  2         12  
5              
6             our $VERSION = '0.02';
7              
8             our @EXPORT_OK = qw(escape_identifier escape_literal sql sql_unsafe);
9              
10 6     6 1 186930 sub escape_identifier ($identifier) {
  6         14  
  6         10  
11 6         15 my $string = "$identifier";
12 6         19 $string =~ s/"/""/g;
13 6         36 return qq{"$string"};
14             }
15              
16 7     7 1 7590 sub escape_literal ($literal) {
  7         17  
  7         11  
17 7         19 my $string = "$literal";
18 7 100       27 my $escape = $string =~ /\\/ ? 1 : 0;
19 7         16 $string =~ s/\\/\\\\/g;
20 7         18 $string =~ s/'/''/g;
21 7 100       44 return $escape ? qq{ E'$string'} : qq{'$string'};
22             }
23              
24 18     18 1 16313 sub sql ($text, @values) { Mojo::SQL::Statement->new->parse($text, @values) }
  18         24  
  18         21  
  18         16  
  18         35  
25              
26 2     2 1 1991 sub sql_unsafe ($text, @values) { Mojo::SQL::Statement->new->parse_unsafe($text, @values) }
  2         3  
  2         3  
  2         3  
  2         6  
27              
28             1;
29              
30             =encoding utf8
31              
32             =head1 NAME
33              
34             Mojo::SQL - Safely generate and compose SQL statements
35              
36             =head1 SYNOPSIS
37              
38             use Mojo::SQL qw(sql);
39              
40             # {text => 'SELECT * FROM users WHERE name = $1', values => ['sebastian']}
41             my $query = sql('SELECT * FROM users WHERE name = ?', 'sebastian')->to_query;
42              
43             =head1 DESCRIPTION
44              
45             L safely generates and composes SQL statements. To prevent SQL injection attacks, every C in the input
46             becomes a placeholder in the generated query, with the corresponding value bound to it. Partial statements can be
47             composed recursively to build more complex queries.
48              
49             Literal question marks can be escaped with C.
50              
51             use Mojo::SQL qw(sql);
52              
53             my $role = 'admin';
54             my $partial = sql('AND role = ?', $role);
55             my $name = 'root';
56              
57             # {text => 'SELECT * FROM users WHERE name = $1 AND role = $2', values => ['root', 'admin']}
58             my $query = sql('SELECT * FROM users WHERE name = ? ?', $name, $partial)->to_query;
59              
60             Make partial statements optional to dynamically generate C clauses.
61              
62             my $optional = $foo ? sql('AND foo IS NOT NULL') : sql('');
63             my $query = sql('SELECT * FROM users WHERE name = ? ?', 'sebastian', $optional)->to_query;
64              
65             If you need a little more control over the generated SQL query, you can also bypass safety features with
66             L. But make sure to handle unsafe values yourself with appropriate escaping functions for your database.
67             For PostgreSQL there are L and L functions included with this module.
68              
69             use Mojo::SQL qw(sql sql_unsafe escape_literal);
70              
71             my $role = 'role = ' . escape_literal('power user');
72             my $partial = sql_unsafe 'AND ?', $role;
73             my $name = 'root';
74              
75             # {text => "SELECT * FROM users WHERE name = \$1 AND role = 'power user'", values => ['root']}
76             my $query = sql('SELECT * FROM users WHERE name = ? ?', $name, $partial)->to_query;
77              
78             For databases that do not support numbered placeholders like C<$1> and C<$2>, you can set a custom character with the
79             C option.
80              
81             # {text => 'SELECT * FROM users WHERE name = ?', values => ['root']}
82             my $query = sql('SELECT * FROM users WHERE name = ?', 'root')->to_query({placeholder => '?'});
83              
84             =head1 FUNCTIONS
85              
86             L implements the following functions, which can be imported individually.
87              
88             =head2 escape_identifier
89              
90             my $escaped = escape_identifier('some_table');
91              
92             Escape an identifier (only the PostgreSQL format is currently supported).
93              
94             =head2 escape_literal
95              
96             my $escaped = escape_literal('some value');
97              
98             Escape a literal (only the PostgreSQL format is currently supported).
99              
100             =head2 sql
101              
102             my $stmt = sql('SELECT * FROM users WHERE name = ?', 'sebastian');
103              
104             Create a new L from an SQL string. Each C in the string becomes a placeholder, and the
105             corresponding value is bound to it. L values are spliced in recursively, so partial statements
106             can be composed to build more complex queries. Literal question marks can be escaped with C.
107              
108             =head2 sql_unsafe
109              
110             my $stmt = sql_unsafe 'SELECT * FROM users WHERE name = ?', 'sebastian';
111              
112             Create a new L without safe placeholders. Each C in the string is replaced literally by the
113             corresponding value. Literal question marks can be escaped with C. Use with care.
114              
115             =head1 COPYRIGHT AND LICENSE
116              
117             Copyright (C) 2026, Sebastian Riedel.
118              
119             This program is free software, you can redistribute it and/or modify it under the terms of the MIT license.
120              
121             =head1 SEE ALSO
122              
123             L, L, L.
124              
125             =cut