/ Check-in [25e99f3f]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Some experimental command line shell input/output enhancements.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | expShell
Files: files | file ages | folders
SHA1:25e99f3fe5e4c90e92554b8ac6cd6a83a8d01a6a
User & Date: mistachkin 2015-01-18 05:35:01
Context
2015-01-18
09:02
Modify the new shell test case to work on non-Windows platforms as well. check-in: f362c5d9 user: mistachkin tags: expShell
05:35
Some experimental command line shell input/output enhancements. check-in: 25e99f3f user: mistachkin tags: expShell
01:50
Set the command-line shell stdin to binary mode on windows. check-in: 80541e8b user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/shell.c.

742
743
744
745
746
747
748
















749
750
751
752
753
754
755
...
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
....
1711
1712
1713
1714
1715
1716
1717

1718
1719
1720
1721
1722
1723
1724
....
1871
1872
1873
1874
1875
1876
1877


1878
1879


1880

1881
1882

1883

1884
1885
1886
1887
1888
1889
1890
1891
1892
1893


1894
1895






1896
1897




1898
1899
1900
1901
1902
1903
1904
....
2512
2513
2514
2515
2516
2517
2518









2519
2520
2521
2522
2523
2524
2525
....
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
  UNUSED_PARAMETER(NotUsed);
  seenInterrupt++;
  if( seenInterrupt>2 ) exit(1);
  if( db ) sqlite3_interrupt(db);
}
#endif

















/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
static int shell_callback(
  void *pArg,
  int nArg,        /* Number of result columns */
................................................................................
        output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
        if(i<nArg-1) fprintf(p->out, "%s", p->colSeparator);
      }
      fprintf(p->out, "%s", p->rowSeparator);
      break;
    }
    case MODE_Csv: {
#if defined(WIN32) || defined(_WIN32)
      fflush(p->out);
      _setmode(_fileno(p->out), _O_BINARY);
#endif
      if( p->cnt++==0 && p->showHeader ){
        for(i=0; i<nArg; i++){
          output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
        }
        fprintf(p->out, "%s", p->rowSeparator);
      }
      if( nArg>0 ){
        for(i=0; i<nArg; i++){
          output_csv(p, azArg[i], i<nArg-1);
        }
        fprintf(p->out, "%s", p->rowSeparator);
      }
#if defined(WIN32) || defined(_WIN32)
      fflush(p->out);
      _setmode(_fileno(p->out), _O_TEXT);
#endif
      break;
    }
    case MODE_Insert: {
      p->cnt++;
      if( azArg==0 ) break;
      fprintf(p->out,"INSERT INTO %s VALUES(",p->zDestTable);
      for(i=0; i<nArg; i++){
................................................................................

/*
** Text of a help message
*/
static char zHelp[] =
  ".backup ?DB? FILE      Backup DB (default \"main\") to FILE\n"
  ".bail on|off           Stop after hitting an error.  Default OFF\n"

  ".clone NEWDB           Clone data into NEWDB from the existing database\n"
  ".databases             List names and files of attached databases\n"
  ".dump ?TABLE? ...      Dump the database in an SQL text format\n"
  "                         If TABLE specified, only dump tables matching\n"
  "                         LIKE pattern TABLE.\n"
  ".echo on|off           Turn command echo on or off\n"
  ".eqp on|off            Enable or disable automatic EXPLAIN QUERY PLAN\n"
................................................................................
                            writefileFunc, 0, 0);
  }
}

