Passed
Push — master ( ba6342...71f198 )
by P.R.
07:12
created

backuppc_clone.DataLayer   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 861
Duplicated Lines 98.84 %

Importance

Changes 0
Metric Value
eloc 257
dl 851
loc 861
rs 3.28
c 0
b 0
f 0
wmc 64

41 Methods

Rating   Name   Duplication   Size   Complexity  
A DataLayer.overview_get_stats() 34 34 1
A DataLayer.backup_prepare_required_clone_pool_files() 29 29 1
A DataLayer.backup_prepare_tree() 38 38 1
A DataLayer.backup_partially_cloned() 16 16 1
A DataLayer.connect() 9 9 1
A DataLayer.backup_set_in_progress() 16 16 2
A DataLayer.disconnect() 6 6 1
A DataLayer.pool_prepare_obsolete_clone_files() 29 29 1
A DataLayer.parameter_update_value() 8 8 1
A DataLayer.vacuum() 5 5 1
A DataLayer.backup_get_all() 11 11 1
A DataLayer.pool_delete_row() 7 7 1
A DataLayer.pool_delete_obsolete_original_rows() 23 23 1
A DataLayer.__init__() 29 29 2
A DataLayer.parameter_get_value() 9 9 1
A DataLayer.dict_factory() 15 15 2
A DataLayer.backup_yield_tree() 27 27 3
A DataLayer.execute_row0() 21 21 2
A DataLayer.backup_get_next() 26 26 1
A DataLayer.execute_singleton1() 18 18 1
A DataLayer.get_host_id() 14 14 2
A DataLayer.backup_empty() 7 7 1
C DataLayer.import_csv() 42 42 10
A DataLayer.host_delete() 17 17 2
A DataLayer.backup_get_stats() 15 15 1
A DataLayer.execute_rows() 18 18 1
A DataLayer.original_backup_insert() 24 24 1
A DataLayer.original_backup_get_stats() 12 12 1
A DataLayer.backup_delete() 8 8 1
A DataLayer.pool_insert_new_original() 45 45 1
A DataLayer.execute_singleton0() 22 22 2
A DataLayer.host_get_obsolete() 14 14 1
A DataLayer.commit() 5 5 1
A DataLayer.backup_yield_required_clone_pool_files() 23 23 3
A DataLayer.get_bck_id() 21 21 2
A DataLayer.pool_yield_obsolete_clone_files() 20 20 3
A DataLayer.original_backup_truncate() 5 5 1
A DataLayer.execute_row1() 18 18 1
A DataLayer.backup_get_obsolete() 18 18 1
A DataLayer.execute_none() 13 13 1
A DataLayer.pool_update_by_inode_original() 22 22 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like backuppc_clone.DataLayer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
BackupPC Clone
3
"""
4
import csv
5
import os
6
import sqlite3
7
from typing import List, Dict, Optional
8
9
10 View Code Duplication
class DataLayer:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
11
    """
12
    Provides an interface to the SQLite3 data_layer
13
    """
14
    instance = None
15
    """
16
    The singleton instance of this class.
17
18
    :type instance: backuppc_clone.DataLayer.DataLayer
19
    """
20
21
    # ------------------------------------------------------------------------------------------------------------------
22
    def __init__(self, database: str):
23
        """
24
        Object constructor.
25
26
        :param str database: Path to the SQLite database.
27
        """
28
        if DataLayer.instance is not None:
29
            raise Exception("This class is a singleton!")
30
        else:
31
            DataLayer.instance = self
32
33
        self.__database: str = database
34
        """
35
        The path to the SQLite database.
36
        """
37
38
        self.__connection: sqlite3.Connection = sqlite3.connect(database, isolation_level="EXCLUSIVE")
39
        """
40
        The connection to the database.
41
        """
42
43
        self.__last_rowid: int = -1
44
        """
45
        The last rowid as returns by the last used cursor.
46
        """
47
48
        tmp_dir = os.path.join(os.path.dirname(database), 'tmp')
49
        self.execute_none('pragma temp_store = 1')
50
        self.execute_none('pragma temp_store_directory = \'{}\''.format(tmp_dir))
51
52
    # ------------------------------------------------------------------------------------------------------------------
53
    def disconnect(self) -> None:
54
        """
