std sh ga-infounused
[sunny256-utils.git] / sort-sqlite
blobc1e83aaa18eccbca6b0a627c1c5ba92942ff2fdd
1 #!/usr/bin/env perl
3 #=======================================================================
4 # sort-sqlite
5 # File ID: 0f3555b6-70fd-11e5-89be-fefdb24f8e10
7 # Sort the rows in an SQLite database.
9 # Character set: UTF-8
10 # ©opyleft 2015– Øyvind A. Holm <sunny@sunbase.org>
11 # License: GNU General Public License version 2 or later, see end of
12 # file for legal stuff.
13 #=======================================================================
15 use strict;
16 use warnings;
17 use Getopt::Long;
18 use File::Copy;
19 use IPC::Open3;
21 local $| = 1;
23 our %Opt = (
25 'help' => 0,
26 'quiet' => 0,
27 'verbose' => 0,
28 'version' => 0,
32 our $progname = $0;
33 $progname =~ s/^.*\/(.*?)$/$1/;
34 our $VERSION = '0.2.0';
36 my $ERROR = 0;
37 my $OK = 1;
39 my $SQLITE = "sqlite3";
41 my @Columns = ();
43 Getopt::Long::Configure('bundling');
44 GetOptions(
46 'column|c=s' => \@Columns,
47 'help|h' => \$Opt{'help'},
48 'quiet|q+' => \$Opt{'quiet'},
49 'verbose|v+' => \$Opt{'verbose'},
50 'version' => \$Opt{'version'},
52 ) || die("$progname: Option error. Use -h for help.\n");
54 $Opt{'verbose'} -= $Opt{'quiet'};
55 $Opt{'help'} && usage(0);
56 if ($Opt{'version'}) {
57 print_version();
58 exit(0);
61 my $sql_error = 0; # Is set to !0 if some sqlite3 error happened
63 exit(main());
65 sub main {
66 if (!scalar(@Columns)) {
67 warn("$progname: Missing -c/--column option\n");
68 return 1;
70 $ENV{'LC_COLLATE'} = 'C';
71 for my $file (@ARGV) {
72 sort_file($file, @Columns) || return 1;
74 return 0;
75 } # main()
77 sub sort_file {
78 # {{{
79 my ($file, @tabcol) = @_;
80 my $tmptable = "sortsqltmp";
81 my $retval = $OK;
82 my $c;
84 for $c (@tabcol) {
85 if ($c !~ /^(\S+?)\.(\S+)$/) {
86 warn("$progname: \"$c\" is not a valid identifier\n");
87 return $ERROR;
91 if (!-r $file || !-f $file) {
92 warn("$progname: $file: File is not readable by you or " .
93 "is not a regular file\n");
94 return $ERROR;
97 my $backup = sprintf("%s.%s.bck", $file, sec_to_string(mod_date($file)));
98 if (!copy($file, $backup)) {
99 warn("$progname: $backup: Cannot create backup file: $!\n");
100 return $ERROR;
103 msg(1, "Sorting $file");
104 for $c (@tabcol) {
105 my ($table, $column);
107 if ($c =~ /^(\S+?)\.(\S+)$/) {
108 ($table, $column) = ($1, $2);
109 } else {
110 warn("$progname: This should not happen, invalid identifier " .
111 "\"$c\" even though it has been validated\n");
112 return $ERROR;
114 sql($file, "
115 BEGIN EXCLUSIVE TRANSACTION;
117 CREATE TEMPORARY TABLE \"$tmptable\" AS
118 SELECT * FROM \"$table\";
119 DELETE FROM \"$table\";
120 INSERT INTO \"$table\"
121 SELECT * FROM \"$tmptable\" ORDER BY \"$column\";
122 DROP TABLE \"$tmptable\";
124 COMMIT TRANSACTION;
127 if ($sql_error) {
128 warn("$progname: $file: $SQLITE error, aborting\n");
129 return $ERROR;
132 return $OK;
133 # }}}
134 } # sort_file()
136 sub mod_date {
137 # Return file modification timestamp {{{
138 my $File = shift;
139 my $Retval = "";
140 my @stat_array = stat($File);
141 if (scalar(@stat_array)) {
142 $Retval = $stat_array[9];
143 } else {
144 warn("$progname: $File: Cannot stat file: $!\n");
146 return $Retval;
147 # }}}
148 } # mod_date()
150 sub sec_to_string {
151 # Convert seconds since 1970 to "yyyymmddThhmmss[.frac]Z" {{{
152 my ($Seconds, $Sep) = @_;
153 length($Seconds) || return '';
154 ($Seconds =~ /^-?(\d*)(\.\d+)?$/) || return undef;
155 my $Secfrac = ($Seconds =~ /^([\-\d]*)(\.\d+)$/) ? 1.0*$2 : "";
156 $Secfrac =~ s/^0//;
158 defined($Sep) || ($Sep = " ");
159 my @TA = gmtime($Seconds);
160 my($DateString) = sprintf("%04u%02u%02uT%02u%02u%02u%sZ",
161 $TA[5]+1900, $TA[4]+1, $TA[3],
162 $TA[2], $TA[1], $TA[0], $Secfrac);
163 return $DateString;
164 # }}}
165 } # sec_to_string()
167 sub sql {
168 # {{{
169 my ($db, $sql) = @_;
170 my @retval = ();
172 msg(3, "sql(): db = '$db'");
173 local(*CHLD_IN, *CHLD_OUT, *CHLD_ERR);
175 my $pid = open3(*CHLD_IN, *CHLD_OUT, *CHLD_ERR, $SQLITE, $db) or (
176 $sql_error = 1,
177 msg(0, "sql(): open3() error: $!"),
178 return "sql() error",
180 msg(3, "sql(): sql = '$sql'");
181 print(CHLD_IN "$sql\n") or msg(0, "sql(): print CHLD_IN error: $!");
182 close(CHLD_IN);
183 @retval = <CHLD_OUT>;
184 msg(3, "sql(): retval = '" . join('|', @retval) . "'");
185 my @child_stderr = <CHLD_ERR>;
186 if (scalar(@child_stderr)) {
187 msg(0, "$SQLITE error: " . join('', @child_stderr));
188 $sql_error = 1;
190 return join('', @retval);
191 # }}}
192 } # sql()
194 sub print_version {
195 # Print program version {{{
196 print("$progname $VERSION\n");
197 return;
198 # }}}
199 } # print_version()
201 sub usage {
202 # Send the help message to stdout {{{
203 my $Retval = shift;
205 if ($Opt{'verbose'}) {
206 print("\n");
207 print_version();
209 print(<<"END");
211 Sort the rows in an SQLite database. A backup of the previous version is
212 copied to a *.bck file containing the date of the file modification time
213 in the file name.
215 Usage: $progname [options] SQLite_database_file [...]
217 Options:
219 -c TABLE.COLUMN, --column TABLE.COLUMN
220 Sort COLUMN in TABLE. This option can be repeated to sort several
221 columns.
222 -h, --help
223 Show this help.
224 -q, --quiet
225 Be more quiet. Can be repeated to increase silence.
226 -v, --verbose
227 Increase level of verbosity. Can be repeated.
228 --version
229 Print version information.
232 exit($Retval);
233 # }}}
234 } # usage()
236 sub msg {
237 # Print a status message to stderr based on verbosity level {{{
238 my ($verbose_level, $Txt) = @_;
240 if ($Opt{'verbose'} >= $verbose_level) {
241 print(STDERR "$progname: $Txt\n");
243 return;
244 # }}}
245 } # msg()
247 __END__
249 # This program is free software; you can redistribute it and/or modify
250 # it under the terms of the GNU General Public License as published by
251 # the Free Software Foundation; either version 2 of the License, or (at
252 # your option) any later version.
254 # This program is distributed in the hope that it will be useful, but
255 # WITHOUT ANY WARRANTY; without even the implied warranty of
256 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
257 # See the GNU General Public License for more details.
259 # You should have received a copy of the GNU General Public License
260 # along with this program.
261 # If not, see L<http://www.gnu.org/licenses/>.
263 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :