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