55
        Disconnects from the SQLite database.
56
        """
57
        self.__connection.commit()
58
        self.__connection.close()
59
60
    # ------------------------------------------------------------------------------------------------------------------
61
    def connect(self) -> None:
62
        """
63
        Connects to the SQLite database.
64
        """
65
        self.__connection = sqlite3.connect(self.__database, isolation_level="EXCLUSIVE")
66
67
        tmp_dir = os.path.join(os.path.dirname(self.__database), 'tmp')
68
        self.execute_none('pragma temp_store = 1')
69
        self.execute_none('pragma temp_store_directory = \'{}\''.format(tmp_dir))
70
71
    # ------------------------------------------------------------------------------------------------------------------
72
    def backup_delete(self, bck_id: int) -> None:
73
        """
74
        Deletes cascading a host backup.
75
76
        :param int bck_id: The ID of the host backup.
77
        """
78
        self.backup_empty(bck_id)
79
        self.execute_none('delete from BKC_BACKUP where bck_id=?', (bck_id,))
80
81
    # ------------------------------------------------------------------------------------------------------------------
82
    def backup_empty(self, bck_id: int) -> None:
83
        """
84
        Removes the tree from a host backup.
85
86
        :param int bck_id: The ID of the host backup.
87
        """
88
        self.execute_none('delete from BKC_BACKUP_TREE where bck_id=?', (bck_id,))
89
90
    # ------------------------------------------------------------------------------------------------------------------
91
    def backup_get_all(self) -> List[Dict]:
92
        """
93
        Selects all cloned backups.
94
        """
95
        sql = """
96
select hst.hst_name
97
,      bck.bck_number
98
from       BKC_HOST   hst
99
inner join BKC_BACKUP bck  on  bck.hst_id = hst.hst_id"""
100
101
        return self.execute_rows(sql)
102
103
    # ------------------------------------------------------------------------------------------------------------------
104
    def backup_get_next(self, end_time: int) -> Dict:
105
        """
106
        Selects the next backup to clone.
107
108
        :rtype: dict|None
109
        """
110
        sql = """
111
select bob.bob_host
112
,      bob.bob_number
113
,      bob.bob_end_time
114
,      bob.bob_level
115
,      bob.bob_type
116
from            BKC_ORIGINAL_BACKUP bob
117
left outer join BKC_HOST            hst  on  hst.hst_name = bob.bob_host
118
left outer join BKC_BACKUP          bck  on  bck.hst_id     = hst.hst_id and
119
                                             bck.bck_number = bob.bob_number
120
where bck.bck_id is null 
121
and   bob.bob_version like '3.%'
122
and   bob.bob_type in ('full', 'incr')
123
and   bob.bob_end_time is not null
124
and  (bob.bob_end_time < ? or ? = -1)
125
order by bob.bob_type 
126
,        bob.bob_end_time desc
127
limit 0, 1"""
128
129
        return self.execute_row0(sql, (end_time, end_time))
130
131
    # ------------------------------------------------------------------------------------------------------------------
132
    def backup_get_obsolete(self) -> List[Dict]:
133
        """
134
        Selects obsolete host backups.
135
136
        :rtype: list[dict]
137
        """
138
        sql = """
139
select hst.hst_name
140
,      bck.bck_number
141
from            BKC_HOST            hst
142
inner join      BKC_BACKUP          bck  on  bck.hst_id = hst.hst_id
143
left outer join BKC_ORIGINAL_BACKUP bob  on  bob.bob_host   = hst.hst_name and
144
                                             bob.bob_number = bck.bck_number
145
where bob.rowid is null
146
order by hst.hst_name
147
,        bck.bck_number"""
148
149
        return self.execute_rows(sql)
150
151
    # ------------------------------------------------------------------------------------------------------------------
152
    def backup_partially_cloned(self) -> List[Dict]:
153
        """
154
        Selects partially cloned host backups.
155
156
        :rtype: list[dict]
157
        """
158
        sql = """