/*
** Do C-language style dequoting.
**


**    \t    -> tab
**    \n    -> newline


**    \r    -> carriage return

**    \"    -> "
**    \NNN  -> ascii character NNN in octal

**    \\    -> backslash

*/
static void resolve_backslashes(char *z){
  int i, j;
  char c;
  while( *z && *z!='\\' ) z++;
  for(i=j=0; (c = z[i])!=0; i++, j++){
    if( c=='\\' ){
      c = z[++i];
      if( c=='n' ){
        c = '\n';


      }else if( c=='t' ){
        c = '\t';






      }else if( c=='r' ){
        c = '\r';




      }else if( c=='\\' ){
        c = '\\';
      }else if( c>='0' && c<='7' ){
        c -= '0';
        if( z[i+1]>='0' && z[i+1]<='7' ){
          i++;
          c = (c<<3) + z[i] - '0';
................................................................................
    if( nArg==2 ){
      bail_on_error = booleanValue(azArg[1]);
    }else{
      fprintf(stderr, "Usage: .bail on|off\n");
      rc = 1;
    }
  }else










  /* The undocumented ".breakpoint" command causes a call to the no-op
  ** routine named test_breakpoint().
  */
  if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){
    test_breakpoint();
  }else
................................................................................
  if( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)!=0 ){
    fprintf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
            sqlite3_sourceid(), SQLITE_SOURCE_ID);
    exit(1);
  }
#endif
#if defined(WIN32) || defined(_WIN32)
  _setmode(0, _O_BINARY);
#endif
  Argv0 = argv[0];
  main_init(&data);
  stdin_is_interactive = isatty(0);

  /* Make sure we have a valid signal handler early, before anything
  ** else is done.







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
<
<
<












|
<
<
<







 







>







 







>
>


>
>

>

<
>

>








|
|
>
>


>
>
>
>
>
>


>
>
>
>







 







>
>
>
>
>
>
>
>
>







 







|







742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
...
920
921
922
923
924
925
926
927



928
929
930
931
932
933
934
935
936
937
938
939
940



941
942
943
944
945
946
947
....
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
....
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897

1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
....
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
....
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
  UNUSED_PARAMETER(NotUsed);
  seenInterrupt++;
  if( seenInterrupt>2 ) exit(1);
  if( db ) sqlite3_interrupt(db);
}
#endif

#if defined(WIN32) || defined(_WIN32)
/*
** This routine is used to adjust the file translation mode for the output
** file.  It is only used on Windows.
*/
static void enable_binary_output(
  ShellState *p,
  int enable
){
  fflush(p->out);
  _setmode(_fileno(p->out), enable ? _O_BINARY : _O_TEXT);
}
#else
#define enable_binary_output(p,e)
#endif

/*
** This is the callback routine that the shell
** invokes for each row of a query result.
*/
static int shell_callback(
  void *pArg,
  int nArg,        /* Number of result columns */
................................................................................
        output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
        if(i<nArg-1) fprintf(p->out, "%s", p->colSeparator);
      }
      fprintf(p->out, "%s", p->rowSeparator);
      break;
    }
    case MODE_Csv: {
      enable_binary_output(p, 1);



      if( p->cnt++==0 && p->showHeader ){
        for(i=0; i<nArg; i++){
          output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
        }
        fprintf(p->out, "%s", p->rowSeparator);
      }
      if( nArg>0 ){
        for(i=0; i<nArg; i++){
          output_csv(p, azArg[i], i<nArg-1);
        }
        fprintf(p->out, "%s", p->rowSeparator);
      }
      enable_binary_output(p, 0);



      break;
    }
    case MODE_Insert: {
      p->cnt++;
      if( azArg==0 ) break;
      fprintf(p->out,"INSERT INTO %s VALUES(",p->zDestTable);
      for(i=0; i<nArg; i++){
................................................................................

/*
** Text of a help message
*/
static char zHelp[] =
  ".backup ?DB? FILE      Backup DB (default \"main\") to FILE\n"
  ".bail on|off           Stop after hitting an error.  Default OFF\n"
  ".binary on|off         Turn binary output on or off.  Default OFF\n"
  ".clone NEWDB           Clone data into NEWDB from the existing database\n"
  ".databases             List names and files of attached databases\n"
  ".dump ?TABLE? ...      Dump the database in an SQL text format\n"
  "                         If TABLE specified, only dump tables matching\n"
  "                         LIKE pattern TABLE.\n"
  ".echo on|off           Turn command echo on or off\n"
  ".eqp on|off            Enable or disable automatic EXPLAIN QUERY PLAN\n"
................................................................................
                            writefileFunc, 0, 0);
  }
}

/*
** Do C-language style dequoting.
**
**    \a    -> alarm
**    \b    -> backspace
**    \t    -> tab
**    \n    -> newline
**    \v    -> vertical tab
**    \f    -> form feed
**    \r    -> carriage return
**    \s    -> space
**    \"    -> "

**    \'    -> '
**    \\    -> backslash
**    \NNN  -> ascii character NNN in octal
*/
static void resolve_backslashes(char *z){
  int i, j;
  char c;
  while( *z && *z!='\\' ) z++;
  for(i=j=0; (c = z[i])!=0; i++, j++){
    if( c=='\\' ){
      c = z[++i];
      if( c=='a' ){
        c = '\a';
      }else if( c=='b' ){
        c = '\b';
      }else if( c=='t' ){
        c = '\t';
      }else if( c=='n' ){
        c = '\n';
      }else if( c=='v' ){
        c = '\v';
      }else if( c=='f' ){
        c = '\f';
      }else if( c=='r' ){
        c = '\r';
      }else if( c=='"' ){
        c = '"';
      }else if( c=='\'' ){
        c = '\'';
      }else if( c=='\\' ){
        c = '\\';
      }else if( c>='0' && c<='7' ){
        c -= '0';
        if( z[i+1]>='0' && z[i+1]<='7' ){
          i++;
          c = (c<<3) + z[i] - '0';
................................................................................
    if( nArg==2 ){
      bail_on_error = booleanValue(azArg[1]);
    }else{
      fprintf(stderr, "Usage: .bail on|off\n");
      rc = 1;
    }
  }else

  if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){
    if( nArg==2 ){
      enable_binary_output(p, booleanValue(azArg[1]));
    }else{
      fprintf(stderr, "Usage: .binary on|off\n");
      rc = 1;
    }
  }else

  /* The undocumented ".breakpoint" command causes a call to the no-op
  ** routine named test_breakpoint().
  */
  if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){
    test_breakpoint();
  }else
................................................................................
  if( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)!=0 ){
    fprintf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
            sqlite3_sourceid(), SQLITE_SOURCE_ID);
    exit(1);
  }
