line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Tree::Numbered::Tools; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
39733
|
use 5.006000; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
34
|
|
4
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
28
|
|
5
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
25
|
|
6
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
774
|
use Tree::Numbered; |
|
1
|
|
|
|
|
2059
|
|
|
1
|
|
|
|
|
29
|
|
8
|
1
|
|
|
1
|
|
763
|
use Text::ParseWords; |
|
1
|
|
|
|
|
1192
|
|
|
1
|
|
|
|
|
63
|
|
9
|
1
|
|
|
1
|
|
5
|
use Carp; # generate better errors with more context |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
5994
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
require Exporter; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our @ISA = qw(Tree::Numbered); |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# Items to export into callers namespace by default. Note: do not export |
16
|
|
|
|
|
|
|
# names by default without a very good reason. Use EXPORT_OK instead. |
17
|
|
|
|
|
|
|
# Do not simply export all your public functions/methods/constants. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# This allows declaration use Tree::Numbered::Tools ':all'; |
20
|
|
|
|
|
|
|
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK |
21
|
|
|
|
|
|
|
# will save memory. |
22
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( 'all' => [ qw( |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
) ] ); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
our @EXPORT = qw( |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
our $VERSION = '1.04'; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# - Generate a tree object from different sources: database table, text file, SQL statement, Perl array. |
36
|
|
|
|
|
|
|
# - Dump a tree object to one of these sources. |
37
|
|
|
|
|
|
|
# - Convert between source formats. |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# Parse a tree from a text file and convert it to a Tree::Numbered object |
40
|
|
|
|
|
|
|
# Each line in the text file should indent using at least one space for each level |
41
|
|
|
|
|
|
|
# The term 'top level' is used to describe a root child. |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=head1 NAME |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Tree::Numbered::Tools - Perl module to create tree objects using different sources. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head1 SYNOPSIS |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Example 1: Using a text file as a source: |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
Value LastName FirstName |
53
|
|
|
|
|
|
|
# ----- -------- --------- |
54
|
|
|
|
|
|
|
Grandfather Smith Abraham |
55
|
|
|
|
|
|
|
Son1 Smith Bert |
56
|
|
|
|
|
|
|
Son2 Smith 'Clumsy Carl' |
57
|
|
|
|
|
|
|
Grandson1 Jones Dennis |
58
|
|
|
|
|
|
|
Grandson2 Jones Eric |
59
|
|
|
|
|
|
|
Son3 Smith 'Fatty Fred' |
60
|
|
|
|
|
|
|
Grandson3 Smith Greg |
61
|
|
|
|
|
|
|
Grandson4 Smith Huey |
62
|
|
|
|
|
|
|
Grandmother Smith Anna |
63
|
|
|
|
|
|
|
Daughter1 Smith Berta |
64
|
|
|
|
|
|
|
Daughter2 Smith Celine |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
use Tree::Numbered::Tools; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Reads a text file, returns a tree object |
69
|
|
|
|
|
|
|
my $tree = Tree::Numbered::Tools->readFile( |
70
|
|
|
|
|
|
|
filename => $filename, |
71
|
|
|
|
|
|
|
use_column_names => 1, |
72
|
|
|
|
|
|
|
); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Example 2: Using an array as a source: |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
use Tree::Numbered::Tools; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
my $arrayref = [ |
79
|
|
|
|
|
|
|
[qw(serial parent name url)], |
80
|
|
|
|
|
|
|
[1, 0, 'ROOT', 'ROOT'], |
81
|
|
|
|
|
|
|
[2, 1, 'File', 'file.pl'], |
82
|
|
|
|
|
|
|
[3, 2, 'New', 'file-new.pl'], |
83
|
|
|
|
|
|
|
[4, 3, 'Window', 'file-new-window.pl'], |
84
|
|
|
|
|
|
|
]; |
85
|
|
|
|
|
|
|
my $tree = Tree::Numbered::Tools->readArray( |
86
|
|
|
|
|
|
|
arrayref => $arrayref, |
87
|
|
|
|
|
|
|
use_column_names => 1, |
88
|
|
|
|
|
|
|
); |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
Example 3: Using a database table as a source, use the SQL 'AS' statement for easy column mapping: |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
use Tree::Numbered::Tools; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
my $sql = 'SELECT serial, parent AS "Parent", name AS "Name", url AS "URL" FROM mytable ORDER BY serial'; |
95
|
|
|
|
|
|
|
my $tree = Tree::Numbered::Tools->readSQL( |
96
|
|
|
|
|
|
|
dbh => $dbh, |
97
|
|
|
|
|
|
|
sql => $sql, |
98
|
|
|
|
|
|
|
); |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Example 4: Display a tree object in the same format as the text file in example 1: |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
my $output = Tree::Numbered::Tools->outputFile(); |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
Example 5: Display a tree object as an array reference, to be used for cut 'n paste in a Perl program. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
my $output = Tree::Numbered::Tools->outputArray(); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Example 6: Convert a text file to a database table. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
my $sql = Tree::Numbered::Tools->convertFile2DB( |
111
|
|
|
|
|
|
|
filename => $filename, |
112
|
|
|
|
|
|
|
use_column_names => 1, |
113
|
|
|
|
|
|
|
dbh => $dbh, |
114
|
|
|
|
|
|
|
table => $table, |
115
|
|
|
|
|
|
|
); |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Example 7: Convert a text file to SQL 'INSERT INTO' statements. |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
my $sql = Tree::Numbered::Tools->convertFile2SQL( |
120
|
|
|
|
|
|
|
filename => $filename, |
121
|
|
|
|
|
|
|
use_column_names => 1, |
122
|
|
|
|
|
|
|
); |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head1 DESCRIPTION |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Tree::Numbered::Tools is a child class of Tree::Numbered. |
128
|
|
|
|
|
|
|
Its purpouse is to easily create a tree object from different sources. |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
The most useful source is probably a text file (see SYNOPSIS, example 1). |
131
|
|
|
|
|
|
|
The text file visualizes the tree structure as well as node names in the first column. |
132
|
|
|
|
|
|
|
Any other columns represent each node's properties. |
133
|
|
|
|
|
|
|
The format is easy to read and understand, even for a non-programmer. |
134
|
|
|
|
|
|
|
Besides, editing a text file is normally far more easy than editing records in a database table. |
135
|
|
|
|
|
|
|
Anyhow, at run time, reading from/writing to a database outperformances a text file. |
136
|
|
|
|
|
|
|
This module is intented to be used as a tool to create database tables using text files, not to replace tables with text files (even if the module permits you to use the text file as a source without dealing with a database). |
137
|
|
|
|
|
|
|
The format of the first column in the text file only requires that each tree level should be indented using one or more spaces (or tabs). It is recommended to be consistent and use the same number of spaces to indent all tree levels, even if the readFile() method tries to determine each node's level even if the indenting isn't consistent. To get each node's properties, the readFile() method parses each line in the text file using the Text::ParseWords module, so any property value containg a space must be quoted. If the last column or columns in the text file for a node are omitted, the corresponding property value is assigned the empty string. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Programmers who prefer not using an external source when creating a tree may use an array reference. |
140
|
|
|
|
|
|
|
Being a programmer, it is probably easier to edit an array than database records. |
141
|
|
|
|
|
|
|
See SYNOPSIS, example 2. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
The purpouse of the SQL statement as a source for the tree object is the more straightforward way to map column names using Tree::Numbered::Tools->readSQL() than the Tree::Numbered::DB->read() method. |
144
|
|
|
|
|
|
|
See SYNOPSIS, example 3. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head1 NOTES ABOUT THE ROOT NODE |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
Using a text file as a source, the text file does not contain the root node itself. This is on purpouse. In daily life, describing a tree, frequently there is not one single root node, but two or many 'top level nodes' as the 'Grandfather' and 'Grandmother' nodes in SYNOPSIS, example 1. |
149
|
|
|
|
|
|
|
To manage all the nodes as a single tree, a single root node named 'ROOT' will always be created. |
150
|
|
|
|
|
|
|
In tree terminology, a 'top level node' is the same as a root child. |
151
|
|
|
|
|
|
|
Anyway, using any other source, the 'ROOT' node should be included. |
152
|
|
|
|
|
|
|
See SYNOPSIS, example 2, how to create the 'ROOT' node with an array. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=head1 NOTES ABOUT FIELDS AND COLUMNS |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
A Tree::Numbered object uses the term 'fields' for each node's properties. |
157
|
|
|
|
|
|
|
A Tree::Numbered::Tools object uses the term 'columns'. |
158
|
|
|
|
|
|
|
Shortly, 'columns' are 'fields' in a specified order. |
159
|
|
|
|
|
|
|
The Tree::Numbered->getFieldNames() method uses a hash internally to get field names. |
160
|
|
|
|
|
|
|
This means there is no way to guarantee a specific order in obtaining the field names. The field order doesn't matter for an abstract tree object, but it does when printing a tree structure, for example. |
161
|
|
|
|
|
|
|
The Tree::Numbered::Tools->getColumnNames method uses an array internally to guarantee the specified order. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
The column order is only an issue when working with a tree object created by a source not specifying columns, for example creating a new tree using the Tree::Numbered->new() method. |
164
|
|
|
|
|
|
|
When creating a tree using the readSQL() method, the column names will be obtained from the DBI::$sth->{NAME} method, i.e. the SQL statement, and thus listed in a known order. |
165
|
|
|
|
|
|
|
When creating a tree using the readFile()/readArray() method, the column names can be obtained using the getColumnNames() method, if the source file/array was specified with column names on its first line/row, and use_column_names is set to true. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
There is no way to 'map' column names from a file/SQL/array to field names in the tree object using distinct names, as it is in Tree::Numbered::DB, for example. |
168
|
|
|
|
|
|
|
Instead of mapping, modify the column names in your text file or array row, or use the SQL 'AS' statement, depending on which method you use to create the tree. |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head1 METHODS SUMMARY |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Methods to create a tree object reading from a source: |
173
|
|
|
|
|
|
|
readFile() - read from a text file |
174
|
|
|
|
|
|
|
readArray() - read from an array |
175
|
|
|
|
|
|
|
readSQL() - read from an SQL statement |
176
|
|
|
|
|
|
|
readDB() - read from a database table |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Methods to output the contents of a tree object: |
179
|
|
|
|
|
|
|
outputFile() - output in text file format |
180
|
|
|
|
|
|
|
outputArray() - output in array format (Perl code) |
181
|
|
|
|
|
|
|
outputSQL() - output as SQL statements |
182
|
|
|
|
|
|
|
outputDB() - output to (creates) a database table |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Methods to convert from one source format to another: |
185
|
|
|
|
|
|
|
convertFile2Array() |
186
|
|
|
|
|
|
|
convertFile2SQL() |
187
|
|
|
|
|
|
|
convertFile2DB() |
188
|
|
|
|
|
|
|
convertArray2File() |
189
|
|
|
|
|
|
|
convertArray2SQL() |
190
|
|
|
|
|
|
|
convertArray2DB() |
191
|
|
|
|
|
|
|
convertSQL2File() |
192
|
|
|
|
|
|
|
convertSQL2Array() |
193
|
|
|
|
|
|
|
convertSQL2DB() |
194
|
|
|
|
|
|
|
convertDB2File() |
195
|
|
|
|
|
|
|
convertDB2Array() |
196
|
|
|
|
|
|
|
convertDB2SQL() |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
Using convertX2Y() is practically the same as calling readX() followed by outputY(). |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Other Methods: |
201
|
|
|
|
|
|
|
getColumnNames - see NOTES ABOUT FIELDS AND COLUMNS |
202
|
|
|
|
|
|
|
getSourceType - File, Array, SQL, DB |
203
|
|
|
|
|
|
|
getSourceName - file name, database table name |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head1 METHODS |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head2 readFile() |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
readFile( |
210
|
|
|
|
|
|
|
filename => $filename, |
211
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
212
|
|
|
|
|
|
|
); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Reads $filename, returns a tree object. |
215
|
|
|
|
|
|
|
$use_column_names is a boolean, if set (default), assumes that the first (non-comment, non-blank) line contains column names. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=cut |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub readFile { |
221
|
32
|
|
|
32
|
1
|
512
|
my $self = shift; |
222
|
|
|
|
|
|
|
# Get args |
223
|
32
|
|
|
|
|
537
|
my %args = ( |
224
|
|
|
|
|
|
|
filename => '', |
225
|
|
|
|
|
|
|
use_column_names => 1, # Require column names by default, as we create them by default in outputFile |
226
|
|
|
|
|
|
|
@_, # argument pair list goes here |
227
|
|
|
|
|
|
|
); |
228
|
|
|
|
|
|
|
# Die on missing filename |
229
|
32
|
50
|
|
|
|
154
|
my $filename = $args{filename} or croak "Missing filename"; |
230
|
32
|
|
|
|
|
68
|
my $use_column_names = $args{use_column_names}; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Get the file contents into an array |
233
|
32
|
50
|
|
|
|
2965
|
open FH, "<$filename" or croak "Cannot open $filename: $!"; |
234
|
32
|
|
|
|
|
2139
|
chomp(my @lines = ); |
235
|
32
|
|
|
|
|
1177
|
close FH; |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# Weed out comments and blank lines |
238
|
32
|
|
|
|
|
1403
|
@lines = grep(!/^\s*\#/, @lines); |
239
|
32
|
|
|
|
|
730
|
@lines = grep(!/^\s*$/, @lines); |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Default root value: |
242
|
32
|
|
|
|
|
227
|
my $root_value = 'ROOT'; |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# Optionally, get column names from first line, pass column names to Tree::Numbered->new method |
245
|
|
|
|
|
|
|
# Column names cannot have spaces. |
246
|
32
|
|
|
|
|
96
|
my %args_hash = (); |
247
|
32
|
|
|
|
|
56
|
my @column_names; |
248
|
|
|
|
|
|
|
|
249
|
32
|
|
|
|
|
238
|
my $first_line = $self->_trim($lines[0]); |
250
|
|
|
|
|
|
|
# Initiate the column names array if asked for |
251
|
32
|
50
|
|
|
|
76
|
if ($use_column_names) { |
252
|
|
|
|
|
|
|
# Shift off first line (column names) from contents |
253
|
32
|
|
|
|
|
56
|
shift(@lines); |
254
|
|
|
|
|
|
|
# Get column names from first line |
255
|
32
|
|
|
|
|
250
|
my $column_names_ref = $self->_getColumnNamesFile($first_line); |
256
|
32
|
|
|
|
|
250
|
@column_names = @$column_names_ref; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
# When not using column names, we have to scan all lines in the text file to get the one with most columns, as some lines, including the first, may have omitted the last column(s). |
259
|
|
|
|
|
|
|
# The line with most columns will decide the number of columns used. |
260
|
|
|
|
|
|
|
else { |
261
|
|
|
|
|
|
|
# Get max columns |
262
|
0
|
|
|
|
|
0
|
my $max_cols = $self->_getMaxColumnsFile(\@lines); |
263
|
|
|
|
|
|
|
# Use default column names ('serial', 'parent', 'Value', 'Value2', 'Value3', etc) if no column names were given |
264
|
0
|
|
|
|
|
0
|
@column_names[0..2] = ('serial', 'parent', 'Value'); |
265
|
0
|
|
|
|
|
0
|
for (my $i = 3; $i < $max_cols; $i++) { |
266
|
0
|
|
|
|
|
0
|
$column_names[$i] = 'Value'.($i-1); |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
# The argument hash for the root node |
270
|
32
|
|
|
|
|
111
|
foreach my $column_name (@column_names) { |
271
|
64
|
|
|
|
|
189
|
$args_hash{$column_name} = $root_value; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# Create a root node to tie all top level nodes |
275
|
32
|
|
|
|
|
369
|
$self = $self->new(%args_hash); |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# Assume that first line is a top level node |
278
|
|
|
|
|
|
|
# Use a hash, where the key is the indentation and the value is the level |
279
|
32
|
|
|
|
|
3613
|
my %level_indent = (); |
280
|
|
|
|
|
|
|
# Use first top level node as start values |
281
|
32
|
|
|
|
|
195
|
my $current_indent = $self->_indented($lines[0]); |
282
|
32
|
|
|
|
|
79
|
my $previous_indent = $self->_indented($lines[0]); |
283
|
32
|
|
|
|
|
52
|
my $current_level = 0; |
284
|
32
|
|
|
|
|
174
|
$level_indent{$current_indent} = 0; |
285
|
|
|
|
|
|
|
|
286
|
32
|
|
|
|
|
83
|
my $node = $self; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Loop through lines |
289
|
32
|
|
|
|
|
144
|
for (my $i = 0; $i < @lines; $i++) { |
290
|
736
|
|
|
|
|
1792
|
my $line = $self->_trim($lines[$i]); |
291
|
|
|
|
|
|
|
# Split possible line fields, keep quotes, Text::ParseWords for details |
292
|
736
|
|
|
|
|
2132
|
my @line_fields = &parse_line('\s+', 1, $line); |
293
|
736
|
|
|
|
|
47220
|
@line_fields = $self->_strip_quotes(@line_fields); |
294
|
736
|
|
|
|
|
1249
|
my $value = $line_fields[0]; |
295
|
|
|
|
|
|
|
|
296
|
736
|
|
|
|
|
1689
|
$current_indent = $self->_indented($lines[$i]); |
297
|
736
|
100
|
|
|
|
2521
|
$previous_indent = ($i > 0) ? $self->_indented($lines[$i-1]) : $self->_indented($lines[0]); |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# Down one level ? |
300
|
736
|
100
|
|
|
|
2006
|
if ($current_indent > $previous_indent) { |
|
|
100
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# We never go down more than one level at a time |
302
|
224
|
|
|
|
|
291
|
$self = $node; |
303
|
224
|
|
|
|
|
272
|
$current_level++; |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Up one or more levels ? |
307
|
|
|
|
|
|
|
elsif ($current_indent < $previous_indent) { |
308
|
|
|
|
|
|
|
# We may go up one or more levels at a time |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# BUGFIX - BEGIN |
311
|
|
|
|
|
|
|
# Bug in Tree-Numbered-Tools-1.01: |
312
|
|
|
|
|
|
|
# Warning message "Use of uninitialized value in subtraction (-)" when nodes at the first line or lines use a higher indent level than following lines. |
313
|
|
|
|
|
|
|
# The warning message is caused by $current_indent having a undefined value. |
314
|
|
|
|
|
|
|
# The solution is to set $current_indent to 0 and show a customized warning message. |
315
|
|
|
|
|
|
|
# (NOT reported in bug ticket http://rt.cpan.org/Public/Bug/Display.html?id=48068) |
316
|
|
|
|
|
|
|
# Bugfix added in 1.02 (2009-07-25). |
317
|
144
|
100
|
|
|
|
471
|
if (!defined $level_indent{$current_indent}) |
318
|
|
|
|
|
|
|
{ |
319
|
16
|
|
|
|
|
47
|
$level_indent{$current_indent} = 0; |
320
|
16
|
50
|
|
|
|
113
|
my $warn_lines = $i ? "'$lines[$i-1]'\n'$lines[$i]'\n'$lines[$i+1]'\n" : "'$lines[0]'\n'$lines[1]'\n'$lines[2]'\n"; |
321
|
16
|
|
|
|
|
291
|
warn "WARNING: One or more of the following line seems to be incorrectly indented:\n$warn_lines"; |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
# BUGFIX - END |
324
|
144
|
|
|
|
|
797
|
my $up_levels = $level_indent{$previous_indent} - $level_indent{$current_indent}; |
325
|
144
|
|
|
|
|
187
|
$current_level = $current_level - $up_levels; |
326
|
144
|
|
|
|
|
317
|
foreach (1..$up_levels) { |
327
|
128
|
|
|
|
|
456
|
$self = $self->getParentRef; |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# Determine fields used |
332
|
736
|
50
|
|
|
|
1979
|
if ($use_column_names) { |
333
|
736
|
|
|
|
|
943
|
my $j = 0; |
334
|
736
|
|
|
|
|
1075
|
foreach my $column_name (@column_names) { |
335
|
1472
|
|
|
|
|
3551
|
$args_hash{$column_name} = $line_fields[$j++]; |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
else { |
340
|
|
|
|
|
|
|
# Default field 'Value' if no column names |
341
|
0
|
|
|
|
|
0
|
$args_hash{Value} = $value; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
# Append node |
344
|
736
|
|
|
|
|
3042
|
$node = $self->append(%args_hash); |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# Save current level state |
347
|
736
|
|
|
|
|
81343
|
$level_indent{$current_indent} = $current_level; |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
# Up to top level, to get the entire tree object |
351
|
32
|
|
|
|
|
160
|
while ($self->getNumber != 1) { |
352
|
96
|
|
|
|
|
700
|
$self = $self->getParentRef; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Set object properties |
356
|
|
|
|
|
|
|
# Initiate the column names variable so the outside world can use getColumnNames |
357
|
|
|
|
|
|
|
# (will return undef if use_column_names was set to false) |
358
|
32
|
|
|
|
|
274
|
$self->{COLUMN_NAMES_REF} = \@column_names; |
359
|
32
|
|
|
|
|
98
|
$self->{SOURCE_TYPE} = 'File'; |
360
|
32
|
|
|
|
|
81
|
$self->{SOURCE_NAME} = $filename; |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
# Return the tree object |
363
|
32
|
|
|
|
|
301
|
return $self; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=cut |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head2 readArray() |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
readArray( |
371
|
|
|
|
|
|
|
arrayref => $arrayref, |
372
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
373
|
|
|
|
|
|
|
); |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
Reads $arrayref, returns a Tree::Numbered object. |
376
|
|
|
|
|
|
|
$use_column_names is a boolean, if set (default), assumes that the first array row contains column names. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=cut |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
sub readArray { |
381
|
49
|
|
|
49
|
1
|
462
|
my $self = shift; |
382
|
|
|
|
|
|
|
# Get args |
383
|
49
|
|
|
|
|
280
|
my %args = ( |
384
|
|
|
|
|
|
|
arrayref => '', |
385
|
|
|
|
|
|
|
use_column_names => 1, # Assume column names by default |
386
|
|
|
|
|
|
|
@_, # argument pair list goes here |
387
|
|
|
|
|
|
|
); |
388
|
49
|
50
|
|
|
|
200
|
my $arrayref = $args{arrayref} or croak "Missing array"; |
389
|
49
|
|
|
|
|
122
|
my $use_column_names = $args{use_column_names}; |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Get the array |
392
|
49
|
|
|
|
|
182
|
my @array = @$arrayref; |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# Get first element |
395
|
49
|
|
|
|
|
67
|
my @first_element = @{$array[0]}; |
|
49
|
|
|
|
|
161
|
|
396
|
49
|
50
|
|
|
|
110
|
croak "The array must have at least three columns: 'serial', 'parent', and 'Value'" if (@first_element < 3); |
397
|
|
|
|
|
|
|
|
398
|
49
|
|
|
|
|
75
|
my @column_names = (); |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# Shift off first element (column names) from array if we are using column names. |
401
|
49
|
100
|
|
|
|
87
|
if ($use_column_names) { |
402
|
48
|
|
|
|
|
82
|
@column_names = @first_element; |
403
|
48
|
|
|
|
|
61
|
shift @array; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
# use default column names ('serial', 'parent', 'Value', 'Value2', 'Value3', etc) if no column names were given |
406
|
|
|
|
|
|
|
else { |
407
|
1
|
|
|
|
|
3
|
@column_names[0..2] = ('serial', 'parent', 'Value'); |
408
|
1
|
|
|
|
|
5
|
for (my $i = 3; $i < @first_element; $i++) { |
409
|
4
|
|
|
|
|
15
|
$column_names[$i] = 'Value'.($i-1); |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
# BUGFIX BEGIN |
414
|
|
|
|
|
|
|
# First column's name must be 'serial' (lower case). |
415
|
49
|
50
|
|
|
|
113
|
croak "The first column's name must be 'serial' (lower case)" if ($column_names[0] ne 'serial'); |
416
|
|
|
|
|
|
|
# BUGFIX END |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# BUGFIX - BEGIN |
419
|
|
|
|
|
|
|
# Bug in Tree-Numbered-Tools-1.01: |
420
|
|
|
|
|
|
|
# http://rt.cpan.org/Public/Bug/Display.html?id=48068 |
421
|
|
|
|
|
|
|
# Bugfix added in 1.02 (2009-07-24), suggested by Daniel Higgins: |
422
|
|
|
|
|
|
|
# Check column 'serial' and 'parent', both must be numeric integer values. |
423
|
|
|
|
|
|
|
# Then sort array numerically by 'parent' column to avoid append() error later, bug occurs with unsorted arrays. |
424
|
|
|
|
|
|
|
# Check for valid integers. |
425
|
49
|
|
|
|
|
124
|
for (my $i = 0; $i < @array; $i++) |
426
|
|
|
|
|
|
|
{ |
427
|
|
|
|
|
|
|
# Get element and it fields |
428
|
451
|
|
|
|
|
397
|
my @element_fields = @{$array[$i]}; |
|
451
|
|
|
|
|
949
|
|
429
|
|
|
|
|
|
|
# Get current node and parent node numbers |
430
|
451
|
|
|
|
|
523
|
my $serial = $element_fields[0]; |
431
|
451
|
|
|
|
|
376
|
my $parent = $element_fields[1]; |
432
|
451
|
50
|
|
|
|
657
|
croak "The 'serial' element '$serial' in row $i isn't an integer'" if (!_isInteger($serial)); |
433
|
451
|
50
|
|
|
|
680
|
croak "The 'parent' element '$parent' in row $i isn't an integer'" if (!_isInteger($parent)); |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
# Sort array. |
436
|
1123
|
|
|
|
|
1246
|
@array = sort { |
437
|
49
|
|
|
|
|
231
|
($a->[1] <=> $b->[1]) } @array; |
438
|
|
|
|
|
|
|
# BUGFIX - END |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Get root node |
441
|
49
|
|
|
|
|
58
|
my @root_node = @{$array[0]}; |
|
49
|
|
|
|
|
117
|
|
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# Create argument hash using column names as keys and root node as values |
444
|
49
|
|
|
|
|
107
|
my %args_hash = (); |
445
|
49
|
|
|
|
|
115
|
for (my $i = 0; $i < @column_names; $i++) { |
446
|
199
|
|
|
|
|
570
|
$args_hash{$column_names[$i]} = $root_node[$i]; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# Shift off the root node from the array |
450
|
49
|
|
|
|
|
54
|
shift @array; |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# Create a root node to tie all top level nodes |
453
|
49
|
|
|
|
|
234
|
$self = $self->new( |
454
|
|
|
|
|
|
|
%args_hash |
455
|
|
|
|
|
|
|
); |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
# Loop through elements |
458
|
49
|
|
|
|
|
5667
|
for (my $i = 0; $i < @array; $i++) { |
459
|
|
|
|
|
|
|
# Get element and it fields |
460
|
402
|
|
|
|
|
47405
|
my @element_fields = @{$array[$i]}; |
|
402
|
|
|
|
|
939
|
|
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# Get current node and parent node numbers |
463
|
402
|
|
|
|
|
450
|
my $serial = $element_fields[0]; |
464
|
402
|
|
|
|
|
411
|
my $parent = $element_fields[1]; |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# Determine fields used |
467
|
402
|
|
|
|
|
581
|
my $j = 0; |
468
|
402
|
|
|
|
|
551
|
foreach my $column_name (@column_names) { |
469
|
1614
|
|
|
|
|
2594
|
$args_hash{$column_name} = $element_fields[$j++]; |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
# BUGFIX - BEGIN |
473
|
|
|
|
|
|
|
# Bug in Tree-Numbered-Tools-1.01: |
474
|
|
|
|
|
|
|
# http://rt.cpan.org/Public/Bug/Display.html?id=48068 |
475
|
|
|
|
|
|
|
# Bugfix added in 1.02 (2009-07-24), suggested by Daniel Higgins: |
476
|
402
|
|
|
|
|
631
|
our $parentnode=undef; |
477
|
|
|
|
|
|
|
$self->allProcess( sub { |
478
|
3795
|
|
|
3795
|
|
29034
|
my ($self,$parent) = @_; |
479
|
3795
|
|
|
|
|
3570
|
our $parentnode; |
480
|
3795
|
|
|
|
|
14062
|
$_ = $self->getserial ; |
481
|
3795
|
100
|
|
|
|
120915
|
$parentnode = $self if $_ == $parent ; |
482
|
|
|
|
|
|
|
}, |
483
|
402
|
|
|
|
|
1966
|
$parent ); |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# Add current node to its parent |
486
|
402
|
|
|
|
|
10735
|
my $node = $parentnode ; |
487
|
402
|
|
|
|
|
1354
|
$node = $node->append(%args_hash); |
488
|
|
|
|
|
|
|
# BUGFIX - END |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
# Set object properties |
492
|
|
|
|
|
|
|
# Initiate the column names variable so the outside world can use getColumnNames |
493
|
|
|
|
|
|
|
# (will return undef if use_column_names was set to false) |
494
|
|
|
|
|
|
|
# Column names serial and parent should not be included in column names list, shift them off |
495
|
49
|
50
|
|
|
|
6102
|
shift @column_names if @column_names; |
496
|
49
|
50
|
|
|
|
331
|
shift @column_names if @column_names; |
497
|
49
|
|
|
|
|
90
|
$self->{COLUMN_NAMES_REF} = \@column_names; |
498
|
49
|
|
|
|
|
80
|
$self->{SOURCE_TYPE} = 'Array'; |
499
|
49
|
|
|
|
|
169
|
$self->{SOURCE_NAME} = undef; |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
# Return the tree object |
502
|
49
|
|
|
|
|
267
|
return $self; |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=head2 readSQL() |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
readSQL( |
509
|
|
|
|
|
|
|
dbh => $dbh, |
510
|
|
|
|
|
|
|
sql => $sql, |
511
|
|
|
|
|
|
|
); |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
Fetches an array using the database handle $dbh and the SQL statement $sql, returns a tree object. |
514
|
|
|
|
|
|
|
Uses readArray() internally to create the tree object. |
515
|
|
|
|
|
|
|
To map column names in the database table to tree column names, use the SQL 'AS' statement. |
516
|
|
|
|
|
|
|
Always get used to double quote the alias name, to make the SQL statement database type independent. |
517
|
|
|
|
|
|
|
Without alias quotes, reserved SQL words such as 'AS' will work as an alias on MySQL but not on PgSQL (PgSQL returns lower case aliases unless double quoted). |
518
|
|
|
|
|
|
|
Remember that aliases cannot contain spaces, as they reflect the column names, which in turn are used for methods getting a column's value. For example, to obtain a value for a column created from an alias called 'MyColumn', the method getMyColumn() will be used. An alias called 'My Column' will try to call the method getMy Column(), which of course will cause a run-time syntax error. |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
Example 1: |
521
|
|
|
|
|
|
|
# GOOD, works on both MySQL and PgSQL |
522
|
|
|
|
|
|
|
my $sql = 'SELECT serial AS "Serial", parent AS "Parent", name AS "Name", url AS "URL" FROM mytable ORDER BY Serial'; |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
Example 2: |
525
|
|
|
|
|
|
|
# BAD, works on MySQL but not on PgSQL |
526
|
|
|
|
|
|
|
my $sql = 'SELECT serial AS Serial, parent AS Parent, name AS Name, url AS URL FROM mytable ORDER BY Serial'; |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
Example 3: |
529
|
|
|
|
|
|
|
# BAD, single quotes will not do on PgSQL |
530
|
|
|
|
|
|
|
my $sql = "SELECT serial AS 'Serial', parent AS 'Parent', name AS 'Name', url AS 'URL' FROM mytable ORDER BY Serial"; |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
Well, if you forgot to quote the aliases, readSQL() adds the quotes for you. |
533
|
|
|
|
|
|
|
You should just be aware of that unquoted aliases doesn't always work as expected in your daily SQL life. :-) |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=cut |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
sub readSQL { |
538
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
539
|
|
|
|
|
|
|
# Get args |
540
|
0
|
|
|
|
|
0
|
my %args = ( |
541
|
|
|
|
|
|
|
dbh => '', |
542
|
|
|
|
|
|
|
sql => '', |
543
|
|
|
|
|
|
|
@_, # argument pair list goes here |
544
|
|
|
|
|
|
|
); |
545
|
0
|
0
|
|
|
|
0
|
my $dbh = $args{dbh} or croak "Missing DB handle"; |
546
|
0
|
0
|
|
|
|
0
|
my $sql = $args{sql} or croak "Missing SQL statement" ; |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
# Quote any SQL aliases |
549
|
0
|
|
|
|
|
0
|
$sql = $self->_sql_alias_quoted($sql); |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
# Get array reference |
552
|
|
|
|
|
|
|
# Column names are always used, named after the SQL columns |
553
|
0
|
0
|
|
|
|
0
|
my $sth = $dbh->prepare($sql) or croak $dbh->errstr; |
554
|
0
|
0
|
|
|
|
0
|
$sth->execute or croak $dbh->errstr; |
555
|
|
|
|
|
|
|
# Get column names |
556
|
0
|
|
|
|
|
0
|
my $colnamesref = $sth->{'NAME'}; |
557
|
0
|
0
|
|
|
|
0
|
my $arrayref = $sth->fetchall_arrayref or croak $dbh->errstr; |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# Insert column names as first element into the retreived array |
560
|
0
|
|
|
|
|
0
|
unshift @$arrayref, $colnamesref; |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
# Use readArray to create the tree |
563
|
0
|
|
|
|
|
0
|
my $use_column_names = 1; |
564
|
0
|
|
|
|
|
0
|
$self = $self->readArray( |
565
|
|
|
|
|
|
|
arrayref => $arrayref, |
566
|
|
|
|
|
|
|
use_column_names => 1, |
567
|
|
|
|
|
|
|
); |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# Set object properties |
570
|
|
|
|
|
|
|
# Initiate the column names variable so the outside world can use getColumnNames |
571
|
|
|
|
|
|
|
# $self->{COLUMN_NAMES_REF} = $colnamesref; # Already set from readArray |
572
|
0
|
|
|
|
|
0
|
$self->{SOURCE_TYPE} = 'SQL'; |
573
|
0
|
|
|
|
|
0
|
$self->{SOURCE_NAME} = undef; |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# Return the tree object |
576
|
0
|
|
|
|
|
0
|
return $self; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=head2 readDB() |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
readDB( |
582
|
|
|
|
|
|
|
dbh => $dbh, |
583
|
|
|
|
|
|
|
table => $table, |
584
|
|
|
|
|
|
|
); |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
Fetches an array using the database handle $dbh from the table $table, returns a Tree::Numbered object. |
587
|
|
|
|
|
|
|
This is a wrapper for the readSQL() mehod using the SQL statement 'SELECT * from $table'. |
588
|
|
|
|
|
|
|
It is recommended to use the more flexible readSQL() instead, as you can map names using the 'AS' statement. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=cut |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub readDB { |
593
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
594
|
|
|
|
|
|
|
# Get args |
595
|
0
|
|
|
|
|
0
|
my %args = ( |
596
|
|
|
|
|
|
|
dbh => '', |
597
|
|
|
|
|
|
|
table => '', |
598
|
|
|
|
|
|
|
@_, # argument pair list goes here |
599
|
|
|
|
|
|
|
); |
600
|
0
|
0
|
|
|
|
0
|
my $dbh = $args{dbh} or croak "Missing DB handle"; |
601
|
0
|
0
|
|
|
|
0
|
my $table = $args{table} or croak "Missing database table name" ; |
602
|
0
|
|
|
|
|
0
|
my $sql = "SELECT * FROM $table"; |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
# Use readSQL to create the tree |
605
|
0
|
|
|
|
|
0
|
$self = $self->readSQL( |
606
|
|
|
|
|
|
|
dbh => $dbh, |
607
|
|
|
|
|
|
|
sql => $sql, |
608
|
|
|
|
|
|
|
); |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
# Set object properties |
611
|
|
|
|
|
|
|
# Initiate the column names variable so the outside world can use getColumnNames |
612
|
|
|
|
|
|
|
# $self->{COLUMN_NAMES_REF} = $colnamesref; # Already set from readSQL |
613
|
0
|
|
|
|
|
0
|
$self->{SOURCE_TYPE} = 'DB'; |
614
|
0
|
|
|
|
|
0
|
$self->{SOURCE_NAME} = $table; |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
# Return the tree object |
617
|
0
|
|
|
|
|
0
|
return $self; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
=head2 outputFile() |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
outputFile( |
624
|
|
|
|
|
|
|
first_indent => $first_indent, |
625
|
|
|
|
|
|
|
level_indent => $level_indent, |
626
|
|
|
|
|
|
|
column_indent => $column_indent, |
627
|
|
|
|
|
|
|
); |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
The ouputFile() method returns the tree structure as used in the file format. |
630
|
|
|
|
|
|
|
The purpouse of this method is to display/create an overview of a tree object, both the tree hierarchy and each node's properties, which easily can be modified with a text editor to create a new tree object using the readFile() method. |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
All arguments are optional. |
633
|
|
|
|
|
|
|
Formatting arguments: |
634
|
|
|
|
|
|
|
$first_indent decides the position of the first column. |
635
|
|
|
|
|
|
|
$level_indent decides the indenting for each node level. |
636
|
|
|
|
|
|
|
$column_indent decides the number of spaces to separate columns. |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
=cut |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
sub outputFile { |
641
|
20
|
|
|
20
|
1
|
7596
|
my $self = shift; |
642
|
20
|
|
|
|
|
91
|
my %args = ( |
643
|
|
|
|
|
|
|
first_indent => 2, |
644
|
|
|
|
|
|
|
level_indent => 2, |
645
|
|
|
|
|
|
|
column_indent => 2, |
646
|
|
|
|
|
|
|
@_, # argument pair list goes here |
647
|
|
|
|
|
|
|
); |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
# Get list of column names |
650
|
20
|
|
|
|
|
83
|
my $column_names_ref = $self->getColumnNames(); |
651
|
|
|
|
|
|
|
# If column names are defined, compare number of columns with number of fields in tree |
652
|
20
|
|
|
|
|
43
|
my @column_names = @$column_names_ref; |
653
|
20
|
|
|
|
|
74
|
my @field_names = $self->getFieldNames; |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
# If column names aren't defined ($column_names_ref returns undef), use tree field names (arbitrary order). |
656
|
20
|
50
|
|
|
|
162
|
@column_names = @field_names if (!$column_names_ref); |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
# Create the indented tree structure and optional additional columns |
659
|
20
|
|
|
|
|
30
|
my $first_indent = $args{first_indent}; |
660
|
20
|
|
|
|
|
22
|
my $level_indent = $args{level_indent}; |
661
|
20
|
|
|
|
|
25
|
my $column_indent = $args{column_indent}; |
662
|
20
|
|
|
|
|
34
|
my $first_indent_string = ' ' x $first_indent; |
663
|
20
|
|
|
|
|
25
|
my $indent_string = ' ' x $level_indent;; |
664
|
20
|
|
|
|
|
25
|
my $tree_structure = ''; |
665
|
|
|
|
|
|
|
# Use a copy of the array, as it will be modified. |
666
|
20
|
|
|
|
|
33
|
my @extra_column_names = @column_names; |
667
|
20
|
|
|
|
|
27
|
my $first_column_name = shift(@extra_column_names); |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
# Calculate each node value's string length, needed for pretty printing. |
670
|
|
|
|
|
|
|
# The longest string in each column will decide each column's position. |
671
|
|
|
|
|
|
|
# The first column's value will be indented according to its tree level. |
672
|
|
|
|
|
|
|
# Thus, the indenting has to be included when calculate the string length for the first column. |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
# Array to store each column's position, needed for pretty printing |
675
|
|
|
|
|
|
|
# Initiate the @column_pos array with the length of each column_name, in case that the column name is longer than any of its values. |
676
|
20
|
|
|
|
|
27
|
my @column_pos = (); |
677
|
20
|
|
|
|
|
35
|
foreach (@column_names) { |
678
|
40
|
|
|
|
|
62
|
push @column_pos, length($_); |
679
|
|
|
|
|
|
|
} |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# Calculate first column's position, including indenting |
682
|
20
|
|
|
|
|
64
|
foreach my $node_number ($self->listChildNumbers) { |
683
|
215
|
|
|
|
|
4781
|
my @values = $self->follow($node_number, $first_column_name); |
684
|
|
|
|
|
|
|
# Calculate spaces before this node's string |
685
|
215
|
|
|
|
|
49447
|
my $node_indent_length = $first_indent + (scalar(@values) - 1) * $level_indent; |
686
|
|
|
|
|
|
|
# Add this node's string length |
687
|
215
|
|
|
|
|
915
|
my $first_value = pop(@values); |
688
|
215
|
|
|
|
|
275
|
my $first_value_string_length = length($first_value); |
689
|
|
|
|
|
|
|
# Add 2 to string length if quoting is needed |
690
|
215
|
50
|
|
|
|
517
|
$first_value_string_length = $first_value_string_length + 2 if ($first_value =~ m/\s+/); |
691
|
|
|
|
|
|
|
# Caclulate entire length for first column |
692
|
215
|
|
|
|
|
266
|
my $first_column_string_length = $node_indent_length + $first_value_string_length + 1; |
693
|
215
|
100
|
|
|
|
385
|
$column_pos[0] = $first_column_string_length if ($first_column_string_length > $column_pos[0]); |
694
|
|
|
|
|
|
|
# Calculate extra columns' positions |
695
|
215
|
|
|
|
|
414
|
for (my $i = 1; $i < @column_names; $i++) { |
696
|
|
|
|
|
|
|
# Last value in array contains this node's value |
697
|
215
|
|
|
|
|
664
|
my @values = $self->follow($node_number, $column_names[$i]); |
698
|
215
|
|
|
|
|
55661
|
my $value = pop(@values); |
699
|
|
|
|
|
|
|
# If no $value (last column may be blank, which returns undef), ignore. |
700
|
215
|
50
|
|
|
|
405
|
if ($value) { |
701
|
215
|
50
|
|
|
|
337
|
my $column_string_length = ($value) ? length($value) : 0; |
702
|
|
|
|
|
|
|
# Add 2 to length if quoting is needed |
703
|
215
|
50
|
|
|
|
617
|
$column_string_length = $column_string_length + 2 if ($value =~ m/\s+/); |
704
|
215
|
100
|
|
|
|
1089
|
$column_pos[$i] = $column_string_length if ($column_string_length > $column_pos[$i]); |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
} |
707
|
|
|
|
|
|
|
} |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
# Create contents string |
710
|
20
|
|
|
|
|
68
|
foreach my $node_number ($self->listChildNumbers) { |
711
|
215
|
|
|
|
|
4593
|
my $line = $first_indent_string; |
712
|
|
|
|
|
|
|
# The array contains a list of all the node's parent values as well as its own value |
713
|
215
|
|
|
|
|
531
|
my @values = $self->follow($node_number, $first_column_name); |
714
|
|
|
|
|
|
|
# The scalar contains only the node's own value |
715
|
215
|
|
|
|
|
47426
|
my $value = pop(@values); |
716
|
215
|
|
|
|
|
355
|
$line .= $indent_string x scalar(@values); |
717
|
215
|
|
|
|
|
217
|
$line .= $value; |
718
|
|
|
|
|
|
|
# Add any necessary spaces after the value |
719
|
215
|
|
|
|
|
361
|
$line .= " " x ($column_pos[0] - length($line) + $column_indent - 1); |
720
|
|
|
|
|
|
|
# Loop through all other columns but the first |
721
|
215
|
|
|
|
|
524
|
for (my $i = 1; $i < @column_names; $i++) { |
722
|
215
|
|
|
|
|
581
|
my @values = $self->follow($node_number, $column_names[$i]); |
723
|
215
|
|
|
|
|
50316
|
my $column_value = pop(@values); |
724
|
|
|
|
|
|
|
# If no $value (last column may be blank, which returns undef), ignore. |
725
|
215
|
50
|
|
|
|
427
|
if ($column_value) { |
726
|
|
|
|
|
|
|
# Quote if necessary |
727
|
215
|
50
|
|
|
|
511
|
$column_value = "'".$column_value."'" if ($column_value =~ m/\s+/); |
728
|
|
|
|
|
|
|
# Pretty printing |
729
|
215
|
|
|
|
|
221
|
$line .= $column_value; |
730
|
215
|
|
|
|
|
1055
|
$line .= " " x ($column_pos[$i] - length($column_value) + $column_indent); |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
} |
733
|
215
|
|
|
|
|
463
|
$tree_structure .= "$line\n"; |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
# Insert columns at top of tree contents |
737
|
20
|
|
|
|
|
39
|
my $header = $first_indent_string; |
738
|
20
|
|
|
|
|
48
|
for (my $i = 0; $i < @column_names; $i++) { |
739
|
40
|
|
|
|
|
48
|
$header .= $column_names[$i]; |
740
|
|
|
|
|
|
|
# Dirty hack |
741
|
40
|
100
|
|
|
|
66
|
if ($i == 0) { |
742
|
20
|
|
|
|
|
88
|
$header .= " " x ($column_pos[$i] - length($column_names[$i]) - $first_indent + $column_indent - 1); |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
else { |
745
|
20
|
|
|
|
|
62
|
$header .= " " x ($column_pos[$i] - length($column_names[$i]) + $column_indent); |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
} |
748
|
20
|
|
|
|
|
30
|
$header .= "\n"; |
749
|
|
|
|
|
|
|
# Add underscore to columns, replace all non-space characters with '-' |
750
|
20
|
|
|
|
|
190
|
(my $underscore = $header) =~ s/\S/-/g; |
751
|
|
|
|
|
|
|
# Replace first character with a comment sign |
752
|
20
|
|
|
|
|
78
|
$underscore =~ s/^./\#/g; |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# Insert comments at top |
755
|
20
|
|
|
|
|
28
|
my $package = __PACKAGE__ || ''; |
756
|
20
|
|
50
|
|
|
220
|
my $method = (caller(0))[3] || ''; |
757
|
|
|
|
|
|
|
# Replace last :: with -> |
758
|
20
|
|
|
|
|
172
|
$method =~ s/$package\:\:/->/; |
759
|
20
|
50
|
|
|
|
45
|
$method .= '()' if $method; |
760
|
20
|
|
|
|
|
61
|
my $comments = <
|
761
|
|
|
|
|
|
|
# Tree contents generated by $package$method. |
762
|
|
|
|
|
|
|
# Redirect this output to a file called for example 'tree.txt'. |
763
|
|
|
|
|
|
|
# To create a tree object, use the $package->readFile() method with 'tree.txt' as the filename argument. |
764
|
|
|
|
|
|
|
# For details, check the $package documentation. |
765
|
|
|
|
|
|
|
# |
766
|
|
|
|
|
|
|
COMMENT |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
# Return the entire output |
769
|
20
|
|
|
|
|
86
|
my $output = $comments.$header.$underscore.$tree_structure; |
770
|
20
|
|
|
|
|
162
|
return $output; |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
=cut |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
=head2 outputArray() |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
outputArray(); |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
The outputArray() method returns a Perl code snippet for creating a new tree object based on the current tree object, using an array reference and the readArray() method. |
780
|
|
|
|
|
|
|
The purpouse of this method is to easily create Perl code from whatever tree source, possibly modify/add/delete elements (nodes) in the array reference, and then use the readArray() method to create a new tree object. |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
=cut |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
sub outputArray { |
785
|
|
|
|
|
|
|
|
786
|
15
|
|
|
15
|
1
|
8801
|
my $self = shift; |
787
|
|
|
|
|
|
|
# my %args = ( |
788
|
|
|
|
|
|
|
# @_, # argument pair list goes here |
789
|
|
|
|
|
|
|
# ); |
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
# Get list of column names |
792
|
15
|
|
|
|
|
118
|
my $column_names_ref = $self->getColumnNames(); |
793
|
15
|
|
|
|
|
49
|
my @column_names = @$column_names_ref; |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
# If column names aren't defined ($column_names_ref returns undef), use tree field names (arbitrary order). |
796
|
15
|
|
|
|
|
91
|
my @field_names = $self->getFieldNames; |
797
|
15
|
50
|
|
|
|
174
|
if (!$column_names_ref) { |
798
|
0
|
|
|
|
|
0
|
@column_names = @field_names |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
# Insert required columns: |
801
|
15
|
|
|
|
|
125
|
my @required_column_names = ('serial', 'parent'); |
802
|
|
|
|
|
|
|
|
803
|
15
|
|
|
|
|
154
|
my $arrayref_code = |
804
|
|
|
|
|
|
|
'my $arrayref = [ |
805
|
|
|
|
|
|
|
[qw('.join(' ', @required_column_names, @column_names).')], |
806
|
|
|
|
|
|
|
[1, 0, ' . "'ROOT', " x @column_names . '], |
807
|
|
|
|
|
|
|
'; |
808
|
|
|
|
|
|
|
|
809
|
15
|
|
|
|
|
137
|
foreach my $node_number ($self->listChildNumbers) { |
810
|
301
|
|
|
|
|
7876
|
my $node = $self->getSubTree($node_number); |
811
|
301
|
|
|
|
|
119712
|
my $parent_node = $node->getParentRef; |
812
|
301
|
|
|
|
|
1486
|
my $parent_number = $parent_node->getNumber; |
813
|
301
|
|
|
|
|
1300
|
my $value_code = ''; |
814
|
301
|
|
|
|
|
479
|
foreach my $column_name (@column_names) { |
815
|
|
|
|
|
|
|
# Last value in array contains this node's value |
816
|
602
|
|
|
|
|
1738
|
my @values = $self->follow($node_number, $column_name); |
817
|
602
|
|
|
|
|
158515
|
my $value = pop(@values); |
818
|
|
|
|
|
|
|
# Set value to empty string if undefined |
819
|
602
|
50
|
|
|
|
1449
|
$value = '' if !$value; |
820
|
|
|
|
|
|
|
# Escape possible quote characters in values |
821
|
602
|
|
|
|
|
871
|
$value =~ s/\'/\\\'/g; |
822
|
|
|
|
|
|
|
# Add quotes and comma |
823
|
602
|
|
|
|
|
1759
|
$value_code .= "'$value', "; |
824
|
|
|
|
|
|
|
} |
825
|
301
|
|
|
|
|
1079
|
$arrayref_code .= " [$node_number, $parent_number, $value_code],\n"; |
826
|
|
|
|
|
|
|
} |
827
|
|
|
|
|
|
|
$arrayref_code .= |
828
|
15
|
|
|
|
|
192
|
' ]; |
829
|
|
|
|
|
|
|
'; |
830
|
|
|
|
|
|
|
|
831
|
15
|
|
|
|
|
32
|
my $extra_code = '# Create a new tree object using the array above |
832
|
|
|
|
|
|
|
my $use_column_names = 1; |
833
|
|
|
|
|
|
|
my $tree = Tree::Numbered::Tools->readArray( |
834
|
|
|
|
|
|
|
arrayref => $arrayref, |
835
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
836
|
|
|
|
|
|
|
); |
837
|
|
|
|
|
|
|
# Display the Perl code for the created object |
838
|
|
|
|
|
|
|
print $tree->outputArray(); |
839
|
|
|
|
|
|
|
'; |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
# Insert comments at top |
842
|
15
|
|
|
|
|
25
|
my $package = __PACKAGE__ || ''; |
843
|
15
|
|
50
|
|
|
383
|
my $method = (caller(0))[3] || ''; |
844
|
|
|
|
|
|
|
# Replace last :: with -> |
845
|
15
|
|
|
|
|
185
|
$method =~ s/$package\:\:/->/; |
846
|
15
|
50
|
|
|
|
117
|
$method .= '()' if $method; |
847
|
15
|
|
|
|
|
52
|
my $comments = <
|
848
|
|
|
|
|
|
|
# |
849
|
|
|
|
|
|
|
# Perl code generated by $package$method. |
850
|
|
|
|
|
|
|
# Redirect this output to a file called for example 'tree.pl'. |
851
|
|
|
|
|
|
|
# The run from the command line: |
852
|
|
|
|
|
|
|
# perl -w tree.pl |
853
|
|
|
|
|
|
|
# For details, check the $package documentation. |
854
|
|
|
|
|
|
|
# |
855
|
|
|
|
|
|
|
COMMENT |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
# Insert program header |
858
|
|
|
|
|
|
|
### my $perl_binary = $^X; # BUGFIX: Normally shows just 'perl' instead of '/usr/bin/perl' |
859
|
15
|
|
|
|
|
86133
|
my $perl_binary = `which perl`; |
860
|
15
|
|
|
|
|
207
|
chomp $perl_binary; |
861
|
15
|
|
|
|
|
162
|
my $header = '#!' . $perl_binary . " -w\n"; |
862
|
15
|
|
|
|
|
32
|
$header .= "use strict;\n"; |
863
|
15
|
|
|
|
|
55
|
$header .= "use $package;\n"; |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# Return the entire output (complete program snippet) |
866
|
15
|
|
|
|
|
160
|
my $output = $header.$comments.$arrayref_code.$extra_code; |
867
|
15
|
|
|
|
|
1844
|
return $output; |
868
|
|
|
|
|
|
|
} |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
=cut |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
=head2 outputSQL() |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
outputSQL( |
875
|
|
|
|
|
|
|
table => $table, |
876
|
|
|
|
|
|
|
dbs => $dbs, |
877
|
|
|
|
|
|
|
drop => $drop, |
878
|
|
|
|
|
|
|
); |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
The outputSQL() method returns SQL statements for creating records in the database table $table. |
881
|
|
|
|
|
|
|
The purpouse of this method is to create SQL statements for later use. |
882
|
|
|
|
|
|
|
If you want to create the records instead of the SQL stataments, use the outputDB() method instead. |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
The $dbs argument is optional, sets the database server type, defaults to 'mysql'. |
885
|
|
|
|
|
|
|
Currently supported database server types are MySQL and PostgreSQL. |
886
|
|
|
|
|
|
|
Due to inconsistent naming convention for PostgreSQL ($dbh->{Driver}->{Name} returns 'Pg' while $dbh->get_info( SQL_DBMS_NAME ) returns 'PostgreSQL'), valid 'dbs' values when using PostgreSQL are: 'postgres', 'PostgreSQL', 'PgSQL', and 'Pg'. |
887
|
|
|
|
|
|
|
The 'dbs' argument is case-insensitive. |
888
|
|
|
|
|
|
|
The generated SQL code has been tested with MySQL 5.0.77 and PostgreSQL 8.2.13 on FreeBSD 7.2, but may need modification for use with other database servers/versions/platforms. |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
The $drop argument is optional, if true (false by default), inserts a DROP TABLE statement before the CREATE TABLE statement. |
891
|
|
|
|
|
|
|
If false, the DROP TABLE statement will be left outcommented. |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
=cut |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
sub outputSQL { |
896
|
|
|
|
|
|
|
|
897
|
60
|
|
|
60
|
1
|
7836
|
my $self = shift; |
898
|
60
|
|
|
|
|
520
|
my %args = ( |
899
|
|
|
|
|
|
|
table => '', |
900
|
|
|
|
|
|
|
dbs => 'mysql', |
901
|
|
|
|
|
|
|
drop => '', |
902
|
|
|
|
|
|
|
@_, # argument pair list goes here |
903
|
|
|
|
|
|
|
); |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
# Die on missing table name |
906
|
60
|
50
|
|
|
|
186
|
my $table = $args{table} or croak "Missing table name"; |
907
|
60
|
|
|
|
|
100
|
my $dbs = $args{dbs}; |
908
|
60
|
|
|
|
|
110
|
my $drop = $args{drop}; |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
# Get all SQL statements into array refs |
911
|
60
|
|
|
|
|
288
|
my $sql_statements_ref = $self->_sql_statements(%args); |
912
|
60
|
|
|
|
|
198
|
my @sql_statements = @$sql_statements_ref; |
913
|
60
|
|
|
|
|
136
|
my ($sql_header_ref, $drop_table_ref, $create_table_ref, $insert_into_ref, $create_index_ref, $comments_ref) = @sql_statements; |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
# Format SQL statements and comments for string output |
916
|
60
|
|
|
|
|
91
|
my $comments_header = $comments_ref->[0]; |
917
|
60
|
|
|
|
|
107
|
my $comments1 = $comments_ref->[1]; |
918
|
60
|
100
|
|
|
|
188
|
$comments1 .= "\n" if $comments1; |
919
|
60
|
|
|
|
|
95
|
my $comments2 = $comments_ref->[2]; |
920
|
60
|
100
|
|
|
|
112
|
$comments2 .= "\n" if $comments2; |
921
|
60
|
|
|
|
|
190
|
my $sql_header = join("\n", @$sql_header_ref); |
922
|
60
|
100
|
|
|
|
129
|
$sql_header .= "\n" if $sql_header; |
923
|
60
|
|
|
|
|
148
|
my $drop_table = join("\n", @$drop_table_ref); |
924
|
60
|
50
|
|
|
|
138
|
$drop_table .= "\n" if $drop_table; |
925
|
60
|
|
|
|
|
113
|
my $create_table = join("\n", @$create_table_ref); |
926
|
60
|
50
|
|
|
|
153
|
$create_table .= "\n" if $create_table; |
927
|
60
|
|
|
|
|
361
|
my $insert_into = join("\n", @$insert_into_ref); |
928
|
60
|
50
|
|
|
|
140
|
$insert_into .= "\n" if $insert_into; |
929
|
60
|
|
|
|
|
112
|
my $create_index = join("\n", @$create_index_ref); |
930
|
60
|
100
|
|
|
|
123
|
$create_index .= "\n" if $create_index; |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
# Return the entire output (SQL statements and comments) |
933
|
60
|
|
|
|
|
341
|
my $output = $comments_header.$sql_header.$comments1.$drop_table.$create_table.$insert_into.$comments2.$create_index; |
934
|
60
|
|
|
|
|
1449
|
return $output; |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
=cut |
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
=head2 outputDB() |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
outputDB( |
943
|
|
|
|
|
|
|
dbh => $dbh, |
944
|
|
|
|
|
|
|
table => $table, |
945
|
|
|
|
|
|
|
drop => $drop, |
946
|
|
|
|
|
|
|
); |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
The outputDB() method creates a database table $table using the database handle $dbh, and insert tree nodes as table records. |
949
|
|
|
|
|
|
|
The purpouse of this method is to store a tree in a table. The tree object can be recreated by using one of the readSQL() or readDB methods. |
950
|
|
|
|
|
|
|
This method uses outputSQL() internally to get the SQL statements, and executes them. |
951
|
|
|
|
|
|
|
If you want to tie a tree object to a database table in "real time", first use this method with an existing tree object to create the database table. Then create a tree object using the Tree::Numbered::DB module by Yosef Meller, which will reflect changes in the database table as you modify the tree nodes. |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
The $dbh is a database handle. |
954
|
|
|
|
|
|
|
The $table and $drop arguments are the same as for outputSQL(). |
955
|
|
|
|
|
|
|
There is no $dbs argument, as the database server type is determined by the $dbh argument ($dbh->{Driver}->{Name} more exactly). |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=cut |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
sub outputDB { |
960
|
|
|
|
|
|
|
|
961
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
962
|
0
|
|
|
|
|
0
|
my %args = ( |
963
|
|
|
|
|
|
|
dbh => '', |
964
|
|
|
|
|
|
|
table => '', |
965
|
|
|
|
|
|
|
drop => '', |
966
|
|
|
|
|
|
|
@_, # argument pair list goes here |
967
|
|
|
|
|
|
|
); |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
# Die on missing DB handle and/or table name |
970
|
0
|
0
|
|
|
|
0
|
my $dbh = $args{dbh} or croak "Missing DB handle"; |
971
|
0
|
0
|
|
|
|
0
|
my $table = $args{table} or croak "Missing table name"; |
972
|
0
|
|
|
|
|
0
|
$args{dbs} = $dbh->{Driver}->{Name}; |
973
|
0
|
|
|
|
|
0
|
my $dbs = $args{dbs}; |
974
|
0
|
|
|
|
|
0
|
my $drop = $args{drop}; |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
# Get all SQL statements into array refs |
977
|
0
|
|
|
|
|
0
|
my $sql_statements_ref = $self->_sql_statements(%args); |
978
|
0
|
|
|
|
|
0
|
my @sql_statements = @$sql_statements_ref; |
979
|
0
|
|
|
|
|
0
|
my ($sql_header_ref, $drop_table_ref, $create_table_ref, $insert_into_ref, $create_index_ref, $comments_ref) = @sql_statements; |
980
|
|
|
|
|
|
|
|
981
|
0
|
|
|
|
|
0
|
my $sql = ''; |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
# We will not execute comments nor empty strings in array elements. |
984
|
|
|
|
|
|
|
# Execute SQL headers, if any |
985
|
0
|
|
|
|
|
0
|
foreach (@$sql_header_ref) { |
986
|
0
|
|
|
|
|
0
|
$sql = $_; |
987
|
0
|
0
|
|
|
|
0
|
if ($sql) { |
988
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
989
|
|
|
|
|
|
|
} |
990
|
|
|
|
|
|
|
} |
991
|
|
|
|
|
|
|
# Execute DROP TABLE, if $drop |
992
|
0
|
0
|
|
|
|
0
|
if ($drop) { |
993
|
|
|
|
|
|
|
### $sql = $drop_table_ref->[0]; |
994
|
0
|
|
|
|
|
0
|
$sql = $drop_table_ref->[1]; |
995
|
0
|
0
|
|
|
|
0
|
if ($sql) { |
996
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
} |
999
|
|
|
|
|
|
|
# Execute CREATE TABLE |
1000
|
0
|
|
|
|
|
0
|
$sql = $create_table_ref->[0]; |
1001
|
0
|
0
|
|
|
|
0
|
if ($sql) { |
1002
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
1003
|
|
|
|
|
|
|
} |
1004
|
|
|
|
|
|
|
# Execute INSERT INTO statements |
1005
|
0
|
|
|
|
|
0
|
foreach (@$insert_into_ref) { |
1006
|
0
|
|
|
|
|
0
|
$sql = $_; |
1007
|
0
|
0
|
|
|
|
0
|
if ($sql) { |
1008
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
1009
|
|
|
|
|
|
|
} |
1010
|
|
|
|
|
|
|
} |
1011
|
|
|
|
|
|
|
# Execute CREATE INDEX statements, if any |
1012
|
0
|
|
|
|
|
0
|
foreach (@$create_index_ref) { |
1013
|
0
|
|
|
|
|
0
|
$sql = $_; |
1014
|
0
|
0
|
|
|
|
0
|
if ($sql) { |
1015
|
0
|
0
|
|
|
|
0
|
$dbh->do($sql) or croak $dbh->errstr; |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
} |
1018
|
|
|
|
|
|
|
|
1019
|
0
|
|
|
|
|
0
|
return 1; |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
=cut |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
=head2 convertFile2Array() |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
convertFile2Array( |
1028
|
|
|
|
|
|
|
filename => $filename, |
1029
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
1030
|
|
|
|
|
|
|
); |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
Calls readFile() followed by outputArray(). |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
=cut |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
sub convertFile2Array { |
1037
|
10
|
|
|
10
|
1
|
8309
|
my $self = shift; |
1038
|
|
|
|
|
|
|
### my $tree = $self->readFile(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1039
|
10
|
|
|
|
|
322
|
my $tree = Tree::Numbered::Tools->readFile(@_); # SOLUTION: Always use a new tree object. |
1040
|
10
|
|
|
|
|
53
|
return $tree->outputArray(); |
1041
|
|
|
|
|
|
|
} |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
=cut |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
=head2 convertFile2SQL() |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
convertFile2SQL( |
1048
|
|
|
|
|
|
|
filename => $filename, |
1049
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
1050
|
|
|
|
|
|
|
table => $table, |
1051
|
|
|
|
|
|
|
dbs => $dbs, |
1052
|
|
|
|
|
|
|
drop => $drop, |
1053
|
|
|
|
|
|
|
); |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
Calls readFile() followed by outputSQL(). |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
=cut |
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
sub convertFile2SQL { |
1060
|
20
|
|
|
20
|
1
|
6459
|
my $self = shift; |
1061
|
|
|
|
|
|
|
### my $tree = $self->readFile(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1062
|
20
|
|
|
|
|
183
|
my $tree = Tree::Numbered::Tools->readFile(@_); # SOLUTION: Always use a new tree object. |
1063
|
20
|
|
|
|
|
150
|
return $tree->outputSQL(@_); |
1064
|
|
|
|
|
|
|
} |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
=cut |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
=head2 convertFile2DB() |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
convertFile2DB( |
1071
|
|
|
|
|
|
|
filename => $filename, |
1072
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
1073
|
|
|
|
|
|
|
dbh => $dbh, |
1074
|
|
|
|
|
|
|
table => $table, |
1075
|
|
|
|
|
|
|
drop => $drop, |
1076
|
|
|
|
|
|
|
); |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
Calls readFile() followed by outputDB(). |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
=cut |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
sub convertFile2DB { |
1083
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1084
|
|
|
|
|
|
|
### my $tree = $self->readFile(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1085
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readFile(@_); # SOLUTION: Always use a new tree object. |
1086
|
0
|
|
|
|
|
0
|
return $tree->outputDB(@_); |
1087
|
|
|
|
|
|
|
} |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
=cut |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
=head2 convertArray2File() |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
convertArray2File( |
1094
|
|
|
|
|
|
|
arrayref => $arrayref, |
1095
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
1096
|
|
|
|
|
|
|
first_indent => $first_indent, |
1097
|
|
|
|
|
|
|
level_indent => $level_indent, |
1098
|
|
|
|
|
|
|
column_indent => $column_indent, |
1099
|
|
|
|
|
|
|
); |
1100
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
Calls readArray() followed by outputFile(). |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
=cut |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
sub convertArray2File { |
1106
|
15
|
|
|
15
|
1
|
2583
|
my $self = shift; |
1107
|
|
|
|
|
|
|
### my $tree = $self->readArray(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1108
|
15
|
|
|
|
|
58
|
my $tree = Tree::Numbered::Tools->readArray(@_); # SOLUTION: Always use a new tree object. |
1109
|
15
|
|
|
|
|
57
|
return $tree->outputFile(@_); |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
=cut |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=head2 convertArray2SQL() |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
convertArray2SQL( |
1117
|
|
|
|
|
|
|
arrayref => $arrayref, |
1118
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
1119
|
|
|
|
|
|
|
table => $table, |
1120
|
|
|
|
|
|
|
dbs => $dbs, |
1121
|
|
|
|
|
|
|
drop => $drop, |
1122
|
|
|
|
|
|
|
); |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
Calls readArray() followed by outputSQL(). |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
=cut |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
sub convertArray2SQL { |
1129
|
30
|
|
|
30
|
1
|
3839
|
my $self = shift; |
1130
|
|
|
|
|
|
|
### my $tree = $self->readArray(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1131
|
30
|
|
|
|
|
108
|
my $tree = Tree::Numbered::Tools->readArray(@_); # SOLUTION: Always use a new tree object. |
1132
|
30
|
|
|
|
|
96
|
return $tree->outputSQL(@_); |
1133
|
|
|
|
|
|
|
} |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
=cut |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
=head2 convertArray2DB() |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
convertArray2DB( |
1140
|
|
|
|
|
|
|
arrayref => $arrayref, |
1141
|
|
|
|
|
|
|
use_column_names => $use_column_names, |
1142
|
|
|
|
|
|
|
dbh => $dbh, |
1143
|
|
|
|
|
|
|
table => $table, |
1144
|
|
|
|
|
|
|
drop => $drop, |
1145
|
|
|
|
|
|
|
); |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
Calls readArray() followed by outputDB(). |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
=cut |
1150
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
sub convertArray2DB { |
1152
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1153
|
|
|
|
|
|
|
### my $tree = $self->readArray(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1154
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readArray(@_); # SOLUTION: Always use a new tree object. |
1155
|
0
|
|
|
|
|
0
|
return $tree->outputDB(@_); |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
=cut |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
=head2 convertSQL2File() |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
convertSQL2File( |
1163
|
|
|
|
|
|
|
dbh => $dbh, |
1164
|
|
|
|
|
|
|
sql => $sql, |
1165
|
|
|
|
|
|
|
first_indent => $first_indent, |
1166
|
|
|
|
|
|
|
level_indent => $level_indent, |
1167
|
|
|
|
|
|
|
column_indent => $column_indent, |
1168
|
|
|
|
|
|
|
); |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
Calls readSQL() followed by outputFile(). |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
=cut |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
sub convertSQL2File { |
1175
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1176
|
|
|
|
|
|
|
### my $tree = $self->readSQL(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1177
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readSQL(@_); # SOLUTION: Always use a new tree object. |
1178
|
0
|
|
|
|
|
0
|
return $tree->outputFile(@_); |
1179
|
|
|
|
|
|
|
} |
1180
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
=cut |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
=head2 convertSQL2Array() |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
convertSQL2Array( |
1186
|
|
|
|
|
|
|
dbh => $dbh, |
1187
|
|
|
|
|
|
|
sql => $sql, |
1188
|
|
|
|
|
|
|
); |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
Calls readSQL() followed by outputArray(). |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
=cut |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
sub convertSQL2Array { |
1195
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1196
|
|
|
|
|
|
|
### my $tree = $self->readSQL(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1197
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readSQL(@_); # SOLUTION: Always use a new tree object. |
1198
|
0
|
|
|
|
|
0
|
return $tree->outputArray(@_); |
1199
|
|
|
|
|
|
|
} |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
=cut |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
=head2 convertSQL2DB() |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
convertSQL2DB( |
1206
|
|
|
|
|
|
|
dbh => $dbh, |
1207
|
|
|
|
|
|
|
sql => $sql, |
1208
|
|
|
|
|
|
|
dbh_dest => $dbh_dest, |
1209
|
|
|
|
|
|
|
table => $table, |
1210
|
|
|
|
|
|
|
drop => $drop, |
1211
|
|
|
|
|
|
|
); |
1212
|
|
|
|
|
|
|
|
1213
|
|
|
|
|
|
|
Calls readSQL() followed by outputDB(). |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
NOTE: There are two database handles, $dbh and $dbh_dest, in case you use one database as a source and another as destination. The argument $dbh_dest is optional, defaults to $dbh, assumes using the same database handle for both source and destination. |
1216
|
|
|
|
|
|
|
Using different database handles, this method can be useful to migrate a tree table from MySQL to PostgreSQL, for example. |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
=cut |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
sub convertSQL2DB { |
1221
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1222
|
0
|
|
|
|
|
0
|
my %args_sql = ( |
1223
|
|
|
|
|
|
|
dbh => '', |
1224
|
|
|
|
|
|
|
sql => '', |
1225
|
|
|
|
|
|
|
@_, # argument pair list goes here |
1226
|
|
|
|
|
|
|
); |
1227
|
0
|
|
|
|
|
0
|
my %args_db = ( |
1228
|
|
|
|
|
|
|
dbh_dest => $args_sql{dbh}, |
1229
|
|
|
|
|
|
|
table => '', |
1230
|
|
|
|
|
|
|
drop => '', |
1231
|
|
|
|
|
|
|
@_, # argument pair list goes here |
1232
|
|
|
|
|
|
|
); |
1233
|
0
|
|
|
|
|
0
|
$args_db{dbh} = $args_db{dbh_dest}; |
1234
|
|
|
|
|
|
|
### my $tree = $self->readSQL(%args_sql); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1235
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readSQL(%args_sql); # SOLUTION: Always use a new tree object. |
1236
|
0
|
|
|
|
|
0
|
return $tree->outputDB(%args_db); |
1237
|
|
|
|
|
|
|
} |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
=cut |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=head2 convertDB2File() |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
convertDB2File( |
1244
|
|
|
|
|
|
|
dbh => $dbh, |
1245
|
|
|
|
|
|
|
table => $table, |
1246
|
|
|
|
|
|
|
first_indent => $first_indent, |
1247
|
|
|
|
|
|
|
level_indent => $level_indent, |
1248
|
|
|
|
|
|
|
column_indent => $column_indent, |
1249
|
|
|
|
|
|
|
); |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
Calls readDB() followed by outputFile(). |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
=cut |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
sub convertDB2File { |
1256
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1257
|
|
|
|
|
|
|
### my $tree = $self->readDB(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1258
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readDB(@_); # SOLUTION: Always use a new tree object. |
1259
|
0
|
|
|
|
|
0
|
return $tree->outputFile(@_); |
1260
|
|
|
|
|
|
|
} |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
=cut |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
=head2 convertDB2Array() |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
convertDB2Array( |
1267
|
|
|
|
|
|
|
dbh => $dbh, |
1268
|
|
|
|
|
|
|
table => $table, |
1269
|
|
|
|
|
|
|
); |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
Calls readDB() followed by outputArray(). |
1272
|
|
|
|
|
|
|
|
1273
|
|
|
|
|
|
|
=cut |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
sub convertDB2Array { |
1276
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1277
|
|
|
|
|
|
|
### my $tree = $self->readDB(@_); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1278
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readDB(@_); # SOLUTION: Always use a new tree object. |
1279
|
0
|
|
|
|
|
0
|
return $tree->outputArray(@_); |
1280
|
|
|
|
|
|
|
} |
1281
|
|
|
|
|
|
|
|
1282
|
|
|
|
|
|
|
=cut |
1283
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
=head2 convertDB2SQL() |
1285
|
|
|
|
|
|
|
|
1286
|
|
|
|
|
|
|
convertDB2SQL( |
1287
|
|
|
|
|
|
|
dbh => $dbh, |
1288
|
|
|
|
|
|
|
sql => $sql, |
1289
|
|
|
|
|
|
|
table => $table, |
1290
|
|
|
|
|
|
|
table_dest => $table_dest, |
1291
|
|
|
|
|
|
|
dbs => $dbs, |
1292
|
|
|
|
|
|
|
drop => $drop, |
1293
|
|
|
|
|
|
|
); |
1294
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
Calls readDB() followed by outputSQL(). |
1296
|
|
|
|
|
|
|
NOTE: $table is the source table, $table_dest is the table name used in the generated SQL statements. |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
=cut |
1299
|
|
|
|
|
|
|
|
1300
|
|
|
|
|
|
|
sub convertDB2SQL { |
1301
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1302
|
0
|
|
|
|
|
0
|
my %args_db = ( |
1303
|
|
|
|
|
|
|
dbh => '', |
1304
|
|
|
|
|
|
|
sql => '', |
1305
|
|
|
|
|
|
|
table => '', |
1306
|
|
|
|
|
|
|
@_, # argument pair list goes here |
1307
|
|
|
|
|
|
|
); |
1308
|
0
|
|
|
|
|
0
|
my %args_sql = ( |
1309
|
|
|
|
|
|
|
table_dest => '', |
1310
|
|
|
|
|
|
|
dbs => 'mysql', |
1311
|
|
|
|
|
|
|
drop => '', |
1312
|
|
|
|
|
|
|
@_, # argument pair list goes here |
1313
|
|
|
|
|
|
|
); |
1314
|
0
|
|
|
|
|
0
|
$args_sql{table} = $args_db{table_dest}; |
1315
|
|
|
|
|
|
|
### my $tree = $self->readDB(%args_db); # BUG: Using an existing tree object, the tree nodes are not replaced. |
1316
|
0
|
|
|
|
|
0
|
my $tree = Tree::Numbered::Tools->readDB(%args_db); # SOLUTION: Always use a new tree object. |
1317
|
0
|
|
|
|
|
0
|
return $tree->outputSQL(%args_sql); |
1318
|
|
|
|
|
|
|
} |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
=cut |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
=head2 getColumnNames() |
1323
|
|
|
|
|
|
|
|
1324
|
|
|
|
|
|
|
Returns a list (in array context) or a ref to a list (in scalar context) of the column names. |
1325
|
|
|
|
|
|
|
The list corresponds to: |
1326
|
|
|
|
|
|
|
Using a file - the words on the first non-comment or blank line. |
1327
|
|
|
|
|
|
|
Using an array - the first array row. |
1328
|
|
|
|
|
|
|
Using an SQL statement - the SQL field names |
1329
|
|
|
|
|
|
|
Using a database table - the table column names |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
Using this method on a tree created using with use_column_names set to 0 returns the default column names: 'Value', 'Value2', 'Value3', etc. |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
=cut |
1334
|
|
|
|
|
|
|
|
1335
|
|
|
|
|
|
|
sub getColumnNames { |
1336
|
101
|
|
|
101
|
1
|
4374
|
my $self = shift; |
1337
|
101
|
|
|
|
|
184
|
my $ary_ref = $self->{COLUMN_NAMES_REF}; |
1338
|
101
|
|
|
|
|
315
|
my @ary = @$ary_ref; |
1339
|
101
|
100
|
|
|
|
429
|
return (wantarray) ? @ary : $ary_ref; |
1340
|
|
|
|
|
|
|
} |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
=head2 getSourceType() |
1343
|
|
|
|
|
|
|
|
1344
|
|
|
|
|
|
|
Returns one of the strings 'File', 'Array', 'SQL', 'DB' depending on which source was used to create the tree object. |
1345
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
=cut |
1347
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
sub getSourceType { |
1349
|
5
|
|
|
5
|
1
|
1652
|
my $self = shift; |
1350
|
5
|
|
|
|
|
13
|
return $self->{SOURCE_TYPE}; |
1351
|
|
|
|
|
|
|
} |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
=head2 getSourceName() |
1354
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
Returns the file name if the source type is 'File', or the database table name if the source type is 'DB'. |
1356
|
|
|
|
|
|
|
Returns undef if source type is 'Array' or 'SQL'. |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
=cut |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
sub getSourceName { |
1361
|
5
|
|
|
5
|
1
|
2023
|
my $self = shift; |
1362
|
5
|
|
|
|
|
17
|
return $self->{SOURCE_NAME}; |
1363
|
|
|
|
|
|
|
} |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
# version |
1366
|
|
|
|
|
|
|
sub version{ |
1367
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1368
|
0
|
|
|
|
|
0
|
return $VERSION; |
1369
|
|
|
|
|
|
|
} |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
#----------- Internal subs ------------- |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
# Get column names from file (internal use only) |
1374
|
|
|
|
|
|
|
# Use getColumnNames from the outside world |
1375
|
|
|
|
|
|
|
sub _getColumnNamesFile { |
1376
|
32
|
|
|
32
|
|
55
|
my $self = shift; |
1377
|
32
|
|
|
|
|
98
|
my $first_line = shift; |
1378
|
32
|
|
|
|
|
299
|
my @column_names = &parse_line('\s+', 0, $first_line); |
1379
|
32
|
|
|
|
|
3888
|
return \@column_names; |
1380
|
|
|
|
|
|
|
} |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
# Get the max number of columns in a file contents, passed as an array of lines. |
1383
|
|
|
|
|
|
|
sub _getMaxColumnsFile { |
1384
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1385
|
0
|
|
|
|
|
0
|
my $lines_ref = shift; |
1386
|
0
|
|
|
|
|
0
|
my @lines = @$lines_ref; |
1387
|
0
|
|
|
|
|
0
|
my $max_cols = 0; |
1388
|
0
|
|
|
|
|
0
|
foreach my $line (@lines) { |
1389
|
0
|
|
|
|
|
0
|
my @columns = &parse_line('\s+', 0, $line); |
1390
|
0
|
0
|
|
|
|
0
|
$max_cols = scalar(@columns) if (scalar(@columns) > $max_cols); |
1391
|
|
|
|
|
|
|
} |
1392
|
0
|
|
|
|
|
0
|
return $max_cols; |
1393
|
|
|
|
|
|
|
} |
1394
|
|
|
|
|
|
|
|
1395
|
|
|
|
|
|
|
sub _trim { |
1396
|
768
|
|
|
768
|
|
1039
|
my $self = shift; |
1397
|
768
|
|
|
|
|
1509
|
my @s = @_; |
1398
|
768
|
|
|
|
|
1350
|
for (@s) { |
1399
|
768
|
|
|
|
|
2853
|
s/^\s+//; |
1400
|
768
|
|
|
|
|
3901
|
s/\s+$//; |
1401
|
|
|
|
|
|
|
} |
1402
|
768
|
50
|
|
|
|
2645
|
return wantarray ? @s : $s[0]; |
1403
|
|
|
|
|
|
|
} |
1404
|
|
|
|
|
|
|
|
1405
|
|
|
|
|
|
|
sub _strip_quotes { |
1406
|
736
|
|
|
736
|
|
995
|
my $self = shift; |
1407
|
736
|
|
|
|
|
1688
|
my @s = @_; |
1408
|
736
|
|
|
|
|
1275
|
for (@s) { |
1409
|
1472
|
|
|
|
|
2175
|
s/^\'(.*)\'$/$1/; |
1410
|
1472
|
|
|
|
|
2970
|
s/^\"(.*)\"$/$1/; |
1411
|
|
|
|
|
|
|
# s/^[\'|\"]//; |
1412
|
|
|
|
|
|
|
# s/[\'|\"]$//; |
1413
|
|
|
|
|
|
|
} |
1414
|
736
|
50
|
|
|
|
9744
|
return wantarray ? @s : $s[0]; |
1415
|
|
|
|
|
|
|
} |
1416
|
|
|
|
|
|
|
|
1417
|
|
|
|
|
|
|
sub _indented { |
1418
|
1536
|
|
|
1536
|
|
1769
|
my $self = shift; |
1419
|
1536
|
|
|
|
|
2019
|
my $s = shift; |
1420
|
1536
|
|
|
|
|
5363
|
$s =~ s/^(\s*).*/$1/; |
1421
|
1536
|
|
|
|
|
3996
|
return length($s); |
1422
|
|
|
|
|
|
|
} |
1423
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
sub _isInteger { |
1425
|
902
|
|
|
902
|
|
783
|
my $string = shift; |
1426
|
902
|
50
|
|
|
|
4059
|
return ($string =~ /^[+-]?\d+$/) ? 1 : 0; |
1427
|
|
|
|
|
|
|
} |
1428
|
|
|
|
|
|
|
|
1429
|
|
|
|
|
|
|
# Quotes SQL aliases (the word that follows 'AS' in an SQL statement). |
1430
|
|
|
|
|
|
|
# Used by readSQL() to ensure all aliases are quoted. |
1431
|
|
|
|
|
|
|
# Unquoted aliases works on MySQL but not on PgSQL. |
1432
|
|
|
|
|
|
|
sub _sql_alias_quoted { |
1433
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1434
|
0
|
|
|
|
|
0
|
my $sql = shift; |
1435
|
|
|
|
|
|
|
# Split the SQL statement into an array of words. |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
# When found the word 'AS' (without quotes, case insensitive), the following word is an alias. |
1438
|
|
|
|
|
|
|
# If the following word (the alias) isn't double quoted, double quote it. |
1439
|
|
|
|
|
|
|
# It is possible to use a double quote character as part of the alias, escaping it with an extra double quote: |
1440
|
|
|
|
|
|
|
# SELECT serial as """SERIAL""" FROM treetest |
1441
|
|
|
|
|
|
|
# will create the alias "SERIAL", including the double quotes. |
1442
|
|
|
|
|
|
|
# This means, if the alias was quoted with 1, 3, 5, or any odd number of double quotes, there is no need to quote the alias, as it will work any way. |
1443
|
|
|
|
|
|
|
# If the alias was quoted with 2, 4, 6, or any even number of double quotes, there is no need to quote the alias, as the SQL statement was invalid anyway. ;-) |
1444
|
|
|
|
|
|
|
# Summary: never double quote an already double quoted alias. |
1445
|
|
|
|
|
|
|
|
1446
|
|
|
|
|
|
|
# It is possible to use a reserved SQL word as an alias, as long as it is quoted: |
1447
|
|
|
|
|
|
|
# SELECT serial AS "AS" from treetest |
1448
|
|
|
|
|
|
|
# On PgSQL, it even works without quotes: |
1449
|
|
|
|
|
|
|
# SELECT serial AS AS FROM treetest |
1450
|
|
|
|
|
|
|
# This could cause a parsing error, as the second AS could try to quote the following word ('FROM' in the example above). |
1451
|
|
|
|
|
|
|
# To avoid this, test exactly on the word 'AS' (without quotes). |
1452
|
|
|
|
|
|
|
# When found, the following word in the array will be double quoted. |
1453
|
|
|
|
|
|
|
# When testing the next element ('"AS"' in the example above) for the word 'AS', it will not match. |
1454
|
|
|
|
|
|
|
|
1455
|
|
|
|
|
|
|
# Quoted aliases may have spaces: |
1456
|
|
|
|
|
|
|
# SELECT serial AS "My Serial" FROM treetest |
1457
|
|
|
|
|
|
|
# This means that we can't just split on \s+ |
1458
|
|
|
|
|
|
|
# Solution: Text::ParseWords takes care of not splitting quoted words. Nevertheless, quotes have to be added, as Text::ParseWords removes them. |
1459
|
|
|
|
|
|
|
# The concern about aliases with spaces is to make this sub generic. |
1460
|
|
|
|
|
|
|
# Aliases with spaces will never occur generating a tree, as the aliases corresponds to the field names, which can contain spaces, so aliases with spaces will not work with trees. |
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
# Bugfix in 1.03: |
1463
|
|
|
|
|
|
|
# Warning message when SQL string contains trailing newline(s) |
1464
|
|
|
|
|
|
|
### chomp $sql; BAD SOLUTION: works ONLY for ones single trailing newline, not for two newlines. |
1465
|
|
|
|
|
|
|
# Better solution: trim leading and trailing whitespace characters [ \t\n\r\f]; |
1466
|
0
|
|
|
|
|
0
|
$sql =~ s/^\s+//; |
1467
|
0
|
|
|
|
|
0
|
$sql =~ s/\s+$//; |
1468
|
|
|
|
|
|
|
|
1469
|
0
|
|
|
|
|
0
|
my @words = &parse_line('\s+', 0, $sql); |
1470
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i < @words; $i++) { |
1471
|
|
|
|
|
|
|
# If reserved word AS, quote the following word |
1472
|
0
|
0
|
|
|
|
0
|
if (uc($words[$i]) eq 'AS') { |
1473
|
|
|
|
|
|
|
# Check for existing array element |
1474
|
0
|
0
|
|
|
|
0
|
if ($words[$i+1]) { |
1475
|
|
|
|
|
|
|
# The alias may include the following comma, which must follow the quote. |
1476
|
0
|
0
|
|
|
|
0
|
if ($words[$i+1] =~ m/\,$/) { |
1477
|
0
|
|
|
|
|
0
|
$words[$i+1] =~ s/\,$/\"\,/; |
1478
|
|
|
|
|
|
|
} |
1479
|
|
|
|
|
|
|
else { |
1480
|
0
|
|
|
|
|
0
|
$words[$i+1] .= '"'; |
1481
|
|
|
|
|
|
|
} |
1482
|
0
|
|
|
|
|
0
|
$words[$i+1] = '"'.$words[$i+1]; |
1483
|
|
|
|
|
|
|
} |
1484
|
|
|
|
|
|
|
} |
1485
|
|
|
|
|
|
|
#print $words[$i], "\n"; |
1486
|
|
|
|
|
|
|
} |
1487
|
0
|
|
|
|
|
0
|
$sql = join(' ', @words); |
1488
|
0
|
|
|
|
|
0
|
return $sql; |
1489
|
|
|
|
|
|
|
} |
1490
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
# Returns the SQL statements as an reference to a list of arrays references, where each element is one statement. |
1492
|
|
|
|
|
|
|
# The statements are separated by type: the CREATE TABLE statement goes in one array, all INSERT INTO statements in another array, etc. |
1493
|
|
|
|
|
|
|
sub _sql_statements { |
1494
|
60
|
|
|
60
|
|
84
|
my $self = shift; |
1495
|
|
|
|
|
|
|
|
1496
|
60
|
|
|
|
|
277
|
my %args = ( |
1497
|
|
|
|
|
|
|
table => '', |
1498
|
|
|
|
|
|
|
dbs => 'mysql', |
1499
|
|
|
|
|
|
|
drop => '', |
1500
|
|
|
|
|
|
|
@_, # argument pair list goes here |
1501
|
|
|
|
|
|
|
); |
1502
|
|
|
|
|
|
|
|
1503
|
|
|
|
|
|
|
# Die on missing table name |
1504
|
60
|
50
|
|
|
|
173
|
my $table = $args{table} or croak "Missing table name"; |
1505
|
60
|
|
|
|
|
83
|
my $dbs = $args{dbs}; |
1506
|
60
|
|
|
|
|
104
|
my $drop = $args{drop}; |
1507
|
|
|
|
|
|
|
|
1508
|
60
|
|
|
|
|
108
|
my @sql_header = (); |
1509
|
60
|
|
|
|
|
78
|
my @drop_index_and_table = (); |
1510
|
60
|
|
|
|
|
80
|
my @create_table = (); |
1511
|
60
|
|
|
|
|
65
|
my @insert_into = (); |
1512
|
60
|
|
|
|
|
87
|
my @create_index = (); |
1513
|
60
|
|
|
|
|
95
|
my @comments = (); |
1514
|
|
|
|
|
|
|
|
1515
|
|
|
|
|
|
|
# Get list of column names |
1516
|
60
|
|
|
|
|
186
|
my $column_names_ref = $self->getColumnNames(); |
1517
|
60
|
|
|
|
|
129
|
my @column_names = @$column_names_ref; |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
# If column names aren't defined ($column_names_ref returns undef), use tree field names (arbitrary order). |
1520
|
60
|
|
|
|
|
225
|
my @field_names = $self->getFieldNames; |
1521
|
60
|
50
|
|
|
|
564
|
if (!$column_names_ref) { |
1522
|
0
|
|
|
|
|
0
|
@column_names = @field_names; |
1523
|
|
|
|
|
|
|
} |
1524
|
|
|
|
|
|
|
|
1525
|
|
|
|
|
|
|
# Insert required columns: |
1526
|
60
|
|
|
|
|
118
|
my @required_column_names = ('serial', 'parent'); |
1527
|
|
|
|
|
|
|
|
1528
|
|
|
|
|
|
|
# Variables for the SQL statements |
1529
|
60
|
|
|
|
|
71
|
my $sql_header = ''; |
1530
|
60
|
|
|
|
|
74
|
my $example_output_file = 'insert-into.sql'; |
1531
|
60
|
|
|
|
|
68
|
my $drop_index = ''; |
1532
|
60
|
|
|
|
|
68
|
my $drop_table = ''; |
1533
|
60
|
|
|
|
|
70
|
my $create_table = ''; |
1534
|
60
|
|
|
|
|
110
|
my $create_table_last_line = ''; |
1535
|
60
|
|
|
|
|
89
|
my $insert_into = ''; |
1536
|
60
|
|
|
|
|
108
|
my $create_index = ''; |
1537
|
60
|
|
|
|
|
68
|
my $field_type = ''; |
1538
|
60
|
|
|
|
|
57
|
my $qc = ''; |
1539
|
60
|
|
|
|
|
152
|
my $sql_comment = ''; |
1540
|
60
|
|
|
|
|
64
|
my $command_line = ''; |
1541
|
60
|
|
|
|
|
101
|
my $comments = ''; |
1542
|
|
|
|
|
|
|
# Use only lower case letters for columns names in SQL statements, even if column names may be mixed or upper case letters. |
1543
|
60
|
|
|
|
|
189
|
my @column_names_sql = @column_names; |
1544
|
60
|
|
|
|
|
491
|
@column_names_sql = grep(s/^(.+$)/lc($1)/e, @column_names_sql); |
|
120
|
|
|
|
|
772
|
|
1545
|
|
|
|
|
|
|
|
1546
|
|
|
|
|
|
|
# Database dependent SQL syntax |
1547
|
60
|
|
|
|
|
119
|
SWITCH: for ($dbs) { |
1548
|
|
|
|
|
|
|
# MySQL |
1549
|
60
|
100
|
|
|
|
223
|
/^mysql$/i && do { |
1550
|
|
|
|
|
|
|
# No SQL header for MySQL |
1551
|
|
|
|
|
|
|
# $sql_header = ''; |
1552
|
|
|
|
|
|
|
# DROP TABLE statement for MySQL (outcommented if $drop is not set) |
1553
|
30
|
|
|
|
|
73
|
$drop_table = "DROP TABLE IF EXISTS $table;"; |
1554
|
30
|
|
|
|
|
37
|
$sql_comment = "#"; |
1555
|
|
|
|
|
|
|
# CREATE TABLE statement for MySQL ('serial' and 'parent' columns only) |
1556
|
30
|
|
|
|
|
61
|
$create_table = |
1557
|
|
|
|
|
|
|
'CREATE TABLE '. $table . ' ( |
1558
|
|
|
|
|
|
|
`serial` int(11) NOT NULL auto_increment, |
1559
|
|
|
|
|
|
|
`parent` int(11) NOT NULL default \'0\', |
1560
|
|
|
|
|
|
|
'; |
1561
|
|
|
|
|
|
|
# CREATE TABLE statement (last line) for MySQL |
1562
|
30
|
|
|
|
|
35
|
$create_table_last_line = |
1563
|
|
|
|
|
|
|
' PRIMARY KEY (serial) |
1564
|
|
|
|
|
|
|
) TYPE=MyISAM;'; |
1565
|
|
|
|
|
|
|
# No separate 'CREATE INDEX' for MySQL |
1566
|
|
|
|
|
|
|
# $create_index = ''; |
1567
|
|
|
|
|
|
|
# Field type for MySQL |
1568
|
30
|
|
|
|
|
35
|
$field_type = 'varchar(255) default NULL'; |
1569
|
|
|
|
|
|
|
# Quote character for MySQL |
1570
|
30
|
|
|
|
|
40
|
$qc = '`'; |
1571
|
|
|
|
|
|
|
# Command line for MySQL |
1572
|
30
|
|
|
|
|
29
|
$example_output_file = 'insert-into-mysql.sql'; |
1573
|
30
|
|
|
|
|
40
|
$command_line = "mysql -u root -pmysqlpassword test < $example_output_file"; |
1574
|
|
|
|
|
|
|
# Push dummy empty string comments |
1575
|
30
|
|
|
|
|
53
|
push @comments, '', ''; |
1576
|
30
|
|
|
|
|
70
|
last SWITCH; |
1577
|
|
|
|
|
|
|
}; |
1578
|
|
|
|
|
|
|
# PgSQL |
1579
|
30
|
50
|
|
|
|
153
|
/^postgres$|^PostgreSQL$|^pgsql$|^pg$/i && do { |
1580
|
|
|
|
|
|
|
# SQL header for PostgresSQL |
1581
|
30
|
|
|
|
|
58
|
$sql_header = |
1582
|
|
|
|
|
|
|
'SET SESSION AUTHORIZATION \'pgsql\';'; |
1583
|
30
|
|
|
|
|
51
|
push @sql_header, $sql_header; |
1584
|
30
|
|
|
|
|
34
|
$sql_header = |
1585
|
|
|
|
|
|
|
'SET search_path = "public", pg_catalog;'; |
1586
|
30
|
|
|
|
|
42
|
push @sql_header, $sql_header; |
1587
|
30
|
|
|
|
|
42
|
$comments = |
1588
|
|
|
|
|
|
|
'-- Definition'; |
1589
|
30
|
|
|
|
|
43
|
push @comments, $comments; |
1590
|
|
|
|
|
|
|
|
1591
|
|
|
|
|
|
|
# DROP INDEX statement for PostgresSQL (outcommented if $drop is not set) |
1592
|
30
|
|
|
|
|
61
|
$drop_index = 'DROP INDEX IF EXISTS "'. $table .'_serial_index"'.";"; |
1593
|
|
|
|
|
|
|
# DROP TABLE statement for PostgresSQL (outcommented if $drop is not set) |
1594
|
30
|
|
|
|
|
48
|
$drop_table = 'DROP TABLE IF EXISTS "'. $table .'"'.";"; |
1595
|
30
|
|
|
|
|
40
|
$sql_comment = "--"; |
1596
|
|
|
|
|
|
|
# CREATE TABLE statement for PostgresSQL ('serial' and 'parent' columns only) |
1597
|
30
|
|
|
|
|
51
|
$create_table = |
1598
|
|
|
|
|
|
|
'CREATE TABLE "'. $table .'" ( |
1599
|
|
|
|
|
|
|
"serial" integer, |
1600
|
|
|
|
|
|
|
"parent" integer, |
1601
|
|
|
|
|
|
|
'; |
1602
|
|
|
|
|
|
|
# CREATE TABLE statement (last line) for PostgresSQL |
1603
|
30
|
|
|
|
|
35
|
$create_table_last_line = |
1604
|
|
|
|
|
|
|
') WITH OIDS;'; |
1605
|
|
|
|
|
|
|
# 'CREATE INDEX' for PostgresSQL |
1606
|
30
|
|
|
|
|
83
|
$comments = |
1607
|
|
|
|
|
|
|
'-- Indexes'; |
1608
|
30
|
|
|
|
|
37
|
push @comments, $comments; |
1609
|
30
|
|
|
|
|
55
|
$create_index = |
1610
|
|
|
|
|
|
|
'CREATE UNIQUE INDEX '.$table.'_serial_index ON '.$table.' USING btree (serial);'; |
1611
|
30
|
|
|
|
|
41
|
push @create_index, $create_index; |
1612
|
|
|
|
|
|
|
# Field type for PostgresSQL |
1613
|
30
|
|
|
|
|
37
|
$field_type = 'text'; |
1614
|
|
|
|
|
|
|
# Quote character for PostgresSQL |
1615
|
30
|
|
|
|
|
28
|
$qc = '"'; |
1616
|
|
|
|
|
|
|
# Command line for PostgresSQL |
1617
|
30
|
|
|
|
|
40
|
$example_output_file = 'insert-into-pgsql.sql'; |
1618
|
30
|
|
|
|
|
36
|
$command_line = "psql -q -U pgsql -d test -f $example_output_file"; |
1619
|
30
|
|
|
|
|
74
|
last SWITCH; |
1620
|
|
|
|
|
|
|
}; |
1621
|
|
|
|
|
|
|
# DEFAULT |
1622
|
0
|
|
|
|
|
0
|
croak "Database server type '$dbs' is not supported."; |
1623
|
|
|
|
|
|
|
} |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
# DROP TABLE statement (outcommented if $drop is not set) |
1626
|
60
|
50
|
|
|
|
163
|
$drop_index = $sql_comment.' '.$drop_index if !$drop; |
1627
|
60
|
|
|
|
|
175
|
push @drop_index_and_table, $drop_index; |
1628
|
60
|
50
|
|
|
|
106
|
$drop_table = $sql_comment.' '.$drop_table if !$drop; |
1629
|
60
|
|
|
|
|
87
|
push @drop_index_and_table, $drop_table; |
1630
|
|
|
|
|
|
|
|
1631
|
|
|
|
|
|
|
# CREATE TABLE statement |
1632
|
|
|
|
|
|
|
|
1633
|
|
|
|
|
|
|
# MySQL |
1634
|
|
|
|
|
|
|
# DROP TABLE IF EXISTS junk; |
1635
|
|
|
|
|
|
|
# CREATE TABLE junk ( |
1636
|
|
|
|
|
|
|
# serial int(11) NOT NULL auto_increment, |
1637
|
|
|
|
|
|
|
# parent int(11) NOT NULL default '0', |
1638
|
|
|
|
|
|
|
# name varchar(255) default NULL, |
1639
|
|
|
|
|
|
|
# url varchar(255) default NULL, |
1640
|
|
|
|
|
|
|
# color varchar(255) default NULL, |
1641
|
|
|
|
|
|
|
# permission varchar(255) default NULL, |
1642
|
|
|
|
|
|
|
# visible varchar(255) default NULL, |
1643
|
|
|
|
|
|
|
# PRIMARY KEY (serial) |
1644
|
|
|
|
|
|
|
# ) TYPE=MyISAM; |
1645
|
|
|
|
|
|
|
|
1646
|
|
|
|
|
|
|
|
1647
|
|
|
|
|
|
|
# PostgreSQL |
1648
|
|
|
|
|
|
|
# SET SESSION AUTHORIZATION 'postgres'; |
1649
|
|
|
|
|
|
|
# SET search_path = "public", pg_catalog; |
1650
|
|
|
|
|
|
|
# -- Definition |
1651
|
|
|
|
|
|
|
# DROP TABLE "public"."menu"; |
1652
|
|
|
|
|
|
|
# CREATE TABLE "menu" ( |
1653
|
|
|
|
|
|
|
# "serial" integer, |
1654
|
|
|
|
|
|
|
# "parent" integer, |
1655
|
|
|
|
|
|
|
# "name" text, |
1656
|
|
|
|
|
|
|
# "url" text, |
1657
|
|
|
|
|
|
|
# "color" text, |
1658
|
|
|
|
|
|
|
# "permission" text, |
1659
|
|
|
|
|
|
|
# "visible" text |
1660
|
|
|
|
|
|
|
# ) WITH OIDS; |
1661
|
|
|
|
|
|
|
# -- Indexes |
1662
|
|
|
|
|
|
|
# CREATE UNIQUE INDEX serial ON menu USING btree (serial); |
1663
|
|
|
|
|
|
|
|
1664
|
60
|
|
|
|
|
177
|
for (my $i = 0; $i < @column_names_sql; $i++) { |
1665
|
|
|
|
|
|
|
# Add quotes |
1666
|
120
|
|
|
|
|
294
|
$create_table .= " ".$qc.$column_names_sql[$i].$qc." $field_type"; |
1667
|
|
|
|
|
|
|
# Add comma for all but last value or if MySQL |
1668
|
120
|
100
|
100
|
|
|
502
|
$create_table .= "," if (($i < @column_names_sql - 1) || lc($dbs) eq 'mysql') ; |
1669
|
|
|
|
|
|
|
# Add newline |
1670
|
120
|
|
|
|
|
257
|
$create_table .= "\n"; |
1671
|
|
|
|
|
|
|
} |
1672
|
60
|
|
|
|
|
94
|
$create_table .= $create_table_last_line; |
1673
|
|
|
|
|
|
|
|
1674
|
60
|
|
|
|
|
113
|
push @create_table, $create_table; |
1675
|
|
|
|
|
|
|
|
1676
|
|
|
|
|
|
|
# INSERT INTO statements |
1677
|
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
# INSERT INTO `junk2` ( `serial` , `parent` , `name` , `url` , `color` , `permission` , `visible` ) |
1679
|
|
|
|
|
|
|
# VALUES ( |
1680
|
|
|
|
|
|
|
# '1', '0', 'ROOT', 'ROOT', 'ROOT', 'ROOT', 'ROOT' |
1681
|
|
|
|
|
|
|
# ); |
1682
|
|
|
|
|
|
|
|
1683
|
60
|
|
|
|
|
370
|
$insert_into = |
1684
|
|
|
|
|
|
|
"INSERT INTO $qc". $table ."$qc ( $qc".join("$qc, $qc", @required_column_names, @column_names_sql)."$qc )\n". |
1685
|
|
|
|
|
|
|
"VALUES (\n". |
1686
|
|
|
|
|
|
|
" 1, 0, " . "'ROOT', " x (@column_names_sql - 1). "'ROOT'\n". |
1687
|
|
|
|
|
|
|
');'; |
1688
|
60
|
|
|
|
|
93
|
push @insert_into, $insert_into; |
1689
|
|
|
|
|
|
|
|
1690
|
60
|
|
|
|
|
243
|
foreach my $node_number ($self->listChildNumbers) { |
1691
|
852
|
|
|
|
|
21315
|
my $node = $self->getSubTree($node_number); |
1692
|
852
|
|
|
|
|
311567
|
my $parent_node = $node->getParentRef; |
1693
|
852
|
|
|
|
|
3912
|
my $parent_number = $parent_node->getNumber; |
1694
|
852
|
|
|
|
|
2800
|
my $value_code = ''; |
1695
|
852
|
|
|
|
|
2285
|
for (my $i = 0; $i < @column_names; $i++) { |
1696
|
1704
|
|
|
|
|
2160
|
my $column_name = $column_names[$i]; |
1697
|
|
|
|
|
|
|
# Last value in array contains this node's value |
1698
|
1704
|
|
|
|
|
4395
|
my @values = $self->follow($node_number, $column_name); |
1699
|
1704
|
|
|
|
|
423529
|
my $value = pop(@values); |
1700
|
|
|
|
|
|
|
# Set value to empty string if undefined |
1701
|
1704
|
50
|
|
|
|
3223
|
$value = '' if !$value; |
1702
|
|
|
|
|
|
|
# Escape possible quote characters in values |
1703
|
|
|
|
|
|
|
### $value =~ s/\'/\\\'/g; # BUGFIX: double quote instead of escape quotes to avoid warning message on PgSQL 8.2. |
1704
|
1704
|
|
|
|
|
2476
|
$value =~ s/\'/\'\'/g; # BUGFIX: double quote instead of escape quotes to avoid warning message on PgSQL 8.2. |
1705
|
|
|
|
|
|
|
# Add quotes |
1706
|
1704
|
|
|
|
|
2479
|
$value_code .= "'$value'"; |
1707
|
|
|
|
|
|
|
# Add comma for all but last value |
1708
|
1704
|
100
|
|
|
|
6802
|
$value_code .= ", " if ($i < @column_names - 1); |
1709
|
|
|
|
|
|
|
} |
1710
|
852
|
|
|
|
|
5240
|
$insert_into = |
1711
|
|
|
|
|
|
|
"INSERT INTO $qc". $table ."$qc ( $qc".join("$qc, $qc", @required_column_names, @column_names_sql)."$qc )\n". |
1712
|
|
|
|
|
|
|
"VALUES (\n". |
1713
|
|
|
|
|
|
|
" $node_number, $parent_number, $value_code\n". |
1714
|
|
|
|
|
|
|
');'; |
1715
|
852
|
|
|
|
|
2216
|
push @insert_into, $insert_into; |
1716
|
|
|
|
|
|
|
} |
1717
|
|
|
|
|
|
|
|
1718
|
|
|
|
|
|
|
# Insert comments at top |
1719
|
60
|
|
|
|
|
188
|
my $package = __PACKAGE__ || ''; |
1720
|
60
|
|
50
|
|
|
863
|
my $method = (caller(1))[3] || ''; |
1721
|
|
|
|
|
|
|
# Replace last :: with -> |
1722
|
60
|
|
|
|
|
517
|
$method =~ s/$package\:\:/->/; |
1723
|
60
|
50
|
|
|
|
214
|
$method .= '()' if $method; |
1724
|
|
|
|
|
|
|
# Supress the following comment if $drop. |
1725
|
60
|
50
|
|
|
|
138
|
my $uncomment_drop = ($drop) ? "Comment out the 'DROP TABLE ...' statement if you don't want to delete an existing table." : "Uncomment the 'DROP TABLE ...' statement if you want to delete an existing table."; |
1726
|
60
|
|
|
|
|
713
|
$comments = <
|
1727
|
|
|
|
|
|
|
$sql_comment SQL statements for $dbs generated by $package$method. |
1728
|
|
|
|
|
|
|
$sql_comment For details, check the $package documentation. |
1729
|
|
|
|
|
|
|
$sql_comment $uncomment_drop |
1730
|
|
|
|
|
|
|
$sql_comment Usage of this output: |
1731
|
|
|
|
|
|
|
$sql_comment Redirect this output to a file called, for example, '$example_output_file': |
1732
|
|
|
|
|
|
|
$sql_comment $0 @ARGV > $example_output_file |
1733
|
|
|
|
|
|
|
$sql_comment Then run from the command line (assumes that the database 'test' already exists): |
1734
|
|
|
|
|
|
|
$sql_comment $command_line |
1735
|
|
|
|
|
|
|
$sql_comment |
1736
|
|
|
|
|
|
|
COMMENT |
1737
|
60
|
|
|
|
|
177
|
unshift @comments, $comments; |
1738
|
|
|
|
|
|
|
|
1739
|
|
|
|
|
|
|
# Return a reference to all array references. |
1740
|
60
|
|
|
|
|
207
|
my @list = (\@sql_header, \@drop_index_and_table, \@create_table, \@insert_into, \@create_index, \@comments); |
1741
|
60
|
|
|
|
|
610
|
return \@list; |
1742
|
|
|
|
|
|
|
} |
1743
|
|
|
|
|
|
|
|
1744
|
|
|
|
|
|
|
|
1745
|
|
|
|
|
|
|
=cut |
1746
|
|
|
|
|
|
|
|
1747
|
|
|
|
|
|
|
=head1 BUGS AND OTHER ISSUES |
1748
|
|
|
|
|
|
|
|
1749
|
|
|
|
|
|
|
There may be bugs in the code. |
1750
|
|
|
|
|
|
|
The code was written more to be useful as a tool, rather than to be compact, fast and clean. |
1751
|
|
|
|
|
|
|
Please report through CPAN: |
1752
|
|
|
|
|
|
|
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Tree-Numbered-Tools |
1753
|
|
|
|
|
|
|
or send mail to bug-Tree-Numbered-Tools@rt.cpan.org |
1754
|
|
|
|
|
|
|
|
1755
|
|
|
|
|
|
|
Incorrectly using $use_column_names=1 together with a source where column names are *not* specified will cause unpredictable results, probably a run-time error. |
1756
|
|
|
|
|
|
|
The same is true for incorrect usage of $use_column_names=0 together with a source where column names *are* specified. |
1757
|
|
|
|
|
|
|
This module doesn't try to determine incorrect usage as described above. |
1758
|
|
|
|
|
|
|
The possible incorrect usage applies to files and arrays, which may or may not use column names. |
1759
|
|
|
|
|
|
|
SQL expressions and DB tables always use column names by nature. |
1760
|
|
|
|
|
|
|
Always use $use_column_names=1 (set by default using any method) and always specify column names in the source text file or array. |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
For suggestions, questions and such, email me directly. |
1763
|
|
|
|
|
|
|
|
1764
|
|
|
|
|
|
|
=head1 EXAMPLES |
1765
|
|
|
|
|
|
|
|
1766
|
|
|
|
|
|
|
To see working examples, see the 'examples' directory in the distribution. |
1767
|
|
|
|
|
|
|
|
1768
|
|
|
|
|
|
|
=head1 SEE ALSO |
1769
|
|
|
|
|
|
|
|
1770
|
|
|
|
|
|
|
Tree::Numbered, Tree::Numbered::DB by Yosef Meller |
1771
|
|
|
|
|
|
|
|
1772
|
|
|
|
|
|
|
=head1 AUTHOR |
1773
|
|
|
|
|
|
|
|
1774
|
|
|
|
|
|
|
Johan Kuuse, Ejohan@kuu.seE |
1775
|
|
|
|
|
|
|
|
1776
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
1777
|
|
|
|
|
|
|
|
1778
|
|
|
|
|
|
|
Copyright (C) 2004-2009 by Johan Kuuse |
1779
|
|
|
|
|
|
|
|
1780
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
1781
|
|
|
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.8.9 or, |
1782
|
|
|
|
|
|
|
at your option, any later version of Perl 5 you may have available. |
1783
|
|
|
|
|
|
|
|
1784
|
|
|
|
|
|
|
=cut |
1785
|
|
|
|
|
|
|
1; |