159
select hst.hst_name
160
,      bck.bck_number
161
from            BKC_HOST   hst
162
inner join      BKC_BACKUP bck  on  bck.hst_id = hst.hst_id
163
where ifnull(bck.bck_in_progress, 1) = 1
164
order by hst.hst_name
165
,        bck.bck_number"""
166
167
        return self.execute_rows(sql)
168
169
    # ------------------------------------------------------------------------------------------------------------------
170
    def backup_set_in_progress(self, bck_id: int, bck_in_progress: int) -> None:
171
        """
172
        Updates the in progress flag of a host backup.
173
174
        :param int bck_id: The ID of the host backup.
175
        :param int bck_in_progress: The in progress flag.
176
        """
177
        if bck_in_progress != 0:
178
            bck_in_progress = 1
179
180
        sql = """
181
update BKC_BACKUP
182
set    bck_in_progress = ? 
183
where  bck_id = ?"""
184
185
        self.execute_none(sql, (bck_in_progress, bck_id))
186
187
    # ------------------------------------------------------------------------------------------------------------------
188
    def backup_get_stats(self, bck_id: int) -> Dict:
189
        """
190
        Selects the statistics of a host backup.
191
192
        :param int bck_id: The ID of the host backup.
193
        """
194
        self.__connection.row_factory = DataLayer.dict_factory
195
196
        sql = """
197
select count(bbt_inode_original)                                   as '#files'
198
,      sum(case when bbt_inode_original is null then 1 else 0 end) as '#dirs'
199
from BKC_BACKUP_TREE
200
where bck_id = ?"""
201
202
        return self.execute_row1(sql, (bck_id,))
203
204
    # ------------------------------------------------------------------------------------------------------------------
205
    def backup_prepare_required_clone_pool_files(self, bck_id: int) -> int:
206
        """
207
        Prepares the files required for a host backup that are not yet copied from the original pool to the clone pool.
208
209
        :param int bck_id: The ID of the host backup.
210
211
        :rtype: int
212
        """
213
        self.execute_none('delete from TMP_CLONE_POOL_REQUIRED')
214
215
        sql = """
216
insert into TMP_CLONE_POOL_REQUIRED( bpl_inode_original
217
,                                    bpl_dir
218
,                                    bpl_name )
219
select distinct bpl_inode_original
220
,               bpl_dir
221
,               bpl_name
222
from       BKC_BACKUP_TREE bbt
223
inner join BKC_POOL        bpl  on bpl.bpl_inode_original = bbt.bbt_inode_original
224
where bbt.bck_id = ?
225
and   bpl.bpl_inode_clone is null"""
226
227
        self.execute_none(sql, (bck_id,))
228
229
        sql = """
230
select count(distinct bpl_inode_original)
231
from   TMP_CLONE_POOL_REQUIRED"""
232
233
        return self.execute_singleton1(sql)
234
235
    # ------------------------------------------------------------------------------------------------------------------
236
    def backup_prepare_tree(self, bck_id: int) -> int:
237
        """
238
        Selects the file entries of a host backup.
239
240
        :param int bck_id: The ID of the host backup.
241
242
        :rtype: int
243
        """
244
        self.execute_none('delete from TMP_BACKUP_TREE')
245
246
        sql = """
247
insert into TMP_BACKUP_TREE( bpl_inode_original
248
,                            bpl_dir
249
,                            bpl_name   
250
251
,                            bbt_seq                             
252
,                            bbt_inode_original
253
,                            bbt_dir
254
,                            bbt_name )
255
select bpl.bpl_inode_original
256
,      bpl.bpl_dir
257
,      bpl.bpl_name
258
259
,      bbt.bbt_seq
260
,      bbt.bbt_inode_original
261
,      bbt.bbt_dir
262
,      bbt.bbt_name
263
from            BKC_BACKUP_TREE bbt
264
left outer join BKC_POOL        bpl  on bpl.bpl_inode_original = bbt.bbt_inode_original
265
where bbt.bck_id = ?"""
266
267
        self.execute_none(sql, (bck_id,))
268
269
        sql = """
