File Coverage

blib/lib/SQL/Steno.pm
Criterion Covered Total %
statement 340 459 74.0
branch 247 364 67.8
condition 89 141 63.1
subroutine 24 28 85.7
pod 3 21 14.2
total 703 1013 69.4


line stmt bran cond sub pod time code
1             =encoding utf8
2              
3             =head1 NAME
4              
5             SQL::Steno - Short hand for SQL and compact output
6              
7             =head1 SYNOPSIS
8              
9             Type some short-hand, see the corresponding SQL and its output:
10              
11             steno> TABLE1;somecolumn > 2 -- ; after tables means where
12             select * from TABLE1 where somecolumn > 2;
13             prepare: 0.000s execute: 0.073s rows: 14
14             id|column1 |column2
15             | | |column3
16             | | | |somecolumn
17             ----|-------------------------------------------|----|-|-|
18             27|foo | |a|7|
19             49|bar |abcd|a|3|
20             81|baz\nbazinga\nbazurka |jk |b|9|
21             1984|bla bla bla bla bla bla bla bla bla bla bla|xyz |c|5|
22             ...
23             steno> /abc|foo/#TBL1;.socol > 2 -- /regexp/ grep, #tableabbrev, .columnabbrev
24             select * from TABLE1 where somecolumn > 2;
25             prepare: 0.000s execute: 0.039s rows: 14
26             id|column1
27             | |column2
28             | | |[column3=a]
29             | | |somecolumn
30             --|---|----|-|
31             27|foo| |7|
32             49|bar|abcd|3|
33             steno> .c1,.c2,.some;#TE1#:ob2d3 -- ; before tables means from, 2nd # alias, :macro
34             select column1,column2,somecolumn from TABLE1 TE1 order by 2 desc, 3;
35             ...
36             steno> n(), yr(), cw(,1,2,3) -- functionabbrev before (, can have initial default arg
37             select count(*), year(now()), concat_ws(',',1,2,3);
38             ...
39             steno> .col1,.clm2,.sn;#TBL1:jTBL2 u(id);mydate :b :m+3d and :d-w -- :jTABLEABBREV and :+/- family
40             select column1,column2,somecolumn from TABLE1 join TABLE2 using(id) where mydate
41             between date_format(now(),"%Y-%m-01")+interval 3 day and curdate()-interval 1 week;
42             ...
43              
44             =head1 DESCRIPTION
45              
46             You're the command-line type, but are tired of typing C
47             where CONDITION>, always forgetting the final C<;>? Output always seems far
48             too wide and at least mysql cli messes up the format when it includes
49             newlines?
50              
51             This module consists of the function C which implements a
52             configurable ultra-compact language that maps to SQL. Then there is C
53             which performs normal SQL queries but has various tricks for narrowing the
54             output. It can also grep on whole rows, rather than having to list all fields
55             that you expect to match. They get combined by the function C which
56             converts and runs in an endless loop.
57              
58             This is work in progress, only recently isolated from a monolithic script.
59             Language elements and API may change as the need arises, e.g. C<:macro> used
60             to be C<@macro>, till the day I wanted to use an SQL-variable and noticed the
61             collision. In this early stage, you are more than welcome to propose
62             ammendments, especially if they make the language more powerful and/or more
63             consistent. Defaults are for MariaDB/MySQL, though the mechanism also works
64             with other DBs.
65              
66             =cut
67              
68 4     4   1974 use v5.14;
  4         9  
69              
70             package SQL::Steno v0.3.2;
71              
72 4     4   1905 use utf8;
  4         32  
  4         15  
73 4     4   100 use strict;
  4         6  
  4         61  
74 4     4   10 use warnings;
  4         5  
  4         83  
75 4     4   1728 use Time::HiRes qw(gettimeofday tv_interval);
  4         3769  
  4         13  
76              
77             binmode $_, ':utf8' for *STDIN, *STDOUT, *STDERR;
78              
79             our $dbh;
80             our $perl_re = qr/(\{(?:(?>[^{}]+)|(?-1))*\})/;
81             our( %Table_Columns, $table_re );
82             sub init {
83 0 0   0 0 0 die "\$dbh is undef\n" unless $dbh;
84 0         0 local @{$dbh}{qw(PrintWarn PrintError RaiseError)} = (0, 0, 0); # \todo is this right? views can barf because more restrictive.
  0         0  
85 0         0 for my $table ( @{$dbh->table_info->fetchall_arrayref} ) {
  0         0  
86 0         0 $Table_Columns{uc $table->[2]} = [];
87 0         0 splice @$table, 3, -1, '%';
88 0 0       0 my $info = $dbh->column_info( @$table ) or next;
89 0         0 for my $column ( @{$info->fetchall_arrayref} ) {
  0         0  
90 0         0 push @{$Table_Columns{$table->[2]}}, uc $column->[3];
  0         0  
91             }
92             }
93 0         0 undef $table_re; # (re)create below
94             }
95             our $init_from_query = <<\SQL;
96             select ucase(TABLE_NAME), ucase(COLUMN_NAME)
97             from information_schema.COLUMNS
98             where TABLE_SCHEMA = schema()
99             SQL
100             sub init_from_query {
101 0 0   0 0 0 die "\$dbh is undef\n" unless $dbh;
102 0         0 local @{$dbh}{qw(PrintWarn PrintError RaiseError)} = (0, 0, 0); # \todo is this right?
  0         0  
103 0         0 my $sth = $dbh->prepare( $init_from_query );
104 0         0 $sth->execute;
105 0         0 $sth->bind_columns( \my( $table, $column ));
106 0         0 push @{$Table_Columns{$table}}, $column while $sth->fetch;
  0         0  
107 0         0 undef $table_re; # (re)create below
108             }
109              
110              
111              
112             my %render =
113             (csv => \&render_csv,
114             table => \&render_table,
115             yaml => \&render_yaml,
116             yml => \&render_yaml);
117             my( $render, %opt );
118              
119             sub set_render($@) {
120 4     4 0 5 ($render, %opt) = ();
121 4         5 for( @_ ) {
122 7 100       8 if( defined $render ) { # all further args are opts
123 3         3 tr/ \t//d;
124 3         7 undef $opt{$_}; # make it exist
125             } else {
126 4         8 $render = substr $_, 1;
127             }
128             }
129 4         6 $render = $render{$render};
130 4         23 ''; # For use as a query
131             }
132              
133              
134              
135             our( %Queries_help, %Queries );
136             sub Query {
137 74     74 0 97 $Queries_help{$_[0]} = $_[1];
138 74         74 $Queries{$_[0]} = $_[2];
139 74         123 undef;
140             }
141             Query ".$_", " output '&.$_() this' or next query as \U$_", \&set_render
142             for keys %render;
143              
144             Query 'c'.substr( $_, 0, 1 ), "$_ show create (&- or similar for full display)",
145             '&{ $render ||= \&render__create; "" }=show create '.$_.' $1'
146             for qw(table function procedure view);
147              
148             Query @$_
149             for
150             ['-' => " output next query as YAML",
151             '&.yaml'],
152              
153              
154             [ps => ' show processlist (without Sleep)',
155             '{(${_}[7] // "") ne "Sleep"}=show processlist'],
156              
157             [psf => ' show full processlist (without Sleep)',
158             '{(${_}[7] // "") ne "Sleep"}=show full processlist'],
159              
160              
161             [t => 'unquotedtablename[,unquotedcolumnpattern] show fields',
162             'COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE NUL,COLUMN_KEY `KEY`,COLUMN_COMMENT;information_schema.`COLUMNS`;TABLE_SCHEMA=schema() and TABLE_NAME=$\1:ob ORDINAL_POSITION'],
163             # 'COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE NUL,COLUMN_KEY `KEY`,COLUMN_COMMENT;information_schema.`COLUMNS`;TABLE_SCHEMA=schema() and TABLE_NAME=$\1$?>? and COLUMN_NAME like$\>??:ob ORDINAL_POSITION'],
164              
165              
166             [s => 'var;value set @var = value',
167             'set @$1=$2'],
168              
169             [ss => "var;value set \@var = 'value'",
170             'set @$1=$\2'],
171              
172             [sd => 'var;value set @var = cast("value" as date)',
173             'set @$1=cast($\2 as date)'],
174              
175             [sdt => 'var;value set @var = cast("value" as datetime)',
176             'set @$1=cast($\2 as datetime)'],
177              
178             [st => 'var;value set @var = cast("value" as time)',
179             'set @$1=cast($\2 as time)'],
180              
181             [sy => ' set @a, @z yesterday is between @a and @z (see :baz)',
182             #'set @a=date(now())-interval 1 day, @z=date(now())-interval 1 second',
183             'select @a:=date(now()-interval 1 day)`@a`, @z:=date(now())-interval 1 second`@z`'];
184              
185              
186              
187             our( %Quotes_help, %Quotes );
188             sub Quote {
189 10     10 0 259 $Quotes_help{$_[0]} = $_[1];
190 10         12 $Quotes{$_[0]} = $_[2];
191 10         24 undef;
192             }
193             Quote @$_
194             for
195             ['a' => 'and: unquoted joined with &&',
196             '!%&&'],
197              
198             ['o' => 'or: unquoted joined with ||',
199             '!%||'];
200              
201              
202             our $weekstart = 1; # Monday is the first day of the week as per ISO 8601.
203             my $timespec_re = qr/[yqmwdhMs]?/;
204             our %Join_clause;
205             our %Macros =
206             (
207             b => ' between',
208             baz => ' between @a and @z',
209             d => ' distinct',
210             h => ' having',
211             j => ' join',
212             l => ' like',
213             lj => ' left join',
214             n => ' is null',
215             nb => ' not between',
216             nc => ' sql_no_cache',
217             nl => ' not like',
218             nn => ' is not null',
219             nr => ' not rlike',
220             od => ' on duplicate key update',
221             odku => ' on duplicate key update',
222             r => ' rlike',
223             u => ' union select',
224             ua => ' union all select',
225             wr => ' with rollup',
226             '' => sub {
227             my $join = 'for all #TBL matching TABLE';
228             my $int = 'see :+ :- :y-m :q+0 :d+2h';
229             my $gob = 'for 0 or more digits, optionally followed by a or d';
230             return ([jTBL => $join], ['jTBL#' => $join], [ljTBL => $join], [1 => 'for all numbers'],
231             [gb147 => $gob], [ob2d5a9 => $gob],
232             ['+' => < $int], ['d+2h' => $int], ['y-m' => $int], ['q+0' => $int])
233             :B+/-NO this B(ase) +/- N(umber, 0 for none, default 1 if O given) O(ffset)
234             optional B, O is y(ear), q(uarter), m(onth), w(eek), d(ay), h(our), M(inute), s(econd)
235             INT
236             unless @_; # help
237             for( $_[0] ) {
238             return " limit $_" if /^\d+$/;
239             if( s/^([og])b(?=(?:\d[ad]?)*$)/ $1 eq 'g' ? ' group by ' : ' order by ' /e ) {
240             s/(?
241             s/a/ asc/g; s/(?
242             return $_;
243             }
244             if( s/^(l?)j/#/ ) { # (l)jtbl: j or lj with any #tbl
245             my $left = $1 ? ' left' : '';
246             &convert_table_column;
247             /^(\w+)/;
248             return "$left join $_" . ($Join_clause{$1} || $Join_clause{''} || '');
249             }
250             return $_ if
251             s(^($timespec_re)([+-])(\d*)($timespec_re)$) {
252             ({ y => ' date_format(now(),"%Y-01-01")',
253             q => ' date_format(now()-interval mod(month(now())+11,3) month,"%Y-%m-01")',
254             m => ' date_format(now(),"%Y-%m-01")',
255             w => ' curdate()-interval weekday(now())' . ($weekstart ? ' day' : '+1 day'),
256             d => ' curdate()',
257             h => ' date_format(now(),"%F %H:00")',
258             M => ' date_format(now(),"%F %H:%M")',
259             s => ' now()' }->{$1} || '') .
260             ($3 ne '0' &&
261             " $2interval" .
262             ($3 ? " $3" : $4 ? ' 1' : '') .
263             ({ y => ' year',
264             q => ' quarter',
265             m => ' month',
266             w => ' week',
267             d => ' day',
268             h => ' hour',
269             M => ' minute',
270             s => ' second' }->{$4} || ''))
271             }eo;
272             }
273             });
274              
275             # \todo default arg n() -> n(*) time*(now())
276             our %Functions =
277             (
278             c => 'concat',
279             cw => 'concat_ws',
280             coa => 'coalesce',
281             gc => 'group_concat',
282             i => 'in', # not really fn, but ( follows
283             in => 'ifnull',
284             l => 'char_length',
285             lc => 'lcase',
286             m => 'min',
287             M => 'max',
288             n => 'count',
289             ni => 'not in', # -"-
290             s => 'substring',
291             u => 'using', # -"-
292             uc => 'ucase'
293             );
294              
295             # functions where the 1st argument can be now()
296             my @nowFunctions = qw(
297             adddate addtime convert_tz date date_add date_format date_sub datediff day
298             dayname dayofmonth dayofweek dayofyear hour last_day minute month
299             monthname quarter second subdate subtime time time_format time_to_sec
300             timediff timestamp to_days to_seconds week weekday weekofyear year
301             yearweek
302             );
303             our @Functions = sort @nowFunctions, qw(
304             abs acos aes_decrypt aes_encrypt ascii asin atan avg benchmark bin bit_and
305             bit_count bit_length bit_or bit_xor cast ceiling char_length char
306             character_length charset coalesce coercibility collation compress
307             concat_ws concat connection_id conv cos cot count crc32 curdate
308             current_date current_time current_timestamp current_user curtime database
309             decode default degrees des_decrypt des_encrypt elt encode encrypt exp
310             export_set field find_in_set floor format found_rows from_days
311             from_unixtime get_format get_lock greatest group_concat hex if ifnull
312             inet_aton inet_ntoa insert instr interval is_free_lock is_used_lock isnull
313             last_insert_id lcase least left length ln load_file localtime
314             localtimestamp locate log10 log2 log lower lpad ltrim make_set makedate
315             maketime master_pos_wait max md5 microsecond mid min mod name_const now
316             nullif oct octet_length old_password ord password period_add period_diff
317             pi position power quote radians rand release_lock repeat replace reverse
318             right round row_count rpad rtrim schema sec_to_time session_user sha1 sign
319             sin sleep soundex space sqrt stddev stddev_pop stddev_samp str_to_date
320             strcmp substring_index substring sum sysdate system_user tan timestampadd
321             timestampdiff trim truncate ucase uncompress uncompressed_length unhex
322             unix_timestamp upper user utc_date utc_time utc_timestamp uuid values
323             var_pop var_samp variance
324             );
325              
326             our %DefaultArguments = (
327             count => '*',
328             concat_ws => "','"
329             );
330             $DefaultArguments{$_} = 'now()' for @nowFunctions;
331              
332              
333             our %Tables;
334             our %Columns;
335              
336             sub regexp($$) {
337 5     5 1 3 my( $str, $type ) = @_;
338 5 100       9 if( $type < 2 ) {
339 2 50       5 return if $str !~ /_/; # Otherwise same as find sprintf cases
340 0 0       0 return ($type ? '' : '^') . join '.*?_', split /_/, $str; # 0 & 1
341             }
342 3         7 my $expr = join '.*?', split //, $str; # 2, 3 & 4
343 3 100       7 if( $type < 4 ) {
344 2         5 substr $expr, 0, 0, '^'; # 2 & 3
345 2 100       3 $expr .= '$' if $type == 2; # 2
346             }
347 3         4 $expr;
348             }
349              
350             my $error;
351             my @simple = qw(^%s$ ^%s_ ^%s _%s$ _%s %s$ %s_ %s);
352             sub find($$$\%;\@) {
353 55     55 0 69 my( $str, $prefix, $suffix, $hash, $list ) = @_;
354 55         61 my $ret = $hash->{$str};
355 55 100       162 return $ret if $ret;
356              
357 13         12 $ret = $hash->{''};
358 13 100       19 $ret = &$ret( $str ) if $ret;
359 13 100       27 return $ret if $ret;
360              
361 11 50       16 if( $list ) {
362 11         22 for my $type ( 0..@simple+4 ) { # Try to find a more and more fuzzy match.
363 23 100       55 my $expr = $type < @simple ?
364             sprintf $simple[$type], $str :
365             regexp $str, $type - @simple;
366 23 100       32 next unless defined $expr;
367 21         568 my @res = grep /$expr/i, @$list;
368 21 100       39 if( @res ) {
369 10 50       31 return $res[0] if @res == 1;
370 0         0 warn "$prefix$str$suffix matches @res\n";
371 0         0 $error = 1;
372 0         0 return '';
373             }
374             }
375             }
376             # no special syntax for fields or functions, so don't fail on real one
377 1 50 33     22 return $str if ord $prefix == ord '.' or ord $suffix == ord '(';
378              
379 0         0 warn "$prefix$str$suffix doesn't match\n";
380 0         0 $error = 1;
381             }
382              
383             my %rq = ('[', ']',
384             '{', '}');
385             my $quote_re = qr(\\([^\W\d_]*)([-,:;./ #?ω^\\\@!'"`[\]{}]*)(?:%(.+?))?); # $1: name, $2: spec, $3: join
386             sub quote($$$$) {
387 40     40 0 40 local $_ = $_[1];
388 40         58 my( $named, undef, $join, @args ) = @_;
389 40         59 while( $named ) {
390 5         6 my $quotes = $Quotes{$named};
391 5 50 33     76 if( !defined $quotes or "\\$quotes" !~ /^$quote_re$/o ) {
392 0 0       0 warn $quotes ? "\\$named is bad '$quotes' in \%Quotes\n" : "\\$named not found in \%Quotes\n";
393 0         0 $error = 1;
394 0         0 return '';
395             }
396 5         5 $named = $1;
397 5         6 substr $_, 0, 0, $2;
398 5   100     17 $join //= $3;
399             }
400 40   100     87 $join //= ',';
401 40         35 my $list = ref $args[0];
402 40 100       50 @args = @{$args[0]} if $list;
  18         26  
403 40 100       101 return join $join, @args unless defined;
404 30         31 /(['"`[{])/;
405 30         36 my( $lq ) = /(['"`[{])/;
406 30   100     59 $lq ||= "'";
407 30   66     65 my $rq = $rq{$lq} || $lq;
408 30         68 my( $noquote, $number, $boolean, $null, $space, $var ) =
409 4     4   7483 (tr/!//, tr/#//, tr/?//, tr/ω^//, tr/\\//, tr/@//);
  4         8  
  4         35  
410 30 50       41 my $split = tr/-// ? '-' : ''; # avoid range by putting - 1st
411 30 100       39 $split .= tr/ // ? '\s' : ''; # space means any whitespace
412 30         45 $split .= join '', /([,:;.\/])/g;
413 30 100 100     64 $split ||= ',' unless $list;
414 30 100 66     227 $split &&= $space ? qr/[$split]/ : qr/\s*[$split]\s*/;
415             join $join, map {
416 80 100 33     456 if( $noquote || $boolean && /^(?:true|false)$/i || $null && /^null$/i || $var && /^\@\w+$/ ) {
    100 66        
      100        
      33        
      33        
      66        
      100        
417 13         43 $_;
418             } elsif( $number && /^[-+]?(?:0b[01]+|0x[\da-f]+|(?=\.?\d)\d*\.?\d*(?:e[-+]?\d+)?)$/i ) {
419 9         17 $_;
420             } else {
421 58         131 s/$rq/$rq$rq/g;
422 58         200 "$lq$_$rq";
423             }
424             } map {
425 30 100       33 unless( $space ) {
  50         61  
426 49         87 s/\A\s*//;
427 49         111 s/\s*\Z//;
428             }
429 50 100       151 $split ? split $split, $_, -1 : $_;
430             } @args;
431             }
432              
433             sub convert_Query($$) {
434 15     15 0 20 my $name = $_[0];
435 15         24 my $res = find $name, '&', '', %Queries;
436 15         15 my $ref = ref $res;
437 15         9 my @arg;
438 15 100       44 for( $ref ? $_[1] : "$res\cA$_[1]" ) {
439 15         14 &convert_table_column;
440 15 100       41 ($res, $_) = split "\cA" unless $ref;
441 15         31 @arg = split ';';
442             }
443 15 100 50     22 return &$res( $name, @arg ) // '' if $ref;
444              
445 11         8 my( @var, %seen, @rest );
446 11         128 $res =~ s(\$$quote_re?(?:(\d+\b)|([*>_]))) { # $4: numbered arg, $5: special arg
447 27 100 100     81 if( $4 && $4 > @arg ) {
448 2         6 '';
449             } else {
450 25 100       86 push @var, [$1, $2, $3, $5 ? (undef, $5) : $4-1];
451 25 100       52 undef $seen{$4-1} if $4; # make it exist
452 25         84 "\cV$#var\cZ";
453             }
454             }eg;
455 11 100       21 if( @arg > keys %seen ) {
456 2         5 @rest = @arg;
457 2         6 undef $rest[$_] for keys %seen;
458 2         7 @rest = grep defined(), @rest;
459             }
460 11         25 $res =~ s(\cV(\d+)\cZ) {
461 25         17 my @res = @{$var[$1]};
  25         56  
462 25 50       65 quote $res[0], $res[1], $res[2],
    100          
    100          
    100          
463             $res[4] ? ($res[4] eq '*' ? \@arg :
464             $res[4] eq '>' ? \@rest :
465             $_) :
466             $res[3] < 0 ? $name : $arg[$res[3]];
467             }eg;
468 11         75 $res;
469             }
470              
471             my @keys_Table_Columns;
472             sub convert_table_column {
473 33 50   33 0 76 @keys_Table_Columns = keys %Table_Columns unless @keys_Table_Columns;
474 33 0       61 s&(?
  0 0       0  
    50          
475              
476 33 100       51 unless( $table_re ) {
477 2         6 $table_re = join '|', keys %Table_Columns;
478 2 50       11 $table_re = $table_re ? qr/\b(?:$table_re)\b/ : qr/\s\b\s/;
479             }
480 33 50       45 unless( $error ) {
481 33         26 my %column;
482 33         227 for( grep /$table_re/io, split /\W+/ ) {
483 0         0 undef $column{$_} for @{$Table_Columns{$_}};
  0         0  
484             }
485 33         54 my @column = keys %column;
486 33 50       57 s/(^|.&|[-+\s(,;|])?(?
  1 50       3  
    50          
487             }
488             }
489              
490              
491             =head2 convert
492              
493             This function takes a short-hand query in C<$_> and transforms it to SQL. See
494             L for more run time oriented features.
495              
496             =head3 C<:\I(I)> E or E C<:\I%I(I)>
497              
498             This is a special macro that transforms odd lists to SQL syntax. It takes a
499             list of unquoted strings and quotes each one of them in various ways. The
500             syntax is inspired by the Shell single character quote and Perl's C<\(...)>
501             syntax. The I is a combination of an optional I (set up with the
502             Perl function C), followed by an optional I that extends the named
503             spec. The I can get split on a variety of one or even simultaneously
504             various characters, which you can give in any order in the I:
505              
506             =over
507              
508             =item C<\> (backslash)
509              
510             This one isn't a character to split on, but rather prevents trimming the
511             whitespace of both ends of the resulting strings.
512              
513             =item C<,> (comma), the default
514              
515             You only need to specify it, if you want to split on commas, as well as other
516             characters, at the same time. Where the C<\> (backslash) syntax is also used,
517             when given a list, there is no default splitting of list members:
518             L
519             code>}>|/Perl-code-or-:-namespec-Perl-code-or-:-namespec-join-Perl-code> and
520             L C<$\*> &
521             C<$\E>. In that case you must specify it, if needed, e.g. C<$\,*>.
522              
523             =item C> (space)
524              
525             This one stands for any single whitespace. Since strings are normally
526             trimmed, it's the equivalent of what the Shell does. But, if you combine it
527             with C<\>, which prevents trimming, you will get an empty string between each
528             of multiple whitespaces.
529              
530             =item C<-> (minus)
531              
532             =item C<:> (colon)
533              
534             =item C<;> (semicolon)
535              
536             =item C<.> (period)
537              
538             =item C (slash)
539              
540             =back
541              
542             The I can also contain several of these characters to prevent certain
543             strings from being quoted:
544              
545             =over
546              
547             =item C<#> (hash)
548              
549             All numbers, including signed, floats, binary and hexadecimal stay literal.
550             If you use C<-> as a separator, there can be no negative numbers.
551              
552             =item C (question mark)
553              
554             The boolean values C and C stay literal.
555              
556             =item C<ω> (omega) E or E C<^> (caret)
557              
558             The value C stays literal. Note that C<ω> is also a word character,
559             that would be part of a name at the beginning of I. It is only the
560             C-symbol following some non-word character, e.g. C<,> (comma).
561              
562             =item C<@> (at sign)
563              
564             Variables C<@name> stay literal.
565              
566             =item C (exclamation mark)
567              
568             Everything, presumed valid sql syntax, stays literal.
569              
570             =back
571              
572             And you can give at most one I for the quotes to use:
573              
574             =over
575              
576             =item C<'> (quote), the default
577              
578             =item C<"> (double quote)
579              
580             =item C<`> (backquote)
581              
582             =item C<[]> (brackets)
583              
584             =item C<{}> (braces)
585              
586             In the latter two cases, the closing quotes are optional, for decoration only,
587             or if code completion adds them.
588              
589             =back
590              
591             The results are joined by comma, unless I is given. E.g. C<:\(a,b,
592             c,NULL,true,-1.2)> gives C<'a','b','c','NULL','true','-1.2'>, while
593             C<:\:"/\#?0(a:b/ c:NULL:true/-1.2)> gives C<"a","b"," c",NULL,true,-1.2> and
594             C<:\ !%&&(a b Ec)> gives C.
595              
596              
597              
598             =head3 C<:I>
599              
600             These are mostly simple text-replacements stored in C<%Macros>. Unlike
601             L|/namespec-strings-or-:-namespec-join-strings> these do not take arguments.
602             There are also some dynamic macros. Those starting with C<:j> (join) or
603             C<:lj> (left join) may continue into a L> E or E C<.I.> E or E C<.I.I> is a key of C<%Columns> or any abbreviation of columns of any table
604             without the leading C<#>. E.g. C<:ljtbl#t> might expand to C
605             t>.
606              
607             Those starting with C<:gb> (group by) or C<:ob> (order by) may be followed by
608             result columns numbers from 1-9, each optionally followed by a or d for asc or
609             desc. E.g. C<:ob2d3> gives C.
610              
611             =head3 C<:+I> E or E C<:I
612              
613             These are time calculation macros, where an optional leading letter indicates
614             a base time, and an optional trailing letter with an optional count means the
615             offset. The letters are:
616              
617             =over
618              
619             =item y
620              
621             (this) year(start). E.g. C<:y+2m> is march this year.
622              
623             =item q
624              
625             (this) quarter(start). E.g. C<:q+0> is this quarter, C<:q+q> is next quarter.
626              
627             =item m
628              
629             (this) month(start). E.g. C<:-3m> is whatever precedes, minus 3 months.
630              
631             =item w
632              
633             (this) week(start). E.g. C<:w+3d> is this week thursday (or wednesday if you
634             set C<$weekstart> to not follow ISO 8601 and the bible).
635              
636             =item d
637              
638             (this) day(start). E.g. C<:d-w> is midnight one week ago.
639              
640             =item h
641              
642             (this) hour(start). E.g. C<:h+30M> is half past current hour.
643              
644             =item M
645              
646             (this) minute(start). E.g. C<:+10M> is whatever precedes, plus 10min.
647              
648             =item s
649              
650             (this) second. E.g. C<:s-2h> is exactly 2h ago.
651              
652             =back
653              
654              
655             =head3 C<:{I}> E or E C<:\I{I}> E or E C<:\I%I{I}>
656              
657             This gets replaced by what it returns in list context. Undefined elements get
658             rendered as C and they all get joined by I or else C<,> (comma).
659             They can become quoted like
660             L|/namespec-strings-or-:-namespec-join-strings>. Since the result is
661             already a list, the elements don't get split by default. If you want that,
662             you must specify it as in C<:\,{I}>.
663              
664              
665             =head3 C<#I> E or E C<#I#> E or E C<#I#I>
666              
667             Here I is a key of C<%Tables> or any abbreviation of known tables in
668             C<@Tables>. If followed by C<#>, the abbreviation is used as an alias, unless
669             an I directly follows, in which case that is used.
670              
671              
672             =head3 C<.I
673              
674             Here I
675             recognized in the query. If followed by C<.>, the abbreviation is used as an
676             alias, unless an I directly follows, in which case that is used. It tries
677             to be clever about whether the 1st C<.> needs to be preserved, i.e. following
678             a table name.
679              
680             =head3 C(> E or E C\I(I)> E
681             or E C\I%I(I)>
682              
683             Here I is a key of C<%Functions> or any
684             abbreviation of known functions in C<@Functions>, which includes words
685             typically followed by an opening parenthesis, such as C for C.
686             C becomes C, whereas C becomes C.
687              
688             If the 2nd or 3rd form is used, the I inside of the parentheses are treated
689             just like C(I)|/namespec-strings-or-:-namespec-join-strings>>,
690             but in this case preserving the parentheses.
691              
692             If the 1st argument of a function is empty and the abbrev or function is found
693             in C<%DefaultArguments> the value becomes the 1st argument.
694             E.g. C or C both become
695             C.
696              
697             =head3 Abbreviated Keyword
698              
699             Finally it picks on the structure of the statement: These keywords can be
700             abbreviated: C, C, C or C. If none of
701             these or C is present, C
702             need to be recognized in the future).
703              
704             For C
705             optional if it starts with a table name) and C. If no result columns
706             are given, they default to C<*>, see L. For C, semicolons
707             are frst replaced by C and then C.
708              
709             =cut
710              
711             sub convert {
712 18     18 1 2269 my @strings; # extract strings to prevent following replacements inside.
713 18         32 until( $error ) {
714             # Handle :\...{perl}
715             next if
716 28 100 100     464 s(:$quote_re?$perl_re){
717 7   100     278 my @ret = map $_ // 'NULL', eval substr $4, 1, -1; # strip {}
718 7 50       18 $error = 1, warn $@ if $@;
719 7         12 quote $1, $2, $3, \@ret;
720             }ego
721              
722             # Handle function\...(str1, str2, str3) and :\...(str1, str2, str3)
723             or
724             s<(?:\b(\w+)|:)$quote_re\((.+?)\)> {
725 8 100       19 ($1 ? "$1(" : '') .
    100          
726             quote( $2, $3, $4, $5 ) .
727             ($1 ? ')' : '')
728             }ge;
729              
730             # extract quoted strings
731 26         91 while( /\G.*?(['"`[{])/gc ) {
732 76   66     143 my $rq = $rq{$1}||$1;
733 76         73 my $pos = pos;
734 76         396 while( /\G.*?([$rq\\])/gc ) {
735 92 100       338 if( $1 eq '\\' ) {
    100          
736 5         18 ++pos; # skip next
737             } elsif( ! /\G$rq/gc ) { # skip doubled quote
738 76         210 push @strings,
739             substr $_, $pos - 1, 1 - $pos + pos, # get string
740             "\cS".@strings."\cZ"; # and replace with counter
741 76         329 last;
742             }
743             }
744             }
745              
746             # \todo (?(?<=\w)\b)
747             next if
748 26 100       110 s&:($timespec_re[+-]\d*$timespec_re(?(?<=\w)\b)|l?j\w+(?:#(\w*))|\w+)&find $1, ':', '', %Macros&ego;
  23         33  
749 18         17 last;
750             }
751              
752 18 50       47 my $was_column = /^\./ or # Avoid next assumption when 1st column name is also a table name.
753             s&^(?=#)&;&; # Assume empty fieldlist before table name
754 18         22 &convert_table_column;
755 18 50       95 s&^(?=$table_re)&;& unless $was_column; # Assume empty fieldlist before table name
756              
757 18 50 33     62 s&\b(\w+)\((?=\s*([,)])?)&my $fn = find $1, '', '(', %Functions, @Functions; ($fn || $1).'('.($2 and $DefaultArguments{$1} || $DefaultArguments{$fn} or '')&eg unless $error;
  13   50     21  
  13         116  
758             #s&\b(\w+)(?=\()&find $1, '', '(', %Functions, @Functions or $1&eg unless $error;
759              
760 18 50       24 return if $error;
761 18         24 s/\A\s*;/*;/;
762 18         24 s/;\s*\Z//;
763 18 50       34 if( s/^upd(?:a(?:t(?:e)?)?)?\b/update/i ) {
764 0 0       0 s/(?
765             } else {
766 18         55 s/(?
767 18 50 33     112 s/^ins(?:e(?:r(?:t)?)?)?\b/insert/i ||
768             s/^del(?:e(?:t(?:e)?)?)?\b/delete/i ||
769             s/^(?!se(?:lec)?t)/select /i;
770             }
771              
772 18         27 s/ $//mg;
773 18         34 s/ {2,}/ /g;
774 18         110 s/\cS(\d+)\cZ/$strings[$1]/g; # put back the strings
775              
776 18         46 1;
777             }
778              
779              
780             # escape map for special replacement characters
781             my %esc = map { $_ eq 'v' ? "\013" : eval( qq!"\\$_"! ), "\\$_" } qw'0 a b e f n r t v \ "';
782              
783             # With an argument of total number of rows, init output counting and return undef if it is to be skipped (not stdout).
784             # Without an argument, do the counting and return undef if no more rows wanted.
785             {
786             my( $total, $cnt, $i );
787             sub count(;$) {
788 16 100   16 0 27 if( @_ ) {
789 11         9 $total = $_[0];
790 11         9 $cnt = 0;
791 11         9 $i = 100;
792 11 100       38 return select eq 'main::STDOUT' ? 1 : undef;
793             }
794 5         4 ++$cnt;
795 5 50 33     11 if( --$i <= 0 && $cnt < $total ) {
796 0         0 printf STDERR "How many more, * for all, or q to quit? (%d of %d) [default: 100] ",
797             $cnt, $total;
798 0         0 $i = <>;
799 0 0       0 if( defined $i ) {
800 0         0 $i =~ tr/qQxX \t\n\r/0000/d;
801 0 0       0 $i = (0 == length $i) ? 100 :
    0          
    0          
802             $i eq '*' ? ~0 :
803             $i == 0 ? return :
804             $i;
805             } else {
806 0         0 print "\n";
807 0         0 return;
808             }
809             }
810 5         15 1;
811             }
812             }
813              
814             sub render_csv($;$$) {
815 6     6 0 1202 my( $sth, $filter ) = @_;
816             my( $semi, $tab ) =
817             (exists $_[2]{semi},
818             exists $_[2]{tab})
819 6 100       18 if $_[2];
820 6         6 my $name = $sth->{NAME};
821 6         14 my @row = @$name;
822 6         4 while() {
823 18         22 for( @row ) {
824 162 100       141 if( defined ) {
825 156 100 100     793 $_ = qq!"$_"! if
    100 66        
    100 66        
826             /\A\Z/ or
827             s/"/""/g or
828             $semi ? tr/;\n// : $tab ? tr/\t\n// : tr/,\n// or
829             /\A=/;
830             } else {
831 6         7 $_ = '';
832             }
833 162         190 utf8::decode $_;
834             }
835 18 100       57 print join( $semi ? ';' : $tab ? "\t" : ',', @row ) . "\n";
    100          
836              
837 21 100       29 FETCH:
838             @row = $sth->fetchrow_array
839             or last;
840 15 100 100     241 $filter->( $name, @row ) or goto FETCH if $filter;
841             }
842             }
843              
844             our $NULL = 'ω';
845             utf8::decode $NULL;
846             my( $r1, $r2, $r3, $r5 ) = ('[01]\d', '[0-2]\d', '[0-3]\d', '[0-5]\d');
847             sub render_table($;$$) {
848 17     17 0 3929 my( $sth, $filter ) = @_;
849             my( $null, $crlf, $date, $time ) =
850             exists $_[2]{all} ?
851             ('NULL', 1, 1, 1) :
852             (exists $_[2]{NULL} ? 'NULL' : exists $_[2]{null} ? 'null' : 0,
853             exists $_[2]{crlf},
854             exists $_[2]{date},
855             exists $_[2]{time})
856 17 100       72 if $_[2];
    50          
    100          
    100          
857 17   66     48 $null ||= $NULL;
858 17         28 my @name = @{$sth->{NAME}};
  17         39  
859 17         34 my @len = (0) x @name;
860 17         14 my( @txt, @res, @comp );
861 17         27 while( my @res1 = $sth->fetchrow_array ) {
862 80 100 50     417 $filter->( \@name, @res1 ) or next if $filter;
863 80         93 for my $i ( 0..$#res1 ) {
864 580 100       1431 if( !defined $res1[$i] ) {
    100          
865 4         6 $res1[$i] = $null;
866             } elsif( $res1[$i] !~ /^\d+(?:\.\d+)?$/ ) {
867 537         329 $txt[$i] = 1;
868 537 100       642 $res1[$i] =~ s/\r\n/\\R/g unless $crlf;
869 537         433 $res1[$i] =~ s/([\t\n\r])/$esc{$1}/g;
870 4     4   22614 no warnings 'uninitialized';
  4         7  
  4         12238  
871 537 100       525 unless( $date ) {
872 215 100       633 if( $res1[$i] =~ s/^(\d{4}-)($r1)-0[01]([T ]$r2:$r5(?::$r5(?:[.,]\d{3})?)?(?:Z|[+-]$r2:$r5)?)?$/$1/o ) {
873 109 100       191 $res1[$i] .= "$2-" if $2 > 1;
874 109 100       193 $res1[$i] .= $3 if $3;
875             }
876             }
877 537 100       539 unless( $time ) {
878 215 100       847 if( $res1[$i] =~ s/^(\d{4}-(?:$r1-(?:$r3)?)?[T ])?($r2):($r5)(?::($r5)(?:([.,])(\d{3}))?)?(Z|[+-]$r2:$r5)?$/$1/o ) {
879 170   100     295 $res1[$i] = $1 || '';
880 170 100 66     750 if( $2 == 23 && $3 == 59 && ($4 // 59) == 59 && ($6 // 999) == 999 ) {
    100 100        
    100 66        
    100 100        
      33        
881 49         46 $res1[$i] .= "24:";
882             } elsif( $6 > 0 ) {
883 24         46 $res1[$i] .= "$2:$3:$4$5$6";
884             } elsif( $4 > 0 ) {
885 12         17 $res1[$i] .= "$2:$3:$4";
886             } elsif( $3 > 0 ) {
887 12         14 $res1[$i] .= "$2:$3";
888             } else {
889 73         78 $res1[$i] .= "$2:";
890             }
891 170 100       312 ($res1[$i] .= $7) =~ s/:00$/:/
892             if $7;
893             }
894             }
895 537         611 utf8::decode $res1[$i];
896             }
897 580 100       681 $txt[$i] = 0 if @txt < $i;
898 580         392 my $len = length $res1[$i];
899 580 100       764 $len[$i] = $len if $len[$i] < $len;
900             }
901 80 100       86 if( @comp ) {
902 71         80 for my $i ( 0..$#comp ) {
903 509 100 100     791 undef $comp[$i] if defined $comp[$i] && $comp[$i] ne $res1[$i];
904             }
905             } else {
906 9         19 @comp = @res1;
907             }
908 80         176 push @res, \@res1;
909             }
910 17 100       87 if( @res ) {
911 9 100       19 @comp = () if @res == 1;
912 9         10 my $fmt = '';
913 9         17 for( my $i = 0; $i < @name; ++$i ) {
914 71         57 $name[$i] =~ s/\r\n/\\R/g;
915 71         65 $name[$i] =~ s/([\t\n\r])/$esc{$1}/g;
916 71 50       75 if( defined $comp[$i] ) {
917 0         0 my $more;
918 0         0 while( defined $comp[$i] ) {
919 0 0       0 printf $fmt, @name[0..$i-1] unless $more;
920 0         0 $more = 1;
921 0         0 printf "[%s=%s]", $name[$i], $comp[$i];
922 0         0 @name[0..$i] = ('') x ($i+1);
923 0         0 for my $row ( \@comp, \@name, \@len, \@txt, @res ) {
924 0         0 splice @$row, $i, 1;
925             }
926             }
927 0         0 print "\n";
928 0         0 --$i, next;
929             }
930 71 100       79 if( $len[$i] < length $name[$i] ) {
931 9         42 printf "$fmt%s\n", @name[0..$i];
932 9         25 @name[0..$i] = ('') x ($i+1);
933             }
934 71 100       145 $fmt .= '%' . ($txt[$i] ? -$len[$i] : $len[$i]) . 's|';
935             }
936 9         10 $fmt .= "\n";
937 9 100       44 printf $fmt, @name if $name[-1];
938 9         66 printf $fmt, map '-'x$_, @len;
939 9         21 my $count = count @res; # init
940 9         12 for my $row ( @res ) {
941 80         197 printf $fmt, @$row;
942 80 100 50     174 defined count or last if defined $count;
943             }
944             }
945             }
946              
947             my $yaml_re = join '', sort keys %esc;
948             $yaml_re =~ s!\\!\\\\!;
949             my $tabsize = $ENV{TABSIZE} || 8;
950             sub render_yaml($;$$) {
951 2     2 0 396 my( $sth, $filter ) = @_;
952 2         2 my @label; # Fill on 0th round with same transformation as data (but \n inline)
953 2   100     4 my $count = count $DBI::rows || 1; # init \todo don't know how many unfiltered
954 2         3 my @row = @{$sth->{NAME}};
  2         7  
955 2         2 while() {
956 6         3 local $_;
957 6         6 my $i = 0;
958 6         9 for( @row ) {
959 54 100 66     216 if( !defined ) {
    100          
    50          
    100          
    100          
960 2         2 $_ = '~';
961             } elsif( /^(?:y(?:es)?|no?|true|false|o(?:n|ff)|-?\.inf|\.nan)$/s ) { # can only be string in Perl or DB
962 0         0 $_ = "'$_'";
963             } elsif( tr/][{},?:`'"|<>&*!%#@=~\0-\010\013-\037\177-\237-// or @label ? 0 : tr/\n// ) {
964 15         48 s/([$yaml_re])/$esc{$1}/go;
965 15         16 s/([\0-\010\013-\037\177-\237])/sprintf "\\x%02x", ord $1/ge;
  0         0  
966 15         18 $_ = qq!"$_"!;
967             } elsif( tr/\n// ) {
968 1         2 my $nl = chomp;
969 1         3 s/^/ /mg;
970 1 50       3 substr $_, 0, 0, $nl ? "|2\n" : "|2-\n";
971             }
972 54 100       117 print "$label[$i++]$_\n" if @label;
973             }
974 6 100       9 if( @label ) {
975 4 50 50     11 defined count or last if defined $count;
976             } else {
977 2         3 my $maxlen = 0;
978 2         3 for( @row ) {
979 18 100       25 substr $_, 0, 0, $maxlen ? ' ' : '- '; # 1st field if no maxlen yet
980 18         12 my $length = 0;
981 18 100       73 $length += $1 ? $tabsize - $length % $tabsize : length $2
982             while /\G(?:(\t)|([^\t]+))/gc;
983 18         22 $_ .= ": $length";
984 18 100       28 $maxlen = $length if $maxlen < $length;
985             }
986 18         57 s/(\d+)\Z/' ' x ($maxlen - $1)/e
987 2         13 for @label = @row;
988             }
989 7 100       17 FETCH:
990             @row = $sth->fetchrow_array
991             or last;
992 5 100 100     72 $filter->( $sth->{NAME}, @row ) or goto FETCH if $filter;
993             }
994             }
995              
996             sub render__create($;$$) {
997 0 0   0 0 0 my @row = $_[0]->fetchrow_array
998             or return;
999 0 0       0 my $col = $_[0]{NAME}[0] =~ /Function|Procedure/i ? 2 : 1;
1000 0 0       0 if( @row > $col ) {
1001 0         0 $col = $row[$col];
1002 0 0       0 $col =~ s/,/,\n/g if $_[0]{NAME}[0] =~ /View/i;
1003 0         0 say $col;
1004             }
1005             }
1006              
1007              
1008              
1009             my $lasttime = time;
1010             sub run($;$\%) {
1011 13     13 0 16 my( $sql, $filter, $opt ) = @_;
1012 13         35 my $t0 = [gettimeofday];
1013 13 50 33     55 if( $DBI::err || $t0->[0] - $lasttime > 3600 and !$dbh->ping ) {
      33        
1014 0         0 printf STDOUT "Inactive for %ds, ping failed after %.03fs, your session variables are lost.\n",
1015             $t0->[0] - $lasttime, tv_interval $t0;
1016             #$dbh->disconnect;
1017 0         0 $dbh = $dbh->clone; # reconnect
1018 0         0 $t0 = [gettimeofday];
1019             }
1020 13         9 $lasttime = $t0->[0];
1021 13 50       70 if( my $sth = UNIVERSAL::isa( $sql, 'DBI::st' ) ? $sql : $dbh->prepare( $sql )) {
    50          
1022 13         143 my $t1 = [gettimeofday];
1023 13         24 $sth->execute;
1024 13         33 printf STDOUT "prepare: %.03fs execute: %.03fs rows: %d\n",
1025             tv_interval( $t0, $t1 ), tv_interval( $t1 ), $DBI::rows;
1026 13 50       297 if( $sth->{Active} ) {
1027 13 100       16 if( $render ) {
1028 4         8 &$render( $sth, $filter, $opt );
1029             } else {
1030 9         17 render_table $sth, $filter, $opt;
1031             }
1032             }
1033             }
1034             }
1035              
1036              
1037             =head2 shell
1038              
1039             This function reads, converts and (if C<$dbh> is set) runs in an end-less loop
1040             (i.e. till end of file or C<^D>). Reading is a single line affair, unless you
1041             request otherwise. This can happen either, as in Unix Shell, by using
1042             continuation lines as long as you put a backslash at the end of your lines.
1043             Or there is a special case, if the 1st line starts with C<\\>, then everything
1044             up to C<\\> at the end of one of the next lines, constitutes one entry.
1045              
1046             In addition to converting, it offers a few extra features, performed in this
1047             order (i.e. C<&I> can return C/=I> etc.):
1048              
1049             =head3 C<&{I} I>
1050              
1051             Run I. It sees the optional I in C<$_> and may
1052             modify it. If it returns C, this statement is skipped. If it returns
1053             a DBI statement handle, run that instead of this statement. Else replace with
1054             what it returns.
1055              
1056             Reprocess result as a shell entry (i.e. it may return another C<&I>).
1057              
1058             =head3 C<&I; ...> E or E C<&I( I; ... ) I>
1059              
1060             These allow canned entries and are more complex than macros, in that they take
1061             arguments and replacement can depend on the argument.
1062              
1063             Reprocess result as a shell entry (i.e. it may return another C<&I>).
1064              
1065             You can define your own canned queries with:
1066              
1067             C< &{ Query I =E 'I', 'I' }>
1068              
1069             Here C becomes the replacement string for C<&name>. It may contain
1070             arguments a bit like the Shell: C<$0> (I), C<$*> (all arguments), C<$1,
1071             $2, ..., $10, ...> (individual arguments) and C<$E> (all arguments not
1072             adressed individually). They can become quoted like
1073             L|/namespec-strings-or-:-namespec-join-strings> as
1074             C<$\II> or C<$\I%II>. Here I is
1075             C<*>, C> or a number directly tacked on to I or I. E.g.:
1076             C<$\-"1> splits the 1st (semi-colon separated from the 2nd) argument itself on
1077             C<-> (minus), quotes the pieces with C<"> (double quote) and joins them with
1078             C<,> (comma). The two list variables C<$\*> & C<$\E> don't get split
1079             individually unless explicitly specified, e.g. C<$\,*>. Putting the quotes
1080             inside the argument like this, eliminates them, if no argument is given.
1081              
1082             =head3 C/ I> E or E C/i I> E or E C/ I> E or E C/i I>
1083              
1084             This will treat the I normally, but will join each output row into
1085             a C<~> (tilde) separated string for matching. NULL fields are rendered as
1086             that string. E.g. to return only rows starting with a number 1-5, followed by
1087             a NULL field, you could write: C.
1088              
1089             With a suffix C, matching becomes case insensitive. This is why the mostly
1090             optional space before I is shown above. Without an C, but if
1091             the statement starts with the word C (e.g. your first column name), you
1092             must separate it with a space. With an C, if the statement starts with an
1093             alphanumeric caracter, you must separate it with a space.
1094              
1095             Only matching rows are considered unless there is a preceding C
1096             (exclamation mark), in which case only non-matching rows are considered.
1097              
1098             You can provide your own formatting of the row by setting C<$regexp_fail> to a
1099             Perl sub that returns a Perl expression as a string. That expression takes
1100             the row in C<@_> and shall be true if the row fails to match.
1101              
1102             Caveat: the whole result set of the I gets generated and
1103             transferred to the client. This is definitely much more expensive than doing
1104             the equivalent filtering in the where clause. But it is not a big deal for
1105             tens or maybe hundreds of thousands or rows, probably still faster than
1106             writing the corresponding SQL. And Perl's regexps are so much more powerful.
1107              
1108             =head3 C<{I}I>
1109              
1110             Call I for every output row returned by the I with the
1111             array of column names as zeroth argument and the values after that (i.e.
1112             numbered from 1 like in SQL). It may modify individual values. If it returns
1113             false, the row is skipped.
1114              
1115             You may combine S/{I}>> in any order and as many of them as
1116             you want.
1117              
1118             The same caveat as for regexps applies here. But again Perl is far more
1119             powerful than any SQL functions.
1120              
1121             =head3 C<=I>
1122              
1123             A preceding C<=> prevents conversion, useful for hitherto untreated keywords
1124             or where the conversion doesn't play well with your intention.
1125              
1126             =head3 C
1127              
1128             Help prefix. Alone it will give an overview. You can follow up with any of
1129             the special syntaxes, with or without an abbreviation. E.g. C will show
1130             all function abbreviations, whereas C(> will show only those functions
1131             matching I or C> only those tables matching I.
1132              
1133             =head3 C>
1134              
1135             Will convert and show, but not perform I. If C<$dbh> is not set, this
1136             is the default behaviour.
1137              
1138             =head3 C>
1139              
1140             Run I.
1141              
1142             =head3 CI> E or E CEI>
1143              
1144             Redirect or append next statement's output to I. For known
1145             suffixes and options, see the L.
1146              
1147             =head3 C<|I>
1148              
1149             Pipe next statement's output through I.
1150              
1151             =head2 Output Formats
1152              
1153             The output format for the next SQL statement that is run, is chosen from the
1154             suffix of a redirection or a special suffix query. In both cases
1155             comma-separated options may be passed:
1156              
1157             =over
1158              
1159             =item >I.I
1160              
1161             =item >I.I( I; ... )
1162              
1163             =item >>I.I
1164              
1165             =item >>I.I( I; ... )
1166              
1167             =item &.I; ...
1168              
1169             =item &.I( I; ... ) following text
1170              
1171             =back
1172              
1173             The known suffixes and their respective options are:
1174              
1175             =over
1176              
1177             =item C<.csv>
1178              
1179             This writes Comma Separated Values with one subtle trick: NULL and empty
1180             strings are distinguished by quoting the latter. Some tools like Perl's file
1181             DB L or rather its underlying L can pick that up. CSV
1182             can take one of these options:
1183              
1184             =over
1185              
1186             =item semi
1187              
1188             Use a semicolon as a separator. This is a common format in environments where
1189             the comma is the decimal separator. However if you want decimal commas, you must
1190             provide such formatting yourself.
1191              
1192             =item tab
1193              
1194             Use tabulators as column separators. Apart from that you get the full CSV
1195             formatting, so this is not the primitive F<.tsv> format some tools may have.
1196              
1197             =back
1198              
1199              
1200             =item C<.table>
1201              
1202             This is the default table format. But you need to name it, if you want to set
1203             options.
1204              
1205             =over
1206              
1207             =item all
1208              
1209             This is a shorthand for outputting everything in the long form, equivalent to
1210             C<( NULL, crlf, date )>.
1211              
1212             =item crlf
1213              
1214             Do not shorten C<\r\n> to C<\R>.
1215              
1216             =item date
1217              
1218             Output ISO dates fully instead of shortening 0000-00-00 to 0000- and
1219             yyyy-01-01 to yyyy- or yyyy-mm-01 to yyyy-mm-.
1220              
1221             =item time
1222              
1223             Output times fully instead of shortening 23:59(:59) to 24: and hh:00(:00) to
1224             hh: or hh:mm(:00) to hh:mm.
1225              
1226             =item NULL
1227              
1228             =item null
1229              
1230             Output this keyword instead of the shorter C<ω> from DB theory (or whatever
1231             you assigned to C<$NULL>).
1232              
1233             =back
1234              
1235              
1236             =item C<.yaml>
1237              
1238             =item C<.yml>
1239              
1240             Format output as YAML. This format has no options. Because its every value
1241             on a new line format can be more readable, there is a shorthand query C<&->
1242             for it.
1243              
1244             =back
1245              
1246             =cut
1247              
1248             our $prompt = 'steno> ';
1249             our $contprompt = '...> ';
1250             our $echo;
1251             # Called for every leading re, 1st arg is the optional '!', 2nd arg '/re/' or '/re/i'. Expression shall be true for non-matching lines.
1252             our $regexp_fail = sub($$) { 'join( "~", map ref() ? () : $_ // q!NULL!, @_ )' . ($_[0] ? '=~' : '!~') . $_[1] };
1253             sub shell() {
1254 1     1 1 815 print STDERR $prompt;
1255 1         2 my $fh;
1256 1         18 while( <> ) {
1257 22         17 undef $error;
1258 22 100       64 goto NEXT unless /\S/;
1259 20 100       43 if( s/^\s*\\\\\s*// ) {
1260 1         6 s/\s*\Z/\n/s;
1261 1         5 local $/ = "\\\\\n"; # leading \n gets chopped below
1262 1         2 $_ .= <>;
1263 1         3 chomp;
1264             } else {
1265 19         57 while( s/(?
1266 1         1 print STDERR $contprompt;
1267 1         4 $_ .= <>;
1268             }
1269 19         27 s/\A\s+//;
1270             }
1271 20         72 s/\s+\Z//;
1272 20 50       58 say if $echo;
1273 20         25 until( $error ) {
1274 36 100       134 if( s!^&$perl_re!! ) {
1275 3         191 my $perl = eval $1;
1276 3         12 local $| = 1; # flush to avoid stderr prompt overtaking last output line.
1277 3 50       8 warn $@ if $@;
1278 3 50       15 if( UNIVERSAL::isa $perl, 'DBI::st' ) {
    100          
1279 0         0 $_ = $perl;
1280 0         0 goto RUN;
1281             } elsif( defined $perl ) {
1282 1         7 substr $_, 0, 0, $perl;
1283             } else {
1284 2         13 goto NEXT;
1285             }
1286             } else {
1287             last unless
1288 33 100 100     136 s!^&(\.?\w+|-)(\(((?:(?>[^()]+)|(?2))*)\))!convert_Query $1, $3!e
  3         7  
1289 12         20 or s!^&(\.?\w+|-) *(.*)!convert_Query $1, $2!e;
1290             }
1291             }
1292              
1293 18         19 my $filter = '';
1294 18   100     134 while( s/^\s*$perl_re// || s%^\s*(!?)(/.+?/(?:i\b)?)\s*%% ) {
1295 9 100       15 if( defined $2 ) {
1296 3         6 $filter .= 'return if ' . $regexp_fail->( $1, $2 ) . ";\n";
1297             } else {
1298 6         39 $filter .= "return unless eval $1;\n";
1299             }
1300             }
1301 18 100       25 if( $filter ) {
1302 7         631 $filter = eval "sub {\n$filter 1; }";
1303 7 50       15 warn $@ if $@;
1304             }
1305 18 100       60 goto RUN if s/^\s*=//; # run literally
1306              
1307 13         13 my $skip = 0;
1308 13 100       24 if( /^\s*\?\s*(?:([?&#.:\\])(\w*)|(\w*)\()?/ ) { # help
1309 3 50 33     15 if( $1 && $1 eq '?' ) {
1310 0         0 s/^\s*\?\s*\?//;
1311 0         0 $skip = 1;
1312             } else {
1313 3         9 help( $1, $2, $3 );
1314 3         13 goto NEXT;
1315             }
1316             }
1317 10 50       16 if( s/^\s*!// ) {
1318 0         0 system $_;
1319 0 0       0 if( $? == -1 ) {
    0          
1320 0         0 print STDERR "failed to execute: $!\n";
1321             } elsif( my $exit = $? & 0b111_1111 ) {
1322 0 0       0 printf STDERR "child died with signal %d, with%s coredump\n",
1323             $exit, ($? & 0b1000_0000) ? '' : 'out';
1324             } else {
1325 0         0 printf STDERR "child exited with value %d\n", $? >> 8;
1326             }
1327 0         0 goto NEXT;
1328             }
1329 10         29 s/^\s*()//; # dummy because $1 survives loop iterations :-o
1330 10 50       31 if( /\A(>{1,2})\s*(.+?(\.\w+)?)(?:\((.*)\))?\s*\Z/ ) { # redirect output
    50          
1331 0 0       0 set_render $3, $4 ? split ';', $4 : () if $3;
    0          
1332 0         0 open $fh, "$1:utf8", (glob $2)[0];
1333 0         0 select $fh;
1334 0         0 goto NEXT;
1335             } elsif( /\A\|(.+)\Z/ ) { # pipe output
1336 0         0 open $fh, '|-:utf8', $1;
1337 0         0 select $fh;
1338 0         0 goto NEXT;
1339             }
1340              
1341 10         8 undef $error;
1342              
1343 10 100 66     21 goto NEXT unless $_ && &convert;
1344              
1345 8         28 print STDOUT "$_;\n";
1346 8 50       14 goto NEXT if $skip;
1347              
1348 13 50       38 RUN:
1349             run $_, $filter, %opt if $dbh;
1350 13         55 ($render, %opt) = ();
1351 13 50       20 if( $fh ) {
1352 0         0 close;
1353 0         0 select STDOUT;
1354 0         0 undef $fh;
1355             }
1356             NEXT:
1357 22         157 print STDERR $prompt;
1358             }
1359 1         99 print STDERR "\n";
1360             }
1361              
1362              
1363              
1364             sub helphashalt(\%@) {
1365 0     0 0 0 my $hash = shift;
1366 0 0       0 if( @_ ) {
1367 0         0 my $ret = $hash->{''};
1368 0         0 print "for *ptr, *cr, *cp, ...:\n";
1369             printf "%-5s %s\n", $_, &$ret( $_ )
1370 0         0 for @_;
1371 0         0 print "\n";
1372             }
1373             $_ eq '' or printf "%-5s %s\n", $_, $hash->{$_}
1374 0   0     0 for sort keys %$hash;
1375             }
1376             sub helphash($$$\%;\@) {
1377             #my( $str, $prefix, $suffix, $hash, $list ) = @_;
1378 3 50   3 0 6 if( $_[0] ) {
1379 3         4 undef $error;
1380 3 50 33     4 $error or printf "%-7s %s\n", "$_[1]$_[0]$_[2]", $_ if $_ = &find;
1381             } else {
1382 0         0 my %hash = %{$_[3]};
  0         0  
1383 0 0       0 if( my $sub = delete $hash{''} ) {
1384 0         0 my @list = $sub->();
1385 0         0 for my $elt ( @list ) {
1386 0         0 $hash{$elt->[0]} = $sub->( my $name = $elt->[0] ) . ' ' . $elt->[1];
1387             }
1388             }
1389 0         0 chomp %hash;
1390             printf "%-7s %s\n", "$_[1]$_$_[2]", $hash{$_}
1391 0 0       0 for sort { lc( $a ) cmp lc( $b ) or $a cmp $b } keys %hash;
  0         0  
1392 0 0       0 return unless $_[4];
1393 0         0 my $i = 0;
1394 0 0       0 my @list = sort { lc( $a ) cmp lc( $b ) or $a cmp $b } @{$_[4]};
  0         0  
  0         0  
1395 0         0 while( @list ) {
1396 0 0       0 if( ($i += length $list[0]) < 80 ) {
1397 0         0 print ' ', shift @list;
1398             } else {
1399 0         0 $i = 0;
1400 0         0 print "\n";
1401             }
1402             }
1403 0 0       0 print "\n" if $i;
1404             }
1405             }
1406              
1407             sub help {
1408 3 50   3 0 19 if( defined $_[2] ) {
    50          
    50          
    50          
    100          
    50          
1409 0         0 helphash $_[2], '', '(', %Functions, @Functions;
1410             } elsif( !$_[0] ) {
1411 0         0 print <<\HELP;
1412             All entries are single line unless \\wrapped at 1st bol and last eol\\ or continued.\
1413             Queries have the form: {{!}/regexp/{i}}{=}query
1414             The query has lots of short-hands expanded, unless it is prefixed by the optional =.
1415             The fields joined with '~' are grepped if regexp is given, case-insensitively if i is given.
1416              
1417             ??query Only shows massaged query.
1418             !perl-code Runs perl-code.
1419             >file Next query's output to file. In csv or yaml format if filename has that suffix.
1420              
1421             Query has the form {select|update|insert|delete}{fieldlist};tablelist{;clause} or set ...
1422             'select' is prepended if none of these initial keywords.
1423             fieldlist defaults to '*', also if Query starts with '#'.
1424             ';' is alternately replaced by 'from' and 'where'.
1425              
1426             Abbreviations, more help with ?&{abbrev}, ?:{abbrev}, ?\{abbrev}, ?#{abbrev}, ?.{abbrev}, ?{abbrev}(
1427             &{Perl code}... # only at bol, if it returns undef then skip, else prepend to ...
1428             &query $1;$2;... # only at bol
1429             &query($1;$2;...)... # only at bol, only replace upto )
1430             :macro
1431             :\quote(arg,...) # split, quote & join (?\ alone needs trailing space, because \ at end continues)
1432             :{Perl code} # dynamic macro
1433             #table #table#t
1434             .column .column.c # for any table recognized in statement
1435             function(
1436              
1437             Characters \t\n\r get masked in output, \r\n as \R.
1438             Date or time 0000-00-00 -> 0000- 1970-01-01 -> 1970- 00:00:00 -> 00: 23:59:59 -> 24:
1439             HELP
1440             } elsif( $_[0] eq '#' ) {
1441 0 0       0 @keys_Table_Columns = keys %Table_Columns unless @keys_Table_Columns;
1442 0         0 helphash $_[1], '#', '', %Tables, @keys_Table_Columns;
1443             } elsif( $_[0] eq '.' ) {
1444 0 0       0 helphashalt %Columns, 'ptr' unless $_[1]; # \todo WIN@
1445 0 0 0     0 $error or print "$_\n" if
      0        
1446             $_[1] and $_ = find $_[1], '.', '', %Columns; # \todo, @column;
1447             } elsif( $_[0] eq '&' ) {
1448 2 50       5 print <<\HELP unless $_[1];
1449             &{ Query name => 'doc', 'query' } define query &name on the fly
1450             query may contain arguments a bit like the Shell: $1, $2, ..., $*
1451             they can become quoted: $\1, $\"2, $\`*, $\[3, $\{}>
1452             $* means all args; $> the remaining args after using up the numbered ones
1453             if it is quoted, each arg gets quoted, separated by a comma
1454             $?arg?arg-replacement?no-arg-replacement? 1st if $arg has a value
1455             HELP
1456 2         6 helphash $_[1], '&', '', %Queries_help;
1457             } elsif( $_[0] eq '\\' ) {
1458 1 50       3 print <<\HELP unless $_[1];
1459             :\namespec(arg,...) or func\namespec(arg,...) quotes args for you.
1460             &{ Quote name => 'doc', 'namespec' } define quote \name on the fly
1461             namespec may another name and/or any splitter chars (-,:;./ ),
1462             preventer chars (#?ω^\@!), quoting chars ('"`[]{}) and/or
1463             a string to join the results with after a %.
1464             HELP
1465 1         4 helphash $_[1], '\\', '', %Quotes_help;
1466             } else {
1467 0 0         print <<\HELP unless $_[1];
1468             :\(...) split arguments and quote in many ways
1469             HELP
1470 0           local $Tables{TBL} = 'TABLE';
1471 0           helphash $_[1], ':', '', %Macros;
1472             }
1473             }
1474              
1475             1;
1476              
1477             =head1 YOUR SCRIPT
1478              
1479             package SQL::Steno; # doesn't export yet, so get the functions easily
1480             use SQL::Steno;
1481             use DBI;
1482             our $dbh = DBI->connect( ... ); # preferably mysql, but other DBs should work (with limitations).
1483             # If you want #tbl and .col to work, (only) one of:
1484             init_from_query; # fast, defaults to mysql information_schema, for which you need read permission
1485             init; # slow, using DBI dbh methods.
1486             # Set any of the variables mentioned above to get you favourite abbreviations.
1487             shell;
1488              
1489             =head1 LICENSE
1490              
1491             This program is free software; you may redistribute it and/or modify it under
1492             the same terms as Perl itself.
1493              
1494             =head1 SEE ALSO
1495              
1496             L, L, L, L, L
1497              
1498             =head1 AUTHOR
1499              
1500             (C) 2015, 2016 by Daniel Pfeiffer .