File Coverage

blib/lib/SQL/Steno.pm
Criterion Covered Total %
statement 319 432 73.8
branch 232 336 69.0
condition 77 132 58.3
subroutine 22 25 88.0
pod 3 19 15.7
total 653 944 69.1


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