270
select count(*)
271
from   TMP_BACKUP_TREE"""
272
273
        return self.execute_singleton1(sql)
274
275
    # ------------------------------------------------------------------------------------------------------------------
276
    def backup_yield_required_clone_pool_files(self):
277
        """
278
        Selects the pool files required for a host backup that are not yet copied from the original pool to the clone
279
        pool.
280
        """
281
        self.__connection.row_factory = DataLayer.dict_factory
282
283
        sql = """
284
select bpl_inode_original
285
,      bpl_dir
286
,      bpl_name
287
from   TMP_CLONE_POOL_REQUIRED
288
order by bpl_dir
289
,        bpl_name"""
290
291
        cursor = self.__connection.cursor()
292
        cursor.execute(sql)
293
        while True:
294
            rows = cursor.fetchmany(10000)
295
            if not rows:
296
                cursor.close()
297
                return
298
            yield rows
299
300
    # ------------------------------------------------------------------------------------------------------------------
301
    def backup_yield_tree(self):
302
        """
303
        Selects the file entries of a host backup.
304
        """
305
        self.__connection.row_factory = DataLayer.dict_factory
306
307
        sql = """
308
select bpl_inode_original
309
,      bpl_dir
310
,      bpl_name
311
312
,      bbt_inode_original
313
,      bbt_dir
314
,      bbt_name
315
from   TMP_BACKUP_TREE
316
order by bbt_seq
317
,        bpl_dir
318
,        bpl_name"""
319
320
        cursor = self.__connection.cursor()
321
        cursor.execute(sql)
322
        while True:
323
            rows = cursor.fetchmany(10000)
324
            if not rows:
325
                cursor.close()
326
                return
327
            yield rows
328
329
    # ------------------------------------------------------------------------------------------------------------------
330
    def commit(self) -> None:
331
        """
332
        Commits the current transaction.
333
        """
334
        self.__connection.commit()
335
336
    # ------------------------------------------------------------------------------------------------------------------
337
    @staticmethod
338
    def dict_factory(cursor: sqlite3.Cursor, old_row: List) -> Dict:
339
        """
340
        Dictionary factory for return results with dictionaries.
341
342
        :param Cursor cursor: The cursor.
343
        :param list old_row: A row from the result a a query.
344
345
        :rtype: dict
346
        """
347
        new_row = {}
348
        for index, col in enumerate(cursor.description):
349
            new_row[col[0]] = old_row[index]
350
351
        return new_row
352
353
    # ------------------------------------------------------------------------------------------------------------------
354
    def execute_none(self, sql: str, *params) -> None:
355
        """
356
        Executes a SQL statement that does not select any rows
357
358
        :param str sql: The SQL calling the stored procedure.
359
        :param iterable params: The arguments for the stored procedure.
360
        """
361
        self.__connection.row_factory = None
362
363
        cursor = self.__connection.cursor()
364
        cursor.execute(sql, *params)
365
        self.__last_rowid = cursor.lastrowid
366
        cursor.close()
367
368
    # ------------------------------------------------------------------------------------------------------------------
369
    def execute_row0(self, sql: str, *params) -> Optional[Dict]:
370
        """
371
        Executes a SQL statement that selects 0 or 1 row.
372
373
        :param str sql: The SQL calling the stored procedure.
374
        :param iterable params: The arguments for the stored procedure.
375
376
        :rtype: dict
377
        """
378
        self.__connection.row_factory = DataLayer.dict_factory
379
380
        cursor = self.__connection.cursor()
381
        cursor.execute(sql, *params)
382
        rows = cursor.fetchall()
383
        self.__last_rowid = cursor.lastrowid
384
        cursor.close()
385
386
        if len(rows) == 0:
387
            return None
388
389
        return rows[0]
390
391
    # ------------------------------------------------------------------------------------------------------------------
392
    def execute_row1(self, sql: str, *params) -> Dict:
393
        """
394
        Executes a SQL statement that selects 1 row.
395
396
        :param str sql: The SQL calling the stored procedure.
397
        :param iterable params: The arguments for the stored procedure.
398
399
        :rtype: dict