#endif
#if defined(WIN32) || defined(_WIN32)
  _setmode(_fileno(stdin), _O_BINARY);
#endif
  Argv0 = argv[0];
  main_init(&data);
  stdin_is_interactive = isatty(0);

  /* Make sure we have a valid signal handler early, before anything
  ** else is done.

Changes to test/shell1.test.

810
811
812
813
814
815
816
817

































818
} {0 {"\""
"["
"]"
"\\{"
"\\}"
";"
"$"} 7}


































finish_test








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
} {0 {"\""
"["
"]"
"\\{"
"\\}"
";"
"$"} 7}

# Test using arbitrary byte data with the shell via standard input/output.
#
do_test shell1-5.0 {
  #
  # NOTE: Skip NUL byte because it appears to be incompatible with command
  #       shell argument parsing.
  #
  for {set i 1} {$i < 256} {incr i} {
    #
    # NOTE: Due to how the Tcl [exec] command works on Windows (i.e. where
    #       it treats command channels opened for it as textual ones), the
    #       carriage-return and end-of-file characters cannot be used here.
    #
    if {$tcl_platform(platform)=="windows" && ($i == 0x0D || $i == 0x1A)} {
      continue
    }
    set hex [format %02X $i]
    set char [subst \\x$hex]; set oldChar $char
    set char [string map [list \
        \a \\a \b \\b \t \\t \n \\n \v \\v \f \\f \r \\r \
        " " "\" \"" \" \\\" ' \"'\" \\ \\\\] $char]
    set x [catchcmdex test.db ".print $char\r\n"]
    set code [lindex $x 0]
    set res [lindex $x 1]
    if {$code ne "0"} {
      error "failed with error: $res"
    }
    if {$res ne "$oldChar\n"} {
      error "failed with byte $hex mismatch"
    }
  }
} {}

finish_test

Changes to test/tester.tcl.

661
662
663
664
665
666
667









668
669
670
671
672
673
674
675
676
677
























678
679
680
681
682
683
684
    }
  } else {
    puts " Omitted"
    omit_test $name "pattern mismatch" 0
  }
  flush stdout
}










proc catchcmd {db {cmd ""}} {
  global CLI
  set out [open cmds.txt w]
  puts $out $cmd
  close $out
  set line "exec $CLI $db < cmds.txt"
  set rc [catch { eval $line } msg]
  list $rc $msg
}

























proc filepath_normalize {p} {
  # test cases should be written to assume "unix"-like file paths
  if {$::tcl_platform(platform)!="unix"} {
    # lreverse*2 as a hack to remove any unneeded {} after the string map
    lreverse [lreverse [string map {\\ /} [regsub -nocase -all {[a-z]:[/\\]+} $p {/}]]]
  } {







>
>
>
>
>
>
>
>
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
    }
  } else {
    puts " Omitted"
    omit_test $name "pattern mismatch" 0
  }
  flush stdout
}

proc dumpbytes {s} {
  set r ""
  for {set i 0} {$i < [string length $s]} {incr i} {
    if {$i > 0} {append r " "}
    append r [format %02X [scan [string index $s $i] %c]]
  }
  return $r
}

proc catchcmd {db {cmd ""}} {
  global CLI
  set out [open cmds.txt w]
  puts $out $cmd
  close $out
  set line "exec $CLI $db < cmds.txt"
  set rc [catch { eval $line } msg]
  list $rc $msg
}

proc catchcmdex {db {cmd ""}} {
  global CLI
  set out [open cmds.txt w]
  fconfigure $out -encoding binary -translation binary
  puts -nonewline $out $cmd
  close $out
  set line "exec -keepnewline -- $CLI $db < cmds.txt"
  set chans [list stdin stdout stderr]
  foreach chan $chans {
    catch {
      set modes($chan) [fconfigure $chan]
      fconfigure $chan -encoding binary -translation binary -buffering none
    }
  }
  set rc [catch { eval $line } msg]
  foreach chan $chans {
    catch {
      eval fconfigure [list $chan] $modes($chan)
    }
  }
  # puts [dumpbytes $msg]
  list $rc $msg
}

proc filepath_normalize {p} {
  # test cases should be written to assume "unix"-like file paths
  if {$::tcl_platform(platform)!="unix"} {
    # lreverse*2 as a hack to remove any unneeded {} after the string map
    lreverse [lreverse [string map {\\ /} [regsub -nocase -all {[a-z]:[/\\]+} $p {/}]]]
  } {