line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
use 5.010; |
3
|
2
|
|
|
2
|
|
16157
|
use Moo; |
|
2
|
|
|
|
|
6
|
|
4
|
2
|
|
|
2
|
|
9
|
use utf8; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
13
|
|
5
|
2
|
|
|
2
|
|
531
|
use Path::Class; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
21
|
|
6
|
2
|
|
|
2
|
|
56
|
use DBI; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
97
|
|
7
|
2
|
|
|
2
|
|
1111
|
use Try::Tiny; |
|
2
|
|
|
|
|
13058
|
|
|
2
|
|
|
|
|
68
|
|
8
|
2
|
|
|
2
|
|
10
|
use App::Sqitch::X qw(hurl); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
93
|
|
9
|
2
|
|
|
2
|
|
11
|
use Locale::TextDomain qw(App-Sqitch); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
14
|
|
10
|
2
|
|
|
2
|
|
533
|
use App::Sqitch::Plan::Change; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
12
|
|
11
|
2
|
|
|
2
|
|
283
|
use List::Util qw(first); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
39
|
|
12
|
2
|
|
|
2
|
|
10
|
use App::Sqitch::Types qw(DBH Dir ArrayRef); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
108
|
|
13
|
2
|
|
|
2
|
|
12
|
use namespace::autoclean; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
14
|
|
14
|
2
|
|
|
2
|
|
1571
|
|
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
34
|
|
15
|
|
|
|
|
|
|
extends 'App::Sqitch::Engine'; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = 'v1.3.1'; # VERSION |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
BEGIN { |
20
|
|
|
|
|
|
|
# We tell the Oracle connector which encoding to use. The last part of the |
21
|
|
|
|
|
|
|
# environment variable NLS_LANG is relevant concerning data encoding. |
22
|
|
|
|
|
|
|
$ENV{NLS_LANG} = 'AMERICAN_AMERICA.AL32UTF8'; |
23
|
2
|
|
|
2
|
|
279
|
|
24
|
|
|
|
|
|
|
# Disable SQLPATH so that no start scripts run. |
25
|
|
|
|
|
|
|
$ENV{SQLPATH} = ''; |
26
|
2
|
|
|
|
|
7642
|
} |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $self = shift; |
29
|
|
|
|
|
|
|
|
30
|
15
|
|
|
15
|
1
|
5366
|
# Just use the target name if it doesn't look like a URI or if the URI |
31
|
|
|
|
|
|
|
# includes the database name. |
32
|
|
|
|
|
|
|
return $self->target->name if $self->target->name !~ /:/ |
33
|
|
|
|
|
|
|
|| $self->target->uri->dbname; |
34
|
15
|
100
|
66
|
|
|
119
|
|
35
|
|
|
|
|
|
|
# Use the URI sans password, and with the database name added. |
36
|
|
|
|
|
|
|
my $uri = $self->target->uri->clone; |
37
|
|
|
|
|
|
|
$uri->password(undef) if $uri->password; |
38
|
12
|
|
|
|
|
407
|
$uri->dbname( |
39
|
12
|
50
|
|
|
|
118
|
$ENV{TWO_TASK} |
40
|
|
|
|
|
|
|
|| ( App::Sqitch::ISWIN ? $ENV{LOCAL} : undef ) |
41
|
|
|
|
|
|
|
|| $ENV{ORACLE_SID} |
42
|
|
|
|
|
|
|
|| $self->username |
43
|
|
|
|
|
|
|
); |
44
|
12
|
|
66
|
|
|
345
|
return $uri->as_string; |
45
|
|
|
|
|
|
|
} |
46
|
12
|
|
|
|
|
439
|
|
47
|
|
|
|
|
|
|
# No username or password defaults. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
has _sqlplus => ( |
50
|
|
|
|
3
|
|
|
is => 'ro', |
51
|
|
|
|
3
|
|
|
isa => ArrayRef, |
52
|
|
|
|
|
|
|
lazy => 1, |
53
|
|
|
|
|
|
|
default => sub { |
54
|
|
|
|
|
|
|
my $self = shift; |
55
|
|
|
|
|
|
|
[ $self->client, qw(-S -L /nolog) ]; |
56
|
|
|
|
|
|
|
}, |
57
|
|
|
|
|
|
|
); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
has tmpdir => ( |
61
|
|
|
|
|
|
|
is => 'ro', |
62
|
|
|
|
|
|
|
isa => Dir, |
63
|
7
|
|
|
7
|
1
|
2005
|
lazy => 1, |
|
7
|
|
|
|
|
120
|
|
64
|
|
|
|
|
|
|
default => sub { |
65
|
|
|
|
|
|
|
require File::Temp; |
66
|
|
|
|
|
|
|
dir File::Temp::tempdir( CLEANUP => 1 ); |
67
|
|
|
|
|
|
|
}, |
68
|
|
|
|
|
|
|
); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
file( ($ENV{ORACLE_HOME} || ()), 'sqlplus' )->stringify |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
has dbh => ( |
75
|
4
|
|
|
4
|
1
|
4857
|
is => 'rw', |
76
|
3
|
|
|
3
|
1
|
26
|
isa => DBH, |
77
|
1
|
|
|
1
|
1
|
3
|
lazy => 1, |
78
|
6
|
|
|
6
|
1
|
886
|
default => sub { |
79
|
|
|
|
|
|
|
my $self = shift; |
80
|
|
|
|
|
|
|
$self->use_driver; |
81
|
2
|
|
66
|
2
|
1
|
275
|
|
82
|
|
|
|
|
|
|
my $uri = $self->uri; |
83
|
|
|
|
|
|
|
DBI->connect($uri->dbi_dsn, $self->username, $self->password, { |
84
|
|
|
|
|
|
|
PrintError => 0, |
85
|
|
|
|
|
|
|
RaiseError => 0, |
86
|
|
|
|
|
|
|
AutoCommit => 1, |
87
|
|
|
|
|
|
|
FetchHashKeyName => 'NAME_lc', |
88
|
|
|
|
|
|
|
HandleError => sub { |
89
|
|
|
|
|
|
|
my ($err, $dbh) = @_; |
90
|
|
|
|
|
|
|
$@ = $err; |
91
|
|
|
|
|
|
|
@_ = ($dbh->state || 'DEV' => $dbh->errstr); |
92
|
|
|
|
|
|
|
goto &hurl; |
93
|
|
|
|
|
|
|
}, |
94
|
|
|
|
|
|
|
Callbacks => { |
95
|
|
|
|
|
|
|
connected => sub { |
96
|
|
|
|
|
|
|
my $dbh = shift; |
97
|
|
|
|
|
|
|
$dbh->do("ALTER SESSION SET $_='YYYY-MM-DD HH24:MI:SS TZR'") for qw( |
98
|
|
|
|
|
|
|
nls_date_format |
99
|
|
|
|
|
|
|
nls_timestamp_format |
100
|
|
|
|
|
|
|
nls_timestamp_tz_format |
101
|
|
|
|
|
|
|
); |
102
|
|
|
|
|
|
|
if (my $schema = $self->registry) { |
103
|
|
|
|
|
|
|
try { |
104
|
|
|
|
|
|
|
$dbh->do("ALTER SESSION SET CURRENT_SCHEMA = $schema"); |
105
|
|
|
|
|
|
|
# https://www.nntp.perl.org/group/perl.dbi.dev/2013/11/msg7622.html |
106
|
|
|
|
|
|
|
$dbh->set_err(undef, undef) if $dbh->err; |
107
|
|
|
|
|
|
|
}; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
return; |
110
|
|
|
|
|
|
|
}, |
111
|
|
|
|
|
|
|
}, |
112
|
|
|
|
|
|
|
}); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Need to wait until dbh is defined. |
117
|
|
|
|
|
|
|
with 'App::Sqitch::Role::DBIEngine'; |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
[ map { $_->format_name } $_[1]->tags ]; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
[ map { $_->as_string } $_[1]->requires ]; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
[ map { $_->as_string } $_[1]->conflicts ]; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# q{CAST(to_char(%1$s AT TIME ZONE 'UTC', '"year":YYYY:"month":MM:"day":DD') AS VARCHAR2(100 byte)) || CAST(to_char(%1$s AT TIME ZONE 'UTC', ':"hour":HH24:"minute":MI:"second":SS:"time_zone":"UTC"') AS VARCHAR2(168 byte))} |
129
|
|
|
|
|
|
|
# Good grief, Oracle, WTF? https://github.com/sqitchers/sqitch/issues/316 |
130
|
1
|
|
|
1
|
|
12
|
join ' || ', ( |
|
3
|
|
|
|
|
12
|
|
131
|
|
|
|
|
|
|
q{to_char(%1$s AT TIME ZONE 'UTC', '"year":YYYY')}, |
132
|
|
|
|
|
|
|
q{to_char(%1$s AT TIME ZONE 'UTC', ':"month":MM')}, |
133
|
|
|
|
|
|
|
q{to_char(%1$s AT TIME ZONE 'UTC', ':"day":DD')}, |
134
|
1
|
|
|
1
|
|
212
|
q{to_char(%1$s AT TIME ZONE 'UTC', ':"hour":HH24')}, |
|
3
|
|
|
|
|
11
|
|
135
|
|
|
|
|
|
|
q{to_char(%1$s AT TIME ZONE 'UTC', ':"minute":MI')}, |
136
|
|
|
|
|
|
|
q{to_char(%1$s AT TIME ZONE 'UTC', ':"second":SS')}, |
137
|
|
|
|
|
|
|
q{':time_zone:UTC'}, |
138
|
1
|
|
|
1
|
|
8
|
); |
|
3
|
|
|
|
|
10
|
|
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
my $dt = $_[1]; |
143
|
|
|
|
|
|
|
join ' ', $dt->ymd('-'), $dt->hms(':'), $dt->time_zone->name; |
144
|
1
|
|
|
1
|
|
1073
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# https://stackoverflow.com/q/16313631/79202 |
147
|
|
|
|
|
|
|
return q{CAST(COLLECT(CAST(%s AS VARCHAR2(512))) AS sqitch_array)}; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
my ($self, $count, $expr) = @_; |
153
|
|
|
|
|
|
|
return join "\nUNION ALL ", ("SELECT $expr FROM dual") x $count; |
154
|
1
|
|
|
1
|
|
4
|
} |
155
|
|
|
|
|
|
|
|
156
|
1
|
|
|
1
|
|
5
|
require App::Sqitch::DateTime; |
157
|
|
|
|
|
|
|
return App::Sqitch::DateTime->new(split /:/ => shift); |
158
|
|
|
|
|
|
|
} |
159
|
1
|
|
|
1
|
|
4220
|
|
160
|
1
|
|
|
|
|
7
|
my ( $self, $ord, $offset, $project ) = @_; |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
return try { |
163
|
|
|
|
|
|
|
return $self->dbh->selectcol_arrayref(qq{ |
164
|
|
|
|
|
|
|
SELECT change_id FROM ( |
165
|
1
|
|
|
1
|
|
503
|
SELECT change_id, rownum as rnum FROM ( |
166
|
|
|
|
|
|
|
SELECT change_id |
167
|
|
|
|
|
|
|
FROM changes |
168
|
1
|
|
|
1
|
|
5
|
WHERE project = ? |
169
|
|
|
|
|
|
|
ORDER BY committed_at $ord |
170
|
1
|
|
|
1
|
|
5
|
) |
171
|
|
|
|
|
|
|
) WHERE rnum = ? |
172
|
|
|
|
|
|
|
}, undef, $project || $self->plan->project, ($offset // 0) + 1)->[0]; |
173
|
3
|
|
|
3
|
|
7
|
} catch { |
174
|
3
|
|
|
|
|
55
|
return if $self->_no_table_error; |
175
|
|
|
|
|
|
|
die $_; |
176
|
|
|
|
|
|
|
}; |
177
|
|
|
|
|
|
|
} |
178
|
1
|
|
|
1
|
|
663
|
|
179
|
1
|
|
|
|
|
12
|
my ($self, $project, $change) = @_; |
180
|
|
|
|
|
|
|
return $self->dbh->selectcol_arrayref(qq{ |
181
|
|
|
|
|
|
|
SELECT change_id FROM ( |
182
|
|
|
|
|
|
|
SELECT change_id |
183
|
1
|
|
|
1
|
|
334
|
FROM changes |
184
|
|
|
|
|
|
|
WHERE project = ? |
185
|
|
|
|
|
|
|
AND change = ? |
186
|
1
|
|
0
|
1
|
|
152
|
ORDER BY committed_at DESC |
|
|
|
0
|
|
|
|
|
187
|
|
|
|
|
|
|
) WHERE rownum = 1 |
188
|
|
|
|
|
|
|
}, undef, $project, $change)->[0]; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
my ( $self, $project, $with_hash ) = @_; |
192
|
|
|
|
|
|
|
my $cdtcol = sprintf $self->_ts2char_format, 'c.committed_at'; |
193
|
|
|
|
|
|
|
my $pdtcol = sprintf $self->_ts2char_format, 'c.planned_at'; |
194
|
|
|
|
|
|
|
my $tagcol = sprintf $self->_listagg_format, 't.tag'; |
195
|
|
|
|
|
|
|
my $hshcol = $with_hash ? "c.script_hash\n , " : ''; |
196
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
197
|
1
|
50
|
|
1
|
|
36
|
return $dbh->selectrow_hashref(qq{ |
198
|
1
|
|
|
|
|
8
|
SELECT * FROM ( |
199
|
1
|
|
|
|
|
28
|
SELECT c.change_id |
200
|
|
|
|
|
|
|
, ${hshcol}c.change |
201
|
|
|
|
|
|
|
, c.project |
202
|
|
|
|
|
|
|
, c.note |
203
|
0
|
|
|
0
|
|
0
|
, c.committer_name |
204
|
0
|
|
|
|
|
0
|
, c.committer_email |
205
|
|
|
|
|
|
|
, $cdtcol AS committed_at |
206
|
|
|
|
|
|
|
, c.planner_name |
207
|
|
|
|
|
|
|
, c.planner_email |
208
|
|
|
|
|
|
|
, $pdtcol AS planned_at |
209
|
|
|
|
|
|
|
, $tagcol AS tags |
210
|
|
|
|
|
|
|
FROM changes c |
211
|
|
|
|
|
|
|
LEFT JOIN tags t ON c.change_id = t.change_id |
212
|
|
|
|
|
|
|
WHERE c.project = ? |
213
|
|
|
|
|
|
|
GROUP BY c.change_id |
214
|
|
|
|
|
|
|
, ${hshcol}c.change |
215
|
|
|
|
|
|
|
, c.project |
216
|
0
|
|
|
0
|
|
0
|
, c.note |
217
|
0
|
|
|
|
|
0
|
, c.committer_name |
218
|
0
|
|
|
|
|
0
|
, c.committer_email |
219
|
0
|
|
|
|
|
0
|
, c.committed_at |
220
|
0
|
0
|
|
|
|
0
|
, c.planner_name |
221
|
0
|
|
|
|
|
0
|
, c.planner_email |
222
|
0
|
|
0
|
|
|
0
|
, c.planned_at |
223
|
|
|
|
|
|
|
ORDER BY c.committed_at DESC |
224
|
|
|
|
|
|
|
) WHERE rownum = 1 |
225
|
|
|
|
|
|
|
}, undef, $project // $self->plan->project); |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
my ( $self, $change ) = @_; |
229
|
|
|
|
|
|
|
$self->dbh->selectcol_arrayref( |
230
|
|
|
|
|
|
|
'SELECT 1 FROM changes WHERE change_id = ?', |
231
|
|
|
|
|
|
|
undef, $change->id |
232
|
|
|
|
|
|
|
)->[0]; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
my $self = shift; |
236
|
|
|
|
|
|
|
return $self->dbh->selectcol_arrayref(q{ |
237
|
|
|
|
|
|
|
SELECT 1 |
238
|
|
|
|
|
|
|
FROM all_tables |
239
|
|
|
|
|
|
|
WHERE owner = UPPER(?) |
240
|
|
|
|
|
|
|
AND table_name = 'CHANGES' |
241
|
|
|
|
|
|
|
}, undef, $self->registry || $self->username)->[0]; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
my ( $self, $event, $change, $tags, $requires, $conflicts) = @_; |
245
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
246
|
|
|
|
|
|
|
my $sqitch = $self->sqitch; |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
$tags ||= $self->_log_tags_param($change); |
249
|
|
|
|
|
|
|
$requires ||= $self->_log_requires_param($change); |
250
|
|
|
|
|
|
|
$conflicts ||= $self->_log_conflicts_param($change); |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# Use the sqitch_array() constructor to insert arrays of values. |
253
|
|
|
|
|
|
|
my $tag_ph = 'sqitch_array('. join(', ', ('?') x @{ $tags }) . ')'; |
254
|
0
|
|
|
0
|
1
|
0
|
my $req_ph = 'sqitch_array('. join(', ', ('?') x @{ $requires }) . ')'; |
255
|
0
|
|
|
|
|
0
|
my $con_ph = 'sqitch_array('. join(', ', ('?') x @{ $conflicts }) . ')'; |
256
|
|
|
|
|
|
|
my $ts = $self->_ts_default; |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
$dbh->do(qq{ |
259
|
|
|
|
|
|
|
INSERT INTO events ( |
260
|
|
|
|
|
|
|
event |
261
|
|
|
|
|
|
|
, change_id |
262
|
1
|
|
|
1
|
1
|
750
|
, change |
263
|
1
|
|
0
|
|
|
12
|
, project |
264
|
|
|
|
|
|
|
, note |
265
|
|
|
|
|
|
|
, tags |
266
|
|
|
|
|
|
|
, requires |
267
|
|
|
|
|
|
|
, conflicts |
268
|
|
|
|
|
|
|
, committer_name |
269
|
|
|
|
|
|
|
, committer_email |
270
|
|
|
|
|
|
|
, planned_at |
271
|
|
|
|
|
|
|
, planner_name |
272
|
0
|
|
|
0
|
|
0
|
, planner_email |
273
|
0
|
|
|
|
|
0
|
, committed_at |
274
|
0
|
|
|
|
|
0
|
) |
275
|
|
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, $tag_ph, $req_ph, $con_ph, ?, ?, ?, ?, ?, $ts) |
276
|
0
|
|
0
|
|
|
0
|
}, undef, |
277
|
0
|
|
0
|
|
|
0
|
$event, |
278
|
0
|
|
0
|
|
|
0
|
$change->id, |
279
|
|
|
|
|
|
|
$change->name, |
280
|
|
|
|
|
|
|
$change->project, |
281
|
0
|
|
|
|
|
0
|
$change->note, |
|
0
|
|
|
|
|
0
|
|
282
|
0
|
|
|
|
|
0
|
@{ $tags }, |
|
0
|
|
|
|
|
0
|
|
283
|
0
|
|
|
|
|
0
|
@{ $requires }, |
|
0
|
|
|
|
|
0
|
|
284
|
0
|
|
|
|
|
0
|
@{ $conflicts }, |
285
|
|
|
|
|
|
|
$sqitch->user_name, |
286
|
|
|
|
|
|
|
$sqitch->user_email, |
287
|
|
|
|
|
|
|
$self->_char2ts( $change->timestamp ), |
288
|
|
|
|
|
|
|
$change->planner_name, |
289
|
|
|
|
|
|
|
$change->planner_email, |
290
|
|
|
|
|
|
|
); |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
return $self; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
my ( $self, $change ) = @_; |
296
|
|
|
|
|
|
|
# Why CTE: https://forums.oracle.com/forums/thread.jspa?threadID=1005221 |
297
|
|
|
|
|
|
|
return @{ $self->dbh->selectall_arrayref(q{ |
298
|
|
|
|
|
|
|
WITH tag AS ( |
299
|
|
|
|
|
|
|
SELECT tag, committed_at, project, |
300
|
|
|
|
|
|
|
ROW_NUMBER() OVER (partition by project ORDER BY committed_at) AS rnk |
301
|
|
|
|
|
|
|
FROM tags |
302
|
|
|
|
|
|
|
) |
303
|
|
|
|
|
|
|
SELECT c.change_id, c.project, c.change, t.tag AS asof_tag |
304
|
|
|
|
|
|
|
FROM dependencies d |
305
|
|
|
|
|
|
|
JOIN changes c ON c.change_id = d.change_id |
306
|
|
|
|
|
|
|
LEFT JOIN tag t ON t.project = c.project AND t.committed_at >= c.committed_at |
307
|
|
|
|
|
|
|
WHERE d.dependency_id = ? |
308
|
|
|
|
|
|
|
AND (t.rnk IS NULL OR t.rnk = 1) |
309
|
|
|
|
|
|
|
}, { Slice => {} }, $change->id) }; |
310
|
0
|
|
|
|
|
0
|
} |
311
|
0
|
|
|
|
|
0
|
|
312
|
0
|
|
|
|
|
0
|
my ( $self, $change_id ) = @_; |
|
0
|
|
|
|
|
0
|
|
313
|
|
|
|
|
|
|
# Why CTE: https://forums.oracle.com/forums/thread.jspa?threadID=1005221 |
314
|
|
|
|
|
|
|
return $self->dbh->selectcol_arrayref(q{ |
315
|
|
|
|
|
|
|
WITH tag AS ( |
316
|
|
|
|
|
|
|
SELECT tag, committed_at, project, |
317
|
|
|
|
|
|
|
ROW_NUMBER() OVER (partition by project ORDER BY committed_at) AS rnk |
318
|
|
|
|
|
|
|
FROM tags |
319
|
|
|
|
|
|
|
) |
320
|
0
|
|
|
|
|
0
|
SELECT change || COALESCE(t.tag, '@HEAD') |
321
|
|
|
|
|
|
|
FROM changes c |
322
|
|
|
|
|
|
|
LEFT JOIN tag t ON c.project = t.project AND t.committed_at >= c.committed_at |
323
|
|
|
|
|
|
|
WHERE change_id = ? |
324
|
0
|
|
|
0
|
1
|
0
|
AND (t.rnk IS NULL OR t.rnk = 1) |
325
|
|
|
|
|
|
|
}, undef, $change_id)->[0]; |
326
|
0
|
|
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
my ( $self, $change_id, $offset ) = @_; |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Just return the ID if there is no offset. |
331
|
|
|
|
|
|
|
return $change_id unless $offset; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# Are we offset forwards or backwards? |
334
|
|
|
|
|
|
|
my ( $dir, $op ) = $offset > 0 ? ( 'ASC', '>' ) : ( 'DESC' , '<' ); |
335
|
|
|
|
|
|
|
return $self->dbh->selectcol_arrayref(qq{ |
336
|
|
|
|
|
|
|
SELECT id FROM ( |
337
|
|
|
|
|
|
|
SELECT id, rownum AS rnum FROM ( |
338
|
|
|
|
|
|
|
SELECT change_id AS id |
339
|
|
|
|
|
|
|
FROM changes |
340
|
|
|
|
|
|
|
WHERE project = ? |
341
|
|
|
|
|
|
|
AND committed_at $op ( |
342
|
0
|
|
|
0
|
1
|
0
|
SELECT committed_at FROM changes WHERE change_id = ? |
343
|
|
|
|
|
|
|
) |
344
|
0
|
|
|
|
|
0
|
ORDER BY committed_at $dir |
345
|
|
|
|
|
|
|
) |
346
|
|
|
|
|
|
|
) WHERE rnum = ? |
347
|
|
|
|
|
|
|
}, undef, $self->plan->project, $change_id, abs $offset)->[0]; |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
my ( $self, $change_id, $offset ) = @_; |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# Just return the object if there is no offset. |
353
|
|
|
|
|
|
|
return $self->load_change($change_id) unless $offset; |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Are we offset forwards or backwards? |
356
|
|
|
|
|
|
|
my ( $dir, $op ) = $offset > 0 ? ( 'ASC', '>' ) : ( 'DESC' , '<' ); |
357
|
|
|
|
|
|
|
my $tscol = sprintf $self->_ts2char_format, 'c.planned_at'; |
358
|
|
|
|
|
|
|
my $tagcol = sprintf $self->_listagg_format, 't.tag'; |
359
|
0
|
|
|
0
|
1
|
0
|
|
360
|
|
|
|
|
|
|
my $change = $self->dbh->selectrow_hashref(qq{ |
361
|
|
|
|
|
|
|
SELECT id, name, project, note, timestamp, planner_name, planner_email, tags, script_hash |
362
|
0
|
0
|
|
|
|
0
|
FROM ( |
363
|
|
|
|
|
|
|
SELECT id, name, project, note, timestamp, planner_name, planner_email, tags, script_hash, rownum AS rnum |
364
|
|
|
|
|
|
|
FROM ( |
365
|
0
|
0
|
|
|
|
0
|
SELECT c.change_id AS id, c.change AS name, c.project, c.note, |
366
|
0
|
|
|
|
|
0
|
$tscol AS timestamp, c.planner_name, c.planner_email, |
367
|
|
|
|
|
|
|
$tagcol AS tags, c.script_hash |
368
|
|
|
|
|
|
|
FROM changes c |
369
|
|
|
|
|
|
|
LEFT JOIN tags t ON c.change_id = t.change_id |
370
|
|
|
|
|
|
|
WHERE c.project = ? |
371
|
|
|
|
|
|
|
AND c.committed_at $op ( |
372
|
|
|
|
|
|
|
SELECT committed_at FROM changes WHERE change_id = ? |
373
|
|
|
|
|
|
|
) |
374
|
|
|
|
|
|
|
GROUP BY c.change_id, c.change, c.project, c.note, c.planned_at, |
375
|
|
|
|
|
|
|
c.planner_name, c.planner_email, c.committed_at, c.script_hash |
376
|
|
|
|
|
|
|
ORDER BY c.committed_at $dir |
377
|
|
|
|
|
|
|
) |
378
|
|
|
|
|
|
|
) WHERE rnum = ? |
379
|
|
|
|
|
|
|
}, undef, $self->plan->project, $change_id, abs $offset) || return undef; |
380
|
|
|
|
|
|
|
$change->{timestamp} = _dt $change->{timestamp}; |
381
|
|
|
|
|
|
|
return $change; |
382
|
0
|
|
|
0
|
1
|
0
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
my ( $self, $tag ) = @_; |
385
|
0
|
0
|
|
|
|
0
|
return $self->dbh->selectcol_arrayref( |
386
|
|
|
|
|
|
|
'SELECT 1 FROM tags WHERE tag_id = ?', |
387
|
|
|
|
|
|
|
undef, $tag->id |
388
|
0
|
0
|
|
|
|
0
|
)->[0]; |
389
|
0
|
|
|
|
|
0
|
} |
390
|
0
|
|
|
|
|
0
|
|
391
|
|
|
|
|
|
|
my $self = shift; |
392
|
0
|
|
0
|
|
|
0
|
@{ $self->dbh->selectcol_arrayref( |
393
|
|
|
|
|
|
|
'SELECT change_id FROM changes WHERE ' . _change_id_in(scalar @_), |
394
|
|
|
|
|
|
|
undef, |
395
|
|
|
|
|
|
|
map { $_->id } @_, |
396
|
|
|
|
|
|
|
) }; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
my $i = shift; |
400
|
|
|
|
|
|
|
my @qs; |
401
|
|
|
|
|
|
|
while ($i > 250) { |
402
|
|
|
|
|
|
|
push @qs => 'change_id IN (' . join(', ' => ('?') x 250) . ')'; |
403
|
|
|
|
|
|
|
$i -= 250; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
push @qs => 'change_id IN (' . join(', ' => ('?') x $i) . ')' if $i > 0; |
406
|
|
|
|
|
|
|
return join ' OR ', @qs; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
my $self = shift; |
410
|
|
|
|
|
|
|
my $schema = $self->registry; |
411
|
|
|
|
|
|
|
return $schema ? ("DEFINE registry=$schema") : ( |
412
|
0
|
|
|
|
|
0
|
# Select the current schema into ®istry. |
413
|
0
|
|
|
|
|
0
|
# https://www.orafaq.com/node/515 |
414
|
|
|
|
|
|
|
'COLUMN sname for a30 new_value registry', |
415
|
|
|
|
|
|
|
q{SELECT SYS_CONTEXT('USERENV', 'SESSION_SCHEMA') AS sname FROM DUAL;}, |
416
|
|
|
|
|
|
|
); |
417
|
0
|
|
|
0
|
1
|
0
|
} |
418
|
0
|
|
|
|
|
0
|
|
419
|
|
|
|
|
|
|
my $self = shift; |
420
|
|
|
|
|
|
|
my $schema = $self->registry; |
421
|
|
|
|
|
|
|
hurl engine => __ 'Sqitch already initialized' if $self->initialized; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
# Load up our database. |
424
|
|
|
|
|
|
|
(my $file = file(__FILE__)->dir->file('oracle.sql')) =~ s/"/""/g; |
425
|
0
|
|
|
0
|
1
|
0
|
$self->_run_with_verbosity($file); |
426
|
0
|
|
|
|
|
0
|
$self->dbh->do("ALTER SESSION SET CURRENT_SCHEMA = $schema") if $schema; |
427
|
|
|
|
|
|
|
$self->_register_release; |
428
|
|
|
|
|
|
|
} |
429
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
430
|
|
|
|
|
|
|
# Override for special handling of regular the expression operator and |
431
|
|
|
|
|
|
|
# LIMIT/OFFSET. |
432
|
|
|
|
|
|
|
my ( $self, %p ) = @_; |
433
|
|
|
|
|
|
|
|
434
|
12
|
|
|
12
|
|
2360
|
# Determine order direction. |
435
|
12
|
|
|
|
|
16
|
my $dir = 'DESC'; |
436
|
12
|
|
|
|
|
22
|
if (my $d = delete $p{direction}) { |
437
|
8
|
|
|
|
|
70
|
$dir = $d =~ /^ASC/i ? 'ASC' |
438
|
8
|
|
|
|
|
11
|
: $d =~ /^DESC/i ? 'DESC' |
439
|
|
|
|
|
|
|
: hurl 'Search direction must be either "ASC" or "DESC"'; |
440
|
12
|
100
|
|
|
|
60
|
} |
441
|
12
|
|
|
|
|
132
|
|
442
|
|
|
|
|
|
|
# Limit with regular expressions? |
443
|
|
|
|
|
|
|
my (@wheres, @params); |
444
|
|
|
|
|
|
|
for my $spec ( |
445
|
20
|
|
|
20
|
|
26
|
[ committer => 'committer_name' ], |
446
|
20
|
|
|
|
|
251
|
[ planner => 'planner_name' ], |
447
|
20
|
100
|
|
|
|
669
|
[ change => 'change' ], |
448
|
|
|
|
|
|
|
[ project => 'project' ], |
449
|
|
|
|
|
|
|
) { |
450
|
|
|
|
|
|
|
my $regex = delete $p{ $spec->[0] } // next; |
451
|
|
|
|
|
|
|
push @wheres => "REGEXP_LIKE($spec->[1], ?)"; |
452
|
|
|
|
|
|
|
push @params => $regex; |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
# Match events? |
456
|
0
|
|
|
0
|
1
|
0
|
if (my $e = delete $p{event} ) { |
457
|
0
|
|
|
|
|
0
|
my ($in, @vals) = $self->_in_expr( $e ); |
458
|
0
|
0
|
|
|
|
0
|
push @wheres => "event $in"; |
459
|
|
|
|
|
|
|
push @params => @vals; |
460
|
|
|
|
|
|
|
} |
461
|
0
|
|
|
|
|
0
|
|
462
|
0
|
|
|
|
|
0
|
# Assemble the where clause. |
463
|
0
|
0
|
|
|
|
0
|
my $where = @wheres |
464
|
0
|
|
|
|
|
0
|
? "\n WHERE " . join( "\n ", @wheres ) |
465
|
|
|
|
|
|
|
: ''; |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
# Handle remaining parameters. |
468
|
|
|
|
|
|
|
my ($lim, $off) = (delete $p{limit}, delete $p{offset}); |
469
|
|
|
|
|
|
|
|
470
|
0
|
|
|
0
|
1
|
0
|
hurl 'Invalid parameters passed to search_events(): ' |
471
|
|
|
|
|
|
|
. join ', ', sort keys %p if %p; |
472
|
|
|
|
|
|
|
|
473
|
0
|
|
|
|
|
0
|
# Prepare, execute, and return. |
474
|
0
|
0
|
|
|
|
0
|
my $cdtcol = sprintf $self->_ts2char_format, 'committed_at'; |
475
|
0
|
0
|
|
|
|
0
|
my $pdtcol = sprintf $self->_ts2char_format, 'planned_at'; |
|
|
0
|
|
|
|
|
|
476
|
|
|
|
|
|
|
my $sql = qq{ |
477
|
|
|
|
|
|
|
SELECT event |
478
|
|
|
|
|
|
|
, project |
479
|
|
|
|
|
|
|
, change_id |
480
|
|
|
|
|
|
|
, change |
481
|
0
|
|
|
|
|
0
|
, note |
482
|
0
|
|
|
|
|
0
|
, requires |
483
|
|
|
|
|
|
|
, conflicts |
484
|
|
|
|
|
|
|
, tags |
485
|
|
|
|
|
|
|
, committer_name |
486
|
|
|
|
|
|
|
, committer_email |
487
|
|
|
|
|
|
|
, $cdtcol AS committed_at |
488
|
0
|
|
0
|
|
|
0
|
, planner_name |
489
|
0
|
|
|
|
|
0
|
, planner_email |
490
|
0
|
|
|
|
|
0
|
, $pdtcol AS planned_at |
491
|
|
|
|
|
|
|
FROM events$where |
492
|
|
|
|
|
|
|
ORDER BY events.committed_at $dir |
493
|
|
|
|
|
|
|
}; |
494
|
0
|
0
|
|
|
|
0
|
|
495
|
0
|
|
|
|
|
0
|
if ($lim || $off) { |
496
|
0
|
|
|
|
|
0
|
my @limits; |
497
|
0
|
|
|
|
|
0
|
if ($lim) { |
498
|
|
|
|
|
|
|
$off //= 0; |
499
|
|
|
|
|
|
|
push @params => $lim + $off; |
500
|
|
|
|
|
|
|
push @limits => 'rnum <= ?'; |
501
|
0
|
0
|
|
|
|
0
|
} |
502
|
|
|
|
|
|
|
if ($off) { |
503
|
|
|
|
|
|
|
push @params => $off; |
504
|
|
|
|
|
|
|
push @limits => 'rnum > ?'; |
505
|
|
|
|
|
|
|
} |
506
|
0
|
|
|
|
|
0
|
|
507
|
|
|
|
|
|
|
$sql = "SELECT * FROM ( SELECT ROWNUM AS rnum, i.* FROM ($sql) i ) WHERE " |
508
|
0
|
0
|
|
|
|
0
|
. join ' AND ', @limits; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
my $sth = $self->dbh->prepare($sql); |
512
|
0
|
|
|
|
|
0
|
$sth->execute(@params); |
513
|
0
|
|
|
|
|
0
|
return sub { |
514
|
0
|
|
|
|
|
0
|
my $row = $sth->fetchrow_hashref or return; |
515
|
|
|
|
|
|
|
delete $row->{rnum}; |
516
|
|
|
|
|
|
|
$row->{committed_at} = _dt $row->{committed_at}; |
517
|
|
|
|
|
|
|
$row->{planned_at} = _dt $row->{planned_at}; |
518
|
|
|
|
|
|
|
return $row; |
519
|
|
|
|
|
|
|
}; |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
# Override to lock the changes table. This ensures that only one instance of |
523
|
|
|
|
|
|
|
# Sqitch runs at one time. |
524
|
|
|
|
|
|
|
my $self = shift; |
525
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# Start transaction and lock changes to allow only one change at a time. |
528
|
|
|
|
|
|
|
$dbh->begin_work; |
529
|
|
|
|
|
|
|
$dbh->do('LOCK TABLE changes IN EXCLUSIVE MODE'); |
530
|
|
|
|
|
|
|
return $self; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
0
|
0
|
0
|
|
|
0
|
my ($self, $file) = @_; |
534
|
0
|
|
|
|
|
0
|
|
535
|
0
|
0
|
|
|
|
0
|
# Just use the file if no special character. |
536
|
0
|
|
0
|
|
|
0
|
if ($file !~ /[@?%\$]/) { |
537
|
0
|
|
|
|
|
0
|
$file =~ s/"/""/g; |
538
|
0
|
|
|
|
|
0
|
return $file; |
539
|
|
|
|
|
|
|
} |
540
|
0
|
0
|
|
|
|
0
|
|
541
|
0
|
|
|
|
|
0
|
# Alias or copy the file to a temporary directory that's removed on exit. |
542
|
0
|
|
|
|
|
0
|
(my $alias = $file->basename) =~ s/[@?%\$]/_/g; |
543
|
|
|
|
|
|
|
$alias = $self->tmpdir->file($alias); |
544
|
|
|
|
|
|
|
|
545
|
0
|
|
|
|
|
0
|
# Remove existing file. |
546
|
|
|
|
|
|
|
if (-e $alias) { |
547
|
|
|
|
|
|
|
$alias->remove or hurl oracle => __x( |
548
|
|
|
|
|
|
|
'Cannot remove {file}: {error}', |
549
|
0
|
|
|
|
|
0
|
file => $alias, |
550
|
0
|
|
|
|
|
0
|
error => $! |
551
|
|
|
|
|
|
|
); |
552
|
0
|
0
|
|
0
|
|
0
|
} |
553
|
0
|
|
|
|
|
0
|
|
554
|
0
|
|
|
|
|
0
|
if (App::Sqitch::ISWIN) { |
555
|
0
|
|
|
|
|
0
|
# Copy it. |
556
|
0
|
|
|
|
|
0
|
$file->copy_to($alias) or hurl oracle => __x( |
557
|
0
|
|
|
|
|
0
|
'Cannot copy {file} to {alias}: {error}', |
558
|
|
|
|
|
|
|
file => $file, |
559
|
|
|
|
|
|
|
alias => $alias, |
560
|
|
|
|
|
|
|
error => $! |
561
|
|
|
|
|
|
|
); |
562
|
|
|
|
|
|
|
} else { |
563
|
0
|
|
|
0
|
1
|
0
|
# Symlink it. |
564
|
0
|
|
|
|
|
0
|
$alias->remove; |
565
|
|
|
|
|
|
|
symlink $file->absolute, $alias or hurl oracle => __x( |
566
|
|
|
|
|
|
|
'Cannot symlink {file} to {alias}: {error}', |
567
|
0
|
|
|
|
|
0
|
file => $file, |
568
|
0
|
|
|
|
|
0
|
alias => $alias, |
569
|
0
|
|
|
|
|
0
|
error => $! |
570
|
|
|
|
|
|
|
); |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
|
573
|
9
|
|
|
9
|
|
6624
|
# Return the alias. |
574
|
|
|
|
|
|
|
$alias =~ s/"/""/g; |
575
|
|
|
|
|
|
|
return $alias; |
576
|
9
|
100
|
|
|
|
61
|
} |
577
|
6
|
|
|
|
|
37
|
|
578
|
6
|
|
|
|
|
47
|
my $self = shift; |
579
|
|
|
|
|
|
|
my $file = $self->_file_for_script(shift); |
580
|
|
|
|
|
|
|
$self->_run(qq{\@"$file"}); |
581
|
|
|
|
|
|
|
} |
582
|
3
|
|
|
|
|
130
|
|
583
|
3
|
|
|
|
|
70
|
my $self = shift; |
584
|
|
|
|
|
|
|
my $file = $self->_file_for_script(shift); |
585
|
|
|
|
|
|
|
# Suppress STDOUT unless we want extra verbosity. |
586
|
3
|
100
|
|
|
|
178
|
my $meth = $self->can($self->sqitch->verbosity > 1 ? '_run' : '_capture'); |
587
|
1
|
50
|
|
|
|
41
|
$self->$meth(qq{\@"$file"}); |
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
my ($self, $fh) = @_; |
592
|
|
|
|
|
|
|
my $conn = $self->_script; |
593
|
|
|
|
|
|
|
open my $tfh, '<:utf8_strict', \$conn; |
594
|
2
|
|
|
|
|
79
|
$self->sqitch->spool( [$tfh, $fh], $self->sqlplus ); |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
# Override to take advantage of the RETURNING expression, and to save tags as |
598
|
|
|
|
|
|
|
# an array rather than a space-delimited string. |
599
|
|
|
|
|
|
|
my ($self, $change) = @_; |
600
|
|
|
|
|
|
|
my $dbh = $self->dbh; |
601
|
|
|
|
|
|
|
my $cid = $change->id; |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
# Delete tags. |
604
|
2
|
|
|
|
|
13
|
my $sth = $dbh->prepare( |
605
|
2
|
50
|
|
|
|
95
|
'DELETE FROM tags WHERE change_id = ? RETURNING tag INTO ?', |
606
|
|
|
|
|
|
|
); |
607
|
|
|
|
|
|
|
$sth->bind_param(1, $cid); |
608
|
|
|
|
|
|
|
$sth->bind_param_inout_array(2, my $del_tags = [], 0, { |
609
|
|
|
|
|
|
|
ora_type => DBD::Oracle::ORA_VARCHAR2() |
610
|
|
|
|
|
|
|
}); |
611
|
|
|
|
|
|
|
$sth->execute; |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
# Retrieve dependencies. |
614
|
2
|
|
|
|
|
210
|
my $depcol = sprintf $self->_listagg_format, 'dependency'; |
615
|
2
|
|
|
|
|
79
|
my ($req, $conf) = $dbh->selectrow_array(qq{ |
616
|
|
|
|
|
|
|
SELECT ( |
617
|
|
|
|
|
|
|
SELECT $depcol |
618
|
|
|
|
|
|
|
FROM dependencies |
619
|
2
|
|
|
2
|
1
|
1162
|
WHERE change_id = ? |
620
|
2
|
|
|
|
|
6
|
AND type = 'require' |
621
|
2
|
|
|
|
|
11
|
), |
622
|
|
|
|
|
|
|
( |
623
|
|
|
|
|
|
|
SELECT $depcol |
624
|
|
|
|
|
|
|
FROM dependencies |
625
|
2
|
|
|
2
|
|
230
|
WHERE change_id = ? |
626
|
2
|
|
|
|
|
6
|
AND type = 'conflict' |
627
|
|
|
|
|
|
|
) FROM dual |
628
|
2
|
100
|
|
|
|
35
|
}, undef, $cid, $cid); |
629
|
2
|
|
|
|
|
254
|
|
630
|
|
|
|
|
|
|
# Delete the change record. |
631
|
|
|
|
|
|
|
$dbh->do( |
632
|
0
|
|
|
0
|
1
|
0
|
'DELETE FROM changes where change_id = ?', |
633
|
2
|
|
|
2
|
1
|
742
|
undef, $change->id, |
634
|
|
|
|
|
|
|
); |
635
|
|
|
|
|
|
|
|
636
|
1
|
|
|
1
|
1
|
645
|
# Log it. |
637
|
1
|
|
|
|
|
7
|
return $self->_log_event( revert => $change, $del_tags, $req, $conf ); |
638
|
1
|
|
|
|
|
17
|
} |
639
|
1
|
|
|
|
|
17
|
|
640
|
|
|
|
|
|
|
return $DBI::err && $DBI::err == 942; # ORA-00942: table or view does not exist |
641
|
|
|
|
|
|
|
} |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
return $DBI::err && $DBI::err == 904; # ORA-00904: invalid identifier |
644
|
|
|
|
|
|
|
} |
645
|
0
|
|
|
0
|
1
|
0
|
|
646
|
0
|
|
|
|
|
0
|
my $self = shift; |
647
|
0
|
|
|
|
|
0
|
my $uri = $self->uri; |
648
|
|
|
|
|
|
|
my $conn = ''; |
649
|
|
|
|
|
|
|
# Use _port instead of port so it's empty if no port is in the URI. |
650
|
0
|
|
|
|
|
0
|
# https://github.com/sqitchers/sqitch/issues/675 |
651
|
|
|
|
|
|
|
my ($user, $pass, $host, $port) = ( |
652
|
|
|
|
|
|
|
$self->username, $self->password, $uri->host, $uri->_port |
653
|
0
|
|
|
|
|
0
|
); |
654
|
0
|
|
|
|
|
0
|
if ($user || $pass || $host || $port) { |
655
|
|
|
|
|
|
|
$conn = $user // ''; |
656
|
|
|
|
|
|
|
if ($pass) { |
657
|
0
|
|
|
|
|
0
|
$pass =~ s/"/""/g; |
658
|
|
|
|
|
|
|
$conn .= qq{/"$pass"}; |
659
|
|
|
|
|
|
|
} |
660
|
0
|
|
|
|
|
0
|
if (my $db = $uri->dbname) { |
661
|
0
|
|
|
|
|
0
|
$conn .= '@'; |
662
|
|
|
|
|
|
|
$db =~ s/"/""/g; |
663
|
|
|
|
|
|
|
if ($host || $port) { |
664
|
|
|
|
|
|
|
$conn .= '//' . ($host || ''); |
665
|
|
|
|
|
|
|
if ($port) { |
666
|
|
|
|
|
|
|
$conn .= ":$port"; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
$conn .= qq{/"$db"}; |
669
|
|
|
|
|
|
|
} else { |
670
|
|
|
|
|
|
|
$conn .= qq{"$db"}; |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
} else { |
674
|
|
|
|
|
|
|
# OS authentication or Oracle wallet (no username or password). |
675
|
|
|
|
|
|
|
if (my $db = $uri->dbname) { |
676
|
|
|
|
|
|
|
$db =~ s/"/""/g; |
677
|
0
|
|
|
|
|
0
|
$conn = qq{/@"$db"}; |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
} |
680
|
|
|
|
|
|
|
my %vars = $self->variables; |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
return join "\n" => ( |
683
|
0
|
|
|
|
|
0
|
'SET ECHO OFF NEWP 0 SPA 0 PAGES 0 FEED OFF HEAD OFF TRIMS ON TAB OFF VERIFY OFF', |
684
|
|
|
|
|
|
|
'WHENEVER OSERROR EXIT 9;', |
685
|
|
|
|
|
|
|
'WHENEVER SQLERROR EXIT SQL.SQLCODE;', |
686
|
|
|
|
|
|
|
(map {; (my $v = $vars{$_}) =~ s/"/""/g; qq{DEFINE $_="$v"} } sort keys %vars), |
687
|
4
|
|
100
|
4
|
|
46
|
"connect $conn", |
688
|
|
|
|
|
|
|
$self->_registry_variable, |
689
|
|
|
|
|
|
|
@_ |
690
|
|
|
|
|
|
|
); |
691
|
3
|
|
100
|
3
|
|
23
|
} |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
my $self = shift; |
694
|
|
|
|
|
|
|
my $script = $self->_script(@_); |
695
|
14
|
|
|
14
|
|
4020
|
open my $fh, '<:utf8_strict', \$script; |
696
|
14
|
|
|
|
|
267
|
return $self->sqitch->spool( $fh, $self->sqlplus ); |
697
|
14
|
|
|
|
|
341
|
} |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
my $self = shift; |
700
|
14
|
|
|
|
|
187
|
my $conn = $self->_script(@_); |
701
|
|
|
|
|
|
|
my @out; |
702
|
|
|
|
|
|
|
|
703
|
14
|
100
|
66
|
|
|
1293
|
require IPC::Run3; |
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
704
|
11
|
|
50
|
|
|
35
|
IPC::Run3::run3( |
705
|
11
|
50
|
|
|
|
19
|
[$self->sqlplus], \$conn, \@out, \@out, |
706
|
11
|
|
|
|
|
23
|
{ return_if_system_error => 1 }, |
707
|
11
|
|
|
|
|
19
|
); |
708
|
|
|
|
|
|
|
if (my $err = $?) { |
709
|
11
|
50
|
|
|
|
58
|
# Ugh, send everything to STDERR. |
710
|
11
|
|
|
|
|
603
|
$self->sqitch->vent(@out); |
711
|
11
|
|
|
|
|
19
|
hurl io => __x( |
712
|
11
|
100
|
66
|
|
|
30
|
'{command} unexpectedly returned exit value {exitval}', |
713
|
10
|
|
50
|
|
|
18
|
command => $self->client, |
714
|
10
|
100
|
|
|
|
17
|
exitval => ($err >> 8), |
715
|
9
|
|
|
|
|
11
|
); |
716
|
|
|
|
|
|
|
} |
717
|
10
|
|
|
|
|
16
|
|
718
|
|
|
|
|
|
|
return wantarray ? @out : \@out; |
719
|
1
|
|
|
|
|
3
|
} |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
1; |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
|
724
|
3
|
100
|
|
|
|
9
|
=head1 Name |
725
|
2
|
|
|
|
|
88
|
|
726
|
2
|
|
|
|
|
5
|
App::Sqitch::Engine::oracle - Sqitch Oracle Engine |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
=head1 Synopsis |
729
|
14
|
|
|
|
|
81
|
|
730
|
|
|
|
|
|
|
my $oracle = App::Sqitch::Engine->load( engine => 'oracle' ); |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
=head1 Description |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
App::Sqitch::Engine::oracle provides the Oracle storage engine for Sqitch. It |
735
|
14
|
|
|
|
|
121
|
supports Oracle 10g and higher. |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
8
|
|
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=head1 Interface |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
=head2 Instance Methods |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
=head3 C<initialized> |
742
|
|
|
|
|
|
|
|
743
|
1
|
|
|
1
|
|
3926
|
$oracle->initialize unless $oracle->initialized; |
744
|
1
|
|
|
|
|
3
|
|
745
|
1
|
|
|
1
|
|
25
|
Returns true if the database has been initialized for Sqitch, and false if it |
|
1
|
|
|
1
|
|
5
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
567
|
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
4
|
|
746
|
1
|
|
|
|
|
686
|
has not. |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
=head3 C<initialize> |
749
|
|
|
|
|
|
|
|
750
|
3
|
|
|
3
|
|
1492
|
$oracle->initialize; |
751
|
3
|
|
|
|
|
11
|
|
752
|
3
|
|
|
|
|
4
|
Initializes a database for Sqitch by installing the Sqitch registry schema. |
753
|
|
|
|
|
|
|
|
754
|
3
|
|
|
|
|
15
|
=head3 C<sqlplus> |
755
|
3
|
|
|
|
|
10
|
|
756
|
|
|
|
|
|
|
Returns a list containing the C<sqlplus> client and options to be passed to it. |
757
|
|
|
|
|
|
|
Used internally when executing scripts. |
758
|
|
|
|
|
|
|
|
759
|
3
|
100
|
|
|
|
19039
|
=head1 Author |
760
|
|
|
|
|
|
|
|
761
|
1
|
|
|
|
|
42
|
David E. Wheeler <david@justatheory.com> |
762
|
1
|
|
|
|
|
72
|
|
763
|
|
|
|
|
|
|
=head1 License |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
Copyright (c) 2012-2022 iovation Inc., David E. Wheeler |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
768
|
|
|
|
|
|
|
of this software and associated documentation files (the "Software"), to deal |
769
|
2
|
100
|
|
|
|
41
|
in the Software without restriction, including without limitation the rights |
770
|
|
|
|
|
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
771
|
|
|
|
|
|
|
copies of the Software, and to permit persons to whom the Software is |
772
|
|
|
|
|
|
|
furnished to do so, subject to the following conditions: |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included in all |
775
|
|
|
|
|
|
|
copies or substantial portions of the Software. |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
778
|
|
|
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
779
|
|
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
780
|
|
|
|
|
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
781
|
|
|
|
|
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
782
|
|
|
|
|
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
783
|
|
|
|
|
|
|
SOFTWARE. |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
=cut |