400
        """
401
        self.__connection.row_factory = DataLayer.dict_factory
402
403
        cursor = self.__connection.cursor()
404
        cursor.execute(sql, *params)
405
        rows = cursor.fetchall()
406
        self.__last_rowid = cursor.lastrowid
407
        cursor.close()
408
409
        return rows[0]
410
411
    # ------------------------------------------------------------------------------------------------------------------
412
    def execute_rows(self, sql: str, *params) -> List[Dict]:
413
        """
414
        Executes a SQL statement that selects 0, 1, or more rows.
415
416
        :param str sql: The SQL calling the stored procedure.
417
        :param iterable params: The arguments for the stored procedure.
418
419
        :rtype: list[dict]
420
        """
421
        self.__connection.row_factory = DataLayer.dict_factory
422
423
        cursor = self.__connection.cursor()
424
        cursor.execute(sql, *params)
425
        rows = cursor.fetchall()
426
        self.__last_rowid = cursor.lastrowid
427
        cursor.close()
428
429
        return rows
430
431
    # ------------------------------------------------------------------------------------------------------------------
432
    def execute_singleton0(self, sql: str, *params):
433
        """
434
        Executes a SQL statement that selects 0 or 1 row with 1 column. Returns the value of selected column or None.
435
436
        :param str sql: The SQL calling the stored procedure.
437
        :param iterable params: The arguments for the stored procedure.
438
439
        :rtype: *
440
        """
441
        self.__connection.row_factory = None
442
443
        cursor = self.__connection.cursor()
444
        cursor.execute(sql, *params)
445
        row = cursor.fetchone()
446
        if row:
447
            ret = row[0]
448
        else:
449
            ret = None
450
        self.__last_rowid = cursor.lastrowid
451
        cursor.close()
452
453
        return ret
454
455
    # ------------------------------------------------------------------------------------------------------------------
456
    def execute_singleton1(self, sql: str, *params):
457
        """
458
        Executes a SQL statement that selects 1 row with 1 column. Returns the value of selected column.
459
460
        :param str sql: The SQL calling the stored procedure.
461
        :param iterable params: The arguments for the stored procedure.
462
463
        :rtype: *
464
        """
465
        self.__connection.row_factory = None
466
467
        cursor = self.__connection.cursor()
468
        cursor.execute(sql, *params)
469
        ret = cursor.fetchone()[0]
470
        self.__last_rowid = cursor.lastrowid
471
        cursor.close()
472
473
        return ret
474
475
    # ------------------------------------------------------------------------------------------------------------------
476
    def get_host_id(self, hostname: str) -> int:
477
        """
478
        Returns the ID of a host. If the host does not exists it will be inserted.
479
480
        :param str hostname: The name of the host.
481
482
        :rtype: int
483
        """
484
        hst_id = self.execute_singleton0('select hst_id from BKC_HOST where hst_name = ?', (hostname,))
485
        if hst_id is None:
486
            self.execute_none('insert into BKC_HOST(hst_name) values(?)', (hostname,))
487
            hst_id = self.__last_rowid
488
489
        return hst_id
490
491
    # ------------------------------------------------------------------------------------------------------------------
492
    def get_bck_id(self, hst_id: int, bck_number: int) -> int:
493
        """
494
        Returns the ID of a host backup. If the backup does not exists it will be inserted.
495
496
        :param int hst_id: The ID of the host.
497
        :param int bck_number: The number of the backup.
498
499
        :rtype: int
500
        """
501
        sql = """
502
select bck_id 
503
from   BKC_BACKUP 
504
where  hst_id     = ?
505
and    bck_number = ?"""
506
507
        bck_id = self.execute_singleton0(sql, (hst_id, bck_number))
508
        if bck_id is None:
509
            self.execute_none('insert into BKC_BACKUP(hst_id, bck_number) values(?, ?)', (hst_id, bck_number))
510
            bck_id = self.__last_rowid
511
512
        return bck_id
513
514
    # ------------------------------------------------------------------------------------------------------------------
515
    def host_delete(self, host: str) -> None:
516
        """
517
        Deletes cascading a host.
518
519
        :param str host: The name of the host.
520
        """
521
        sql = """
522
select bck_id
523
from       BKC_HOST   hst
524
inner join BKC_BACKUP bck  on  bck.hst_id = hst.hst_id
525
where hst.hst_name = ?"""
526
527
        rows = self.execute_rows(sql, (host,))
528
        for row in rows:
529
            self.backup_delete(row['bck_id'])
530
531
        self.execute_none('delete from BKC_HOST where hst_name=?', (host,))
532
533
    # ------------------------------------------------------------------------------------------------------------------
534
    def host_get_obsolete(self) -> List[Dict]:
535
        """
536
        Selects host that are cloned but not longer in original.
537
538
        :rtype: list[dict]
539
        """
540
        sql = """
541
select hst.hst_name
542
from            BKC_HOST            hst
543
left outer join BKC_ORIGINAL_BACKUP bob  on  bob.bob_host = hst.hst_name
544
where bob.rowid is null 
545
order by hst.hst_name"""
546
547
        return self.execute_rows(sql)
548
549
    # ------------------------------------------------------------------------------------------------------------------
550
    def import_csv(self,
551
                   table_name: str,
552
                   column_names: List[str],
553
                   path: str,
554
                   truncate: bool = True,
555
                   defaults: Dict = None):
556
        """
557
        Import a CSV file into a table.
558
559
        :param str table_name: The name of the table.
560
        :param list column_names: The columns names.
561
        :param str path: The path to the CSV file.
562
        :param bool truncate:" If True the table will be truncated first.
563
        :param dict[str,*]|None defaults: The default values for columns not in the CSV file.
564
        """
565
        if truncate:
566
            self.execute_none('delete from {}'.format(table_name))
567
568
        default_values = []
569
        if defaults:
570
            for column_name in defaults:
571
                column_names.append(column_name)
572
                default_values.append(defaults[column_name])
573
574
        place_holders = []
575
        for _ in range(0, len(column_names)):
576
            place_holders.append('?')
577
578
        sql = 'insert into {}({}) values ({})'.format(table_name, ', '.join(column_names), ', '.join(place_holders))
579
        cursor = self.__connection.cursor()
580
        with open(path, 'r') as csv_file:
581
            csv_reader = csv.reader(csv_file)
582
            for row in csv_reader:
583
                # Replace empty string with None.
584
                for index, field in enumerate(row):
585
                    if field == '':
586
                        row[index] = None
587
588
                if defaults:
589
                    row.extend(default_values)
590
                cursor.execute(sql, row)
591
        cursor.close()
592
593
    # ------------------------------------------------------------------------------------------------------------------
594
    def original_backup_insert(self,
595
                               bob_host: str,
596
                               bob_number: int,
597
                               bob_end_time: str,
598
                               bob_version: int,
599
                               bob_level: int,
600
                               bob_type: str) -> None:
601
        """
602
        Inserts an original host backup.
603
        """
604
        sql = """
605
insert into BKC_ORIGINAL_BACKUP( bob_host
606
,                                bob_number
607
,                                bob_end_time
608
,                                bob_version
609
,                                bob_level
610
,                                bob_type )
611
values( ?
612
,       ?
613
,       ?
614
,       ?
615
,       ?
616
,       ? )"""
617
        self.execute_none(sql, (bob_host, bob_number, bob_end_time, bob_version, bob_level, bob_type))
618
619
    # ------------------------------------------------------------------------------------------------------------------
620
    def original_backup_get_stats(self) -> Dict:
621
        """
622
        Select statistics of the original backups.
623
624
        :rtype: dict
625
        """
626
        sql = """
627
select count(distinct bob_host) as '#hosts'
628
,      count(*)                 as '#backups'
629
from   BKC_ORIGINAL_BACKUP"""
630
631
        return self.execute_row1(sql)
632
633
    # ------------------------------------------------------------------------------------------------------------------
634
    def original_backup_truncate(self) -> None:
635
        """
636
        Truncates table BKC_ORIGINAL_BACKUP.
637
        """
638
        self.execute_none('delete from BKC_ORIGINAL_BACKUP')
639
640
    # ------------------------------------------------------------------------------------------------------------------
641
    def overview_get_stats(self) -> Dict:
642
        """
643
        Select statistics of the original backups and cloned backups.
644
645
        :rtype: dict
646
        """
647
        sql = """
648
select sum(case when cnt1=1 then 1 else 0 end)            n_backups
649
,      sum(case when cnt1=1 and cnt2=1 then 1 else 0 end) n_cloned_backups
650
,      sum(case when cnt1=1 and cnt2=0 then 1 else 0 end) n_not_cloned_backups
651
,      sum(case when cnt1=0 and cnt2=1 then 1 else 0 end) n_obsolete_cloned_backups
652
from
653
(
654
  select sum(case when src=1 then 1 else 0 end) cnt1
655
  ,      sum(case when src=2 then 1 else 0 end) cnt2
656
  from (
657
         select bob_host
658
         ,      bob_number
659
         ,      1 src
660
         from BKC_ORIGINAL_BACKUP
661
662
         union all
663
664
         select hst.hst_name
665
         ,      bck.bck_number
666
         ,      2  src
667
         from BKC_BACKUP    bck
668
         join BKC_HOST hst on hst.hst_id = bck.hst_id
669
       ) t
670
  group by bob_host
671
  ,        bob_number
672
)"""
673
674
        return self.execute_row1(sql)
675
676
    # ------------------------------------------------------------------------------------------------------------------
677
    def parameter_get_value(self, prm_code: str) -> str:
678
        """
679
        Select the value of a parameter.
680
681
        :param str prm_code: The code of the parameter.
682
683
        :rtype: *
684
        """
685
        return self.execute_singleton1('select prm_value from BKC_PARAMETER where prm_code = ?', (prm_code,))
686
687
    # ------------------------------------------------------------------------------------------------------------------
688
    def parameter_update_value(self, prm_code: str, prm_value: str) -> None:
689
        """
690
        Sets the value of a parameter.
691
692
        :param str prm_code: The code of the parameter.
693
        :param str prm_value: The value of the parameter.
694
        """
695
        self.execute_none('update BKC_PARAMETER set prm_value = ? where prm_code = ?', (prm_value, prm_code))
696
697
    # ------------------------------------------------------------------------------------------------------------------
698
    def pool_delete_obsolete_original_rows(self) -> int:
699
        """
700
        Deletes rows (i.e. files) from BKC_POOL that are not longer in the actual original pool.
701
        """
702
        self.execute_none('delete from TMP_ID')
703
704
        sql = """
705
insert into TMP_ID(tmp_id)
706
select bpl.bpl_id
707
from            BKC_POOL bpl
708
left outer join IMP_POOL imp  on  imp.imp_inode = bpl.bpl_inode_original and
709
                                  imp.imp_dir   = bpl.bpl_dir            and
710
                                  imp.imp_name  = bpl.bpl_name
711
where bpl.bpl_inode_original is not null 
712
and   imp.rowid is null"""
713
        self.execute_none(sql)
714
715
        sql = """
716
delete from BKC_POOL
717
where bpl_id in (select tmp_id from TMP_ID)"""
718
        self.execute_none(sql)
719
720
        return self.execute_singleton1('select count(*) from TMP_ID')
721
722
    # ------------------------------------------------------------------------------------------------------------------
723
    def pool_delete_row(self, bpl_id: int) -> None:
724
        """
725
        Deletes a row from the pool metadata.
726
727
        :param int bpl_id: The rowid.
728
        """
729
        self.execute_none('delete from BKC_POOL where bpl_id=?', (bpl_id,))
730
731
    # ------------------------------------------------------------------------------------------------------------------
732
    def pool_insert_new_original(self) -> None:
733
        """
734
        Inserts new row into BKC_POOL based on an import from the original pool.
735
        """
736
        self.execute_none('delete from TMP_POOL')
737
738
        sql = """
739
insert into TMP_POOL( tmp_inode
740
,                     tmp_dir
741
,                     tmp_name )
742
select tmp_inode
743
,      tmp_dir
744
,      tmp_name
745
from
746
(
747
  select bpl_inode_original as tmp_inode
748
  ,      bpl_dir            as tmp_dir
749
  ,      bpl_name           as tmp_name
750
  ,      1                  as src
751
  from   BKC_POOL
752
753
  union all
754
755
  select imp_inode
756
  ,      imp_dir
757
  ,      imp_name
758
  ,      2                  as src
759
  from   IMP_POOL
760
) t
761
group by tmp_inode
762
,        tmp_dir
763
,        tmp_name
764
having sum(case when src=1 then 1 else 0 end) < sum(case when src=2 then 1 else 0 end)"""
765
        self.execute_none(sql)
766
767
        sql = """
768
insert into BKC_POOL( bpl_inode_original
769
,                     bpl_dir
770
,                     bpl_name )
771
select tmp_inode
772
,      tmp_dir
773
,      tmp_name
774
from   TMP_POOL"""
775
776
        self.execute_none(sql)
777
778
    # ------------------------------------------------------------------------------------------------------------------
779
    def pool_prepare_obsolete_clone_files(self) -> int:
780
        """
781
        Prepares the clone pool files that are obsolete (i.e. not longer in the original pool).
782
783
        :rtype: int
784
        """
785
        self.execute_none('delete from TMP_CLONE_POOL_OBSOLETE')
786
787
        sql = """
788
insert into TMP_CLONE_POOL_OBSOLETE( bpl_id
789
,                                    bpl_dir
790
,                                    bpl_name )
791
select bpl.bpl_id
792
,      bpl.bpl_dir
793
,      bpl.bpl_name
794
from            BKC_POOL bpl
795
left outer join IMP_POOL imp  on  imp.imp_inode = bpl.bpl_inode_original and
796
                                  imp.imp_dir   = bpl.bpl_dir            and
797
                                  imp.imp_name  = bpl.bpl_name
798
where  bpl.bpl_inode_clone is not null
799
and    imp.rowid is null"""
800
801
        self.execute_none(sql)
802
803
        sql = """
804
select count(*)
805
from   TMP_CLONE_POOL_OBSOLETE"""
806
807
        return self.execute_singleton1(sql)
808
809
    # ------------------------------------------------------------------------------------------------------------------
810
    def pool_update_by_inode_original(self,
811
                                      bpl_inode_original: int,
812
                                      bpl_inode_clone: int,
813
                                      pbl_size: int,
814
                                      pbl_mtime: int) -> None:
815
        """
816
        Sets the inode number of the clone, mtime and size of a file in the pool given a inode number of a file the the
817
        original pool.
818
819
        :param int bpl_inode_original: The inode number of a file file in the original pool.
820
        :param int bpl_inode_clone: The inode number of the pool file in the clone.
821
        :param int pbl_size: The size of the pool file.
822
        :param int pbl_mtime: The mtime of the pool file.
823
        """
824
        sql = """
825
update BKC_POOL
826
set  bpl_inode_clone = ?
827
,    bpl_size        = ?
828
,    bpl_mtime       = ?
829
where bpl_inode_original = ?"""
830
831
        self.execute_none(sql, (bpl_inode_clone, pbl_size, pbl_mtime, bpl_inode_original))
832
833
    # ------------------------------------------------------------------------------------------------------------------
834
    def pool_yield_obsolete_clone_files(self):
835
        """
836
        Selects the clone pool files that are obsolete (i.e. not longer in the original pool).
837
        """
838
        self.__connection.row_factory = DataLayer.dict_factory
839
840
        sql = """
841
select bpl_id
842
,      bpl_dir
843
,      bpl_name
844
from   TMP_CLONE_POOL_OBSOLETE"""
845
846
        cursor = self.__connection.cursor()
847
        cursor.execute(sql)
848
        while True:
849
            rows = cursor.fetchmany(10000)
850
            if not rows:
851
                cursor.close()
852
                return
853
            yield rows
854
855
    # ------------------------------------------------------------------------------------------------------------------
856
    def vacuum(self) -> None:
857
        """
858
        Executes the vacuum command.
859
        """
860
        self.execute_none('vacuum')
861
862
# ----------------------------------------------------------------------------------------------------------------------
863