rda-python-common 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rda-python-common might be problematic. Click here for more details.

@@ -0,0 +1,2217 @@
1
+ #
2
+ ###############################################################################
3
+ #
4
+ # Title : PgDBI.py -- PostgreSQL DataBase Interface
5
+ # Author : Zaihua Ji, zji@ucar.edu
6
+ # Date : 06/07/2022
7
+ # Purpose : Python library module to handle query and manipulate PostgreSQL database
8
+ #
9
+ # Work File : $DSSHOME/lib/python/PgDBI.py
10
+ # Github : https://github.com/NCAR/rda-shared-libraries.git
11
+ #
12
+ ###############################################################################
13
+
14
+ import os
15
+ import re
16
+ import time
17
+ from datetime import datetime
18
+ import psycopg2 as PgSQL
19
+ from psycopg2.extras import execute_values
20
+ from psycopg2.extras import execute_batch
21
+ from os import path as op
22
+ import PgLOG
23
+
24
+ pgdb = None # reference to a connected database object
25
+ curtran = 0 # 0 - no transaction, 1 - in transaction
26
+ NMISSES = [] # array of mising userno
27
+ LMISSES = [] # array of mising logname
28
+ TABLES = {} # record table field information
29
+ SEQUENCES = {} # record table sequence fielnames
30
+ SPECIALIST = {} # hash array refrences to specialist info of dsids
31
+ SYSDOWN = {}
32
+ PGDBI = {}
33
+ ADDTBLS = []
34
+ PGSIGNS = ['!', '<', '>', '<>']
35
+ CHCODE = 1042
36
+
37
+ # hard coded db ports for dbnames
38
+ DBPORTS = {
39
+ 'default' : 0 # skip default port number 5432
40
+ }
41
+
42
+ # hard coded db names for given schema names
43
+ DBNAMES = {
44
+ 'ivaddb' : 'ivaddb',
45
+ 'cntldb' : 'ivaddb',
46
+ 'cdmsdb' : 'ivaddb',
47
+ 'ispddb' : 'ispddb',
48
+ 'obsua' : 'upadb',
49
+ 'default' : 'rdadb',
50
+ }
51
+
52
+ # hard coded socket paths for machine_dbnames
53
+ DBSOCKS = {
54
+ 'default' : '',
55
+ }
56
+
57
+ # home path for check db on alter host
58
+ VIEWHOMES = {
59
+ 'default' : PgLOG.PGLOG['DSSDBHM']
60
+ }
61
+
62
+ # add more to the list if used for names
63
+ PGRES = ['end', 'window']
64
+
65
+ #
66
+ # PostgreSQL specified query timestamp format
67
+ #
68
+ fmtyr = lambda fn: "extract(year from {})::int".format(fn)
69
+ fmtqt = lambda fn: "extract(quarter from {})::int".format(fn)
70
+ fmtmn = lambda fn: "extract(month from {})::int".format(fn)
71
+ fmtdt = lambda fn: "date({})".format(fn)
72
+ fmtym = lambda fn: "to_char({}, 'yyyy-mm')".format(fn)
73
+ fmthr = lambda fn: "extract(hour from {})::int".format(fn)
74
+
75
+ #
76
+ # set environments and defaults
77
+ #
78
+ def SETPGDBI(name, value):
79
+ PGDBI[name] = PgLOG.get_environment(name, value)
80
+
81
+ SETPGDBI('CDHOST', 'rda-db.ucar.edu') # common domain for db host for master server
82
+ SETPGDBI('DEFDB', 'rdadb')
83
+ SETPGDBI("DEFSC", 'dssdb')
84
+ SETPGDBI('DEFHOST', PgLOG.PGLOG['PSQLHOST'])
85
+ SETPGDBI("DEFPORT", 0)
86
+ SETPGDBI("DEFSOCK", '')
87
+ SETPGDBI("DBNAME", PGDBI['DEFDB'])
88
+ SETPGDBI("SCNAME", PGDBI['DEFSC'])
89
+ SETPGDBI("LNNAME", PGDBI['DEFSC'])
90
+ SETPGDBI("PWNAME", PGDBI['DEFSC'])
91
+ SETPGDBI("DBHOST", (os.environ['DSSDBHOST'] if os.environ.get('DSSDBHOST') else PGDBI['DEFHOST']))
92
+ SETPGDBI("DBPORT", 0)
93
+ SETPGDBI("ERRLOG", PgLOG.LOGERR) # default error logact
94
+ SETPGDBI("EXITLG", PgLOG.LGEREX) # default exit logact
95
+ SETPGDBI("DBSOCK", '')
96
+ SETPGDBI("DATADIR", PgLOG.PGLOG['DSDHOME'])
97
+ SETPGDBI("BCKPATH", PgLOG.PGLOG['DSSDBHM'] + "/backup")
98
+ SETPGDBI("SQLPATH", PgLOG.PGLOG['DSSDBHM'] + "/sql")
99
+ SETPGDBI("VWNAME", PGDBI['DEFSC'])
100
+ SETPGDBI("VWPORT", 0)
101
+ SETPGDBI("VWSOCK", '')
102
+
103
+ PGDBI['DBSHOST'] = PgLOG.get_short_host(PGDBI['DBHOST'])
104
+ PGDBI['DEFSHOST'] = PgLOG.get_short_host(PGDBI['DEFHOST'])
105
+ PGDBI['VWHOST'] = PgLOG.PGLOG['PVIEWHOST']
106
+ PGDBI['VWSHOST'] = PgLOG.get_short_host(PGDBI['VWHOST'])
107
+ PGDBI['VWHOME'] = (VIEWHOMES[PgLOG.PGLOG['HOSTNAME']] if PgLOG.PGLOG['HOSTNAME'] in VIEWHOMES else VIEWHOMES['default'])
108
+ PGDBI['SCPATH'] = None # additional schema path for set search_path
109
+ PGDBI['VHSET'] = 0
110
+ PGDBI['PGSIZE'] = 1000 # number of records for page_size
111
+ PGDBI['MTRANS'] = 5000 # max number of changes in one transactions
112
+ PGDBI['MAXICNT'] = 12000000 # maximum number of records in each table
113
+
114
+ #
115
+ # create a pgddl command string with
116
+ # table name (tname), prefix (pre) and suffix (suf)
117
+ #
118
+ def get_pgddl_command(tname, pre = None, suf = None):
119
+
120
+ ms = re.match(r'^(.+)\.(.+)$', tname)
121
+ if ms:
122
+ scname = ms.group(1)
123
+ tname = ms.group(2)
124
+ else:
125
+ scname = PGDBI['SCNAME']
126
+ xy = ''
127
+ if suf: xy += ' -x ' + suf
128
+ if pre: xy += ' -y ' + pre
129
+ return "pgddl {} -aa -h {} -d {} -c {} -u {}{}".format(tname, PGDBI['DBHOST'], PGDBI['DBNAME'], scname, PGDBI['LNNAME'], xy)
130
+
131
+ #
132
+ # set default connection for dssdb PostgreSQL Server
133
+ #
134
+ def dssdb_dbname():
135
+ default_scinfo(PGDBI['DEFDB'], PGDBI['DEFSC'], PgLOG.PGLOG['PSQLHOST'])
136
+
137
+ dssdb_scname = dssdb_dbname
138
+
139
+ #
140
+ # set default connection for obsua PostgreSQL Server
141
+ #
142
+ def obsua_dbname():
143
+ default_scinfo('upadb', 'obsua', "rda-pgdb-03.ucar.edu")
144
+
145
+ obsua_scname = obsua_dbname
146
+
147
+ #
148
+ # set default connection for ivaddb PostgreSQL Server
149
+ #
150
+ def ivaddb_dbname():
151
+ default_scinfo('ivaddb', 'ivaddb', "rda-pgdb-03.ucar.edu")
152
+
153
+ ivaddb_scname = ivaddb_dbname
154
+
155
+ #
156
+ # set default connection for ispddb PostgreSQL Server
157
+ #
158
+ def ispddb_dbname():
159
+ default_scinfo('ispddb', 'ispddb', "rda-pgdb-03.ucar.edu")
160
+
161
+ ispddb_scname = ispddb_dbname
162
+
163
+ #
164
+ # set a default schema info with hard coded info
165
+ #
166
+ def default_dbinfo(scname = None, dbhost = None, lnname = None, pwname = None, dbport = None, socket = None):
167
+
168
+ return default_scinfo(get_dbname(scname), scname, dbhost, lnname, pwname, dbport, socket)
169
+
170
+ #
171
+ # set default database/schema info with hard coded info
172
+ #
173
+ def default_scinfo(dbname = None, scname = None, dbhost = None, lnname = None, pwname = None, dbport = None, socket = None):
174
+
175
+ if not dbname: dbname = PGDBI['DEFDB']
176
+ if not scname: scname = PGDBI['DEFSC']
177
+ if not dbhost: dbhost = PGDBI['DEFHOST']
178
+ if dbport is None: dbport = PGDBI['DEFPORT']
179
+ if socket is None: socket = PGDBI['DEFSOCK']
180
+
181
+ set_scname(dbname, scname, lnname, pwname, dbhost, dbport, socket)
182
+
183
+ #
184
+ # get the datbase sock file name of a given dbname for local connection
185
+ #
186
+ def get_dbsock(dbname):
187
+
188
+ return (DBSOCKS[dbname] if dbname in DBSOCKS else DBSOCKS['default'])
189
+
190
+ #
191
+ # get the datbase port number of a given dbname for remote connection
192
+ #
193
+ def get_dbport(dbname):
194
+
195
+ return (DBPORTS[dbname] if dbname in DBPORTS else DBPORTS['default'])
196
+
197
+ #
198
+ # get the datbase name of a given schema name for remote connection
199
+ #
200
+ def get_dbname(scname):
201
+
202
+ if scname:
203
+ if scname in DBNAMES: return DBNAMES[scname]
204
+ return DBNAMES['default']
205
+ return None
206
+
207
+ #
208
+ # set connection for viewing database information
209
+ #
210
+ def view_dbinfo(scname = None, lnname = None, pwname = None):
211
+
212
+ return view_scinfo(get_dbname(scname), scname, lnname, pwname)
213
+
214
+ #
215
+ # set connection for viewing database/schema information
216
+ #
217
+ def view_scinfo(dbname = None, scname = None, lnname = None, pwname = None):
218
+
219
+ if not dbname: dbname = PGDBI['DEFDB']
220
+ if not scname: scname = PGDBI['DEFSC']
221
+
222
+ set_scname(dbname, scname, lnname, pwname, PgLOG.PGLOG['PVIEWHOST'], PGDBI['VWPORT'])
223
+
224
+ #
225
+ # set connection for given scname
226
+ #
227
+ def set_dbname(scname = None, lnname = None, pwname = None, dbhost = None, dbport = None, socket = None):
228
+
229
+ if not scname: scname = PGDBI['DEFSC']
230
+ return set_scname(get_dbname(scname), scname, lnname, pwname, dbhost, dbport, socket)
231
+
232
+ #
233
+ # set connection for given database & schema names
234
+ #
235
+ def set_scname(dbname = None, scname = None, lnname = None, pwname = None, dbhost = None, dbport = None, socket = None):
236
+
237
+ global pgdb
238
+ changed = 0
239
+
240
+ if dbname and dbname != PGDBI['DBNAME']:
241
+ PGDBI['DBNAME'] = dbname
242
+ changed = 1
243
+ if scname and scname != PGDBI['SCNAME']:
244
+ PGDBI['PWNAME'] = PGDBI['LNNAME'] = PGDBI['SCNAME'] = scname
245
+ changed = 1
246
+ if lnname and lnname != PGDBI['LNNAME']:
247
+ PGDBI['PWNAME'] = PGDBI['LNNAME'] = lnname
248
+ changed = 1
249
+ if pwname and pwname != PGDBI['PWNAME']:
250
+ PGDBI['PWNAME'] = pwname
251
+ changed = 1
252
+ if dbhost and dbhost != PGDBI['DBHOST']:
253
+ PGDBI['DBHOST'] = dbhost
254
+ PGDBI['DBSHOST'] = PgLOG.get_short_host(dbhost)
255
+ changed = 1
256
+ if PGDBI['DBSHOST'] == PgLOG.PGLOG['HOSTNAME']:
257
+ if socket is None: socket = get_dbsock(dbname)
258
+ if socket != PGDBI['DBSOCK']:
259
+ PGDBI['DBSOCK'] = socket
260
+ changed = 1
261
+ else:
262
+ if not dbport: dbport = get_dbport(dbname)
263
+ if dbport != PGDBI['DBPORT']:
264
+ PGDBI['DBPORT'] = dbport
265
+ changed = 1
266
+
267
+ if changed and pgdb is not None: pgdisconnect(1)
268
+
269
+ #
270
+ # start a database transaction and exit if fails
271
+ #
272
+ def starttran():
273
+
274
+ global curtran
275
+ global pgdb
276
+
277
+ if curtran == 1: endtran() # try to end previous transaction
278
+ if not pgdb:
279
+ pgconnect(0, 0, False)
280
+ else:
281
+ try:
282
+ pgdb.isolation_level
283
+ except PgSQL.OperationalError as e:
284
+ pgconnect(0, 0, False)
285
+ if pgdb.closed:
286
+ pgconnect(0, 0, False)
287
+ elif pgdb.autocommit:
288
+ pgdb.autocommit = False
289
+ curtran = 1
290
+
291
+ #
292
+ # end a transaction with changes committed and exit if fails
293
+ #
294
+ def endtran(autocommit = True):
295
+
296
+ global curtran
297
+ global pgdb
298
+ if curtran and pgdb:
299
+ if not pgdb.closed: pgdb.commit()
300
+ pgdb.autocommit = autocommit
301
+ curtran = 0 if autocommit else 1
302
+
303
+ #
304
+ # end a transaction without changes committed and exit inside if fails
305
+ #
306
+ def aborttran(autocommit = True):
307
+
308
+ global curtran
309
+ global pgdb
310
+ if curtran and pgdb:
311
+ if not pgdb.closed: pgdb.rollback()
312
+ pgdb.autocommit = autocommit
313
+ curtran = 0 if autocommit else 1
314
+
315
+ #
316
+ # record error message to dscheck record and clean the lock
317
+ #
318
+ def record_dscheck_error(errmsg):
319
+
320
+ cnd = PgLOG.PGLOG['DSCHECK']['chkcnd']
321
+ if PgLOG.PGLOG['NOQUIT']: PgLOG.PGLOG['NOQUIT'] = 0
322
+ dflags = PgLOG.PGLOG['DSCHECK']['dflags']
323
+
324
+ pgrec = pgget("dscheck", "mcount, tcount, lockhost, pid", cnd, PgLOG.LGEREX)
325
+ if not pgrec: return 0
326
+ if not pgrec['pid'] and not pgrec['lockhost']: return 0
327
+ (chost, cpid) = PgLOG.current_process_info()
328
+ if pgrec['pid'] != cpid or pgrec['lockhost'] != chost: return 0
329
+
330
+ # update dscheck record only if it is still locked by the current process
331
+ record = {}
332
+ record['chktime'] = int(time.time())
333
+ record['status'] = "E"
334
+ record['pid'] = 0 # release lock
335
+ if dflags:
336
+ record['dflags'] = dflags
337
+ record['mcount'] = pgrec['mcount'] + 1
338
+ else:
339
+ record['dflags'] = ''
340
+
341
+ if errmsg:
342
+ errmsg = PgLOG.break_long_string(errmsg, 512, None, 50, None, 50, 25)
343
+ if pgrec['tcount'] > 1: errmsg = "Try {}: {}".format(pgrec['tcount'], errmsg)
344
+ record['errmsg'] = errmsg
345
+
346
+ return pgupdt("dscheck", record, cnd, PGDBI['ERRLOG'])
347
+
348
+ #
349
+ # local function to log query error
350
+ #
351
+ def qelog(dberror, sleep, sqlstr, vals, pgcnt, logact = PGDBI['ERRLOG']):
352
+
353
+ retry = " Sleep {}(sec) & ".format(sleep) if sleep else " "
354
+ if sqlstr:
355
+ if sqlstr.find("Retry ") == 0:
356
+ retry += "the {} ".format(PgLOG.int2order(pgcnt+1))
357
+ elif sleep:
358
+ retry += "the {} Retry: \n".format(PgLOG.int2order(pgcnt+1))
359
+ elif pgcnt:
360
+ retry = " Error the {} Retry: \n".format(PgLOG.int2order(pgcnt))
361
+ else:
362
+ retry = "\n"
363
+ sqlstr = retry + sqlstr
364
+ else:
365
+ sqlstr = ''
366
+
367
+ if vals: sqlstr += " with values: " + str(vals)
368
+
369
+ if dberror: sqlstr = "{}\n{}".format(dberror, sqlstr)
370
+ if logact&PgLOG.EXITLG and PgLOG.PGLOG['DSCHECK']: record_dscheck_error(sqlstr)
371
+ PgLOG.pglog(sqlstr, logact)
372
+ if sleep: time.sleep(sleep)
373
+
374
+ return PgLOG.FAILURE # if not exit in PgLOG.pglog()
375
+
376
+ #
377
+ # try to add a new table according the table not exist error
378
+ #
379
+ def try_add_table(dberror, logact):
380
+
381
+ ms = re.match(r'^42P01 ERROR: relation "(.+)" does not exist', dberror)
382
+ if ms:
383
+ tname = ms.group(1)
384
+ add_new_table(tname, logact = logact)
385
+
386
+ #
387
+ # add a table for given table name
388
+ #
389
+ def add_a_table(tname, logact):
390
+
391
+ add_new_table(tname, logact = logact)
392
+
393
+ #
394
+ # add a new table for given table name
395
+ #
396
+ def add_new_table(tname, pre = None, suf = None, logact = 0):
397
+
398
+ if pre:
399
+ tbname = '{}_{}'.format(pre, tname)
400
+ elif suf:
401
+ tbname = '{}_{}'.format(tname, suf)
402
+ else:
403
+ tbname = tname
404
+ if tbname in ADDTBLS: return
405
+
406
+ PgLOG.pgsystem(get_pgddl_command(tname, pre, suf), logact)
407
+ ADDTBLS.append(tbname)
408
+
409
+ #
410
+ # validate a table for given table name (tname), prefix (pre) and suffix (suf),
411
+ # and add it if not existing
412
+ #
413
+ def valid_table(tname, pre = None, suf = None, logact = 0):
414
+
415
+ if pre:
416
+ tbname = '{}_{}'.format(pre, tname)
417
+ elif suf:
418
+ tbname = '{}_{}'.format(tname, suf)
419
+ else:
420
+ tbname = tname
421
+ if tbname in ADDTBLS: return tbname
422
+
423
+ if not pgcheck(tbname, logact): PgLOG.pgsystem(get_pgddl_command(tname, pre, suf), logact)
424
+ ADDTBLS.append(tbname)
425
+ return tbname
426
+
427
+ #
428
+ # local function to log query error
429
+ #
430
+ def check_dberror(pgerr, pgcnt, sqlstr, ary, logact = PGDBI['ERRLOG']):
431
+
432
+ ret = PgLOG.FAILURE
433
+
434
+ pgcode = pgerr.pgcode
435
+ pgerror = pgerr.pgerror
436
+ dberror = "{} {}".format(pgcode, pgerror) if pgcode and pgerror else str(pgerr)
437
+ if pgcnt < PgLOG.PGLOG['DBRETRY']:
438
+ if not pgcode:
439
+ if PGDBI['DBNAME'] == PGDBI['DEFDB'] and PGDBI['DBSHOST'] != PGDBI['DEFSHOST']:
440
+ default_dbinfo()
441
+ qelog(dberror, 0, "Retry Connecting to {} on {}".format(PGDBI['DBNAME'], PGDBI['DBHOST']), ary, pgcnt, PgLOG.MSGLOG)
442
+ else:
443
+ qelog(dberror, 5+5*pgcnt, "Retry Connecting", ary, pgcnt, PgLOG.LOGWRN)
444
+ return PgLOG.SUCCESS
445
+ elif re.match(r'^(08|57)', pgcode):
446
+ qelog(dberror, 0, "Retry Connecting", ary, pgcnt, PgLOG.LOGWRN)
447
+ pgconnect(1, pgcnt + 1)
448
+ return (PgLOG.FAILURE if not pgdb else PgLOG.SUCCESS)
449
+ elif re.match(r'^55', pgcode): # try to lock again
450
+ qelog(dberror, 10, "Retry Locking", ary, pgcnt, PgLOG.LOGWRN)
451
+ return PgLOG.SUCCESS
452
+ elif pgcode == '25P02': # try to add table
453
+ qelog(dberror, 0, "Rollback transaction", ary, pgcnt, PgLOG.LOGWRN)
454
+ pgdb.rollback()
455
+ return PgLOG.SUCCESS
456
+ elif pgcode == '42P01' and logact&PgLOG.ADDTBL: # try to add table
457
+ qelog(dberror, 0, "Retry after adding a table", ary, pgcnt, PgLOG.LOGWRN)
458
+ try_add_table(dberror, logact)
459
+ return PgLOG.SUCCESS
460
+
461
+ if logact&PgLOG.DOLOCK and pgcode and re.match(r'^55\w\w\w$', pgcode):
462
+ logact &= ~PgLOG.EXITLG # no exit for lock error
463
+ return qelog(dberror, 0, sqlstr, ary, pgcnt, logact)
464
+
465
+ #
466
+ # return hash reference to postgresql batch mode command and output file name
467
+ #
468
+ def pgbatch(sqlfile, foreground = 0):
469
+
470
+ # if(PGDBI['VWHOST'] and PGDBI['VWHOME'] and
471
+ # PGDBI['DBSHOST'] == PGDBI['VWSHOST'] and PGDBI['SCNAME'] == PGDBI['VWNAME']):
472
+ # slave = "/{}/{}.slave".format(PGDBI['VWHOME'], PGDBI['VWHOST'])
473
+ # if not op.exists(slave): default_scname()
474
+
475
+ dbhost = 'localhost' if PGDBI['DBSHOST'] == PgLOG.PGLOG['HOSTNAME'] else PGDBI['DBHOST']
476
+ options = "-h {} -p {}".format(dbhost, PGDBI['DBPORT'])
477
+ os.environ['PGPASSWORD'] = PGDBI['PWNAME']
478
+ options += " -U {} {}".format(PGDBI['LNNAME'], PGDBI['DBNAME'])
479
+
480
+ if not sqlfile: return options
481
+
482
+ if foreground:
483
+ batch = "psql {} < {} |".format(options, sqlfile)
484
+ else:
485
+ batch['out'] = sqlfile
486
+ if re.search(r'\.sql$', batch['out']):
487
+ batch['out'] = re.sub(r'\.sql$', '.out', batch['out'])
488
+ else:
489
+ batch['out'] += ".out"
490
+
491
+ batch['cmd'] = "psql {} < {} > {} 2>&1".format(options, sqlfile , batch['out'])
492
+
493
+ return batch
494
+
495
+ #
496
+ # start a connection to dssdb database and return a DBI object; None if error
497
+ # force connect if connect > 0
498
+ #
499
+ def pgconnect(reconnect = 0, pgcnt = 0, autocommit = True):
500
+
501
+ global pgdb
502
+
503
+ if pgdb:
504
+ if reconnect and not pgdb.closed: return pgdb # no need reconnect
505
+ elif reconnect:
506
+ reconnect = 0 # initial connection
507
+
508
+ # if PGDBI['VWHOST'] and PGDBI['VWHOME'] and PGDBI['DBSHOST'] == PGDBI['VWSHOST'] and PGDBI['SCNAME'] == PGDBI['VWNAME']:
509
+ # slave = "/{}/{}.slave".format(PGDBI['VWHOME'], PGDBI['VWHOST'])
510
+ # if not op.exists(slave): default_scname()
511
+
512
+ while True:
513
+ config = {'database' : PGDBI['DBNAME'],
514
+ 'user' : PGDBI['LNNAME'],
515
+ 'password' : PGDBI['PWNAME']}
516
+ if PGDBI['DBSHOST'] == PgLOG.PGLOG['HOSTNAME']:
517
+ config['host'] = 'localhost'
518
+ else:
519
+ config['host'] = PGDBI['DBHOST'] if PGDBI['DBHOST'] else PGDBI['CDHOST']
520
+ if not PGDBI['DBPORT']: PGDBI['DBPORT'] = get_dbport(PGDBI['DBNAME'])
521
+ if PGDBI['DBPORT']: config['port'] = PGDBI['DBPORT']
522
+
523
+ sqlstr = "psycopg2.connect(**{})".format(config)
524
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, sqlstr)
525
+ try:
526
+ PgLOG.PGLOG['PGDBBUF'] = pgdb = PgSQL.connect(**config)
527
+ if reconnect: PgLOG.pglog("{} Reconnected at {}".format(sqlstr, PgLOG.current_datetime()), PgLOG.MSGLOG|PgLOG.FRCLOG)
528
+ if autocommit: pgdb.autocommit = autocommit
529
+ return pgdb
530
+ except PgSQL.Error as pgerr:
531
+ if not check_dberror(pgerr, pgcnt, sqlstr, None, PGDBI['EXITLG']): return PgLOG.FAILURE
532
+ pgcnt += 1
533
+
534
+ #
535
+ # return a PostgreSQL cursor upon success
536
+ #
537
+ def pgcursor():
538
+
539
+ global pgdb
540
+ pgcur = None
541
+
542
+ if not pgdb:
543
+ pgconnect()
544
+ if not pgdb: return PgLOG.FAILURE
545
+
546
+ pgcnt = 0
547
+ while True:
548
+ try:
549
+ pgcur = pgdb.cursor()
550
+ spath = "SET search_path = '{}'".format(PGDBI['SCNAME'])
551
+ if PGDBI['SCPATH'] and PGDBI['SCPATH'] != PGDBI['SCNAME']:
552
+ spath += ", '{}'".format(PGDBI['SCPATH'])
553
+ pgcur.execute(spath)
554
+ except PgSQL.Error as pgerr:
555
+ if pgcnt == 0 and pgdb.closed:
556
+ pgconnect(1)
557
+ elif not check_dberror(pgerr, pgcnt, '', None, PGDBI['EXITLG']):
558
+ return PgLOG.FAILURE
559
+ else:
560
+ break
561
+ pgcnt += 1
562
+
563
+ return pgcur
564
+
565
+ #
566
+ # disconnect to dssdb database
567
+ #
568
+ def pgdisconnect(stopit = 1):
569
+
570
+ global pgdb
571
+ if pgdb:
572
+ if stopit: pgdb.close()
573
+ PgLOG.PGLOG['PGDBBUF'] = pgdb = None
574
+
575
+ #
576
+ # gather table field default information as hash array with field names as keys
577
+ # and default values as values
578
+ # the whole table information is cached to a hash array with table names as keys
579
+ #
580
+ def pgtable(tablename, logact = PGDBI['ERRLOG']):
581
+
582
+ if tablename in TABLES: return TABLES[tablename].copy() # cached already
583
+ intms = r'^(smallint||bigint|integer)$'
584
+ fields = "column_name col, data_type typ, is_nullable nil, column_default def"
585
+ condition = table_condition(tablename)
586
+ pgcnt = 0
587
+ while True:
588
+ pgrecs = pgmget('information_schema.columns', fields, condition, logact)
589
+ cnt = len(pgrecs['col']) if pgrecs else 0
590
+ if cnt: break
591
+ if pgcnt == 0 and logact&PgLOG.ADDTBL:
592
+ add_new_table(tablename, logact = logact)
593
+ else:
594
+ return PgLOG.pglog(tablename + ": Table not exists", logact)
595
+ pgcnt += 1
596
+
597
+ pgdefs = {}
598
+ for i in range(cnt):
599
+ name = pgrecs['col'][i]
600
+ isint = re.match(intms, pgrecs['typ'][i])
601
+ dflt = pgrecs['def'][i]
602
+ if dflt != None:
603
+ if re.match(r'^nextval\(', dflt):
604
+ dflt = 0
605
+ else:
606
+ dflt = check_default_value(dflt, isint)
607
+ elif pgrecs['nil'][i] == 'YES':
608
+ dflt = None
609
+ elif isint:
610
+ dflt = 0
611
+ else:
612
+ dflt = ''
613
+ pgdefs[name] = dflt
614
+
615
+ TABLES[tablename] = pgdefs.copy()
616
+ return pgdefs
617
+
618
+ #
619
+ # get sequence field name for given table name
620
+ #
621
+ def pgsequence(tablename, logact = PGDBI['ERRLOG']):
622
+
623
+ if tablename in SEQUENCES: return SEQUENCES[tablename] # cached already
624
+ condition = table_condition(tablename) + " AND column_default LIKE 'nextval(%'"
625
+ pgrec = pgget('information_schema.columns', 'column_name', condition, logact)
626
+ seqname = pgrec['column_name'] if pgrec else None
627
+ SEQUENCES[tablename] = seqname
628
+
629
+ return seqname
630
+
631
+ #
632
+ # check default value for integer & string
633
+ #
634
+ def check_default_value(dflt, isint):
635
+
636
+ if isint:
637
+ ms = re.match(r"^'{0,1}(\d+)", dflt)
638
+ if ms: dflt = int(ms.group(1))
639
+ elif dflt[0] == "'":
640
+ ms = re.match(r"^(.+)::", dflt)
641
+ if ms: dflt = ms.group(1)
642
+ elif dflt != 'NULL':
643
+ dflt = "'{}'".format(dflt)
644
+ return dflt
645
+
646
+ #
647
+ # local fucntion: insert prepare pgadd()/pgmadd() for given table and field names
648
+ # according to options of multiple place holds and returning sequence id
649
+ #
650
+ def prepare_insert(tablename, fields, multi = True, getid = None):
651
+
652
+ strfld = pgnames(fields, '.', ',')
653
+ if multi:
654
+ strplc = "(" + ','.join(['%s']*len(fields)) + ")"
655
+ else:
656
+ strplc = '%s'
657
+ sqlstr = "INSERT INTO {} ({}) VALUES {}".format(tablename, strfld, strplc)
658
+ if getid: sqlstr += " RETURNING " + getid
659
+
660
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, sqlstr)
661
+
662
+ return sqlstr
663
+
664
+ #
665
+ # local fucntion: prepare default value for single record
666
+ #
667
+ def prepare_default(tablename, record, logact = 0):
668
+
669
+ table = pgtable(tablename, logact)
670
+
671
+ for fld in record:
672
+ val = record[fld]
673
+ if val is None:
674
+ vlen = 0
675
+ elif isinstance(val, str):
676
+ vlen = len(val)
677
+ else:
678
+ vlen = 1
679
+ if vlen == 0: record[fld] = table[fld]
680
+
681
+ #
682
+ # local fucntion: prepare default value for multiple records
683
+ #
684
+ def prepare_defaults(tablename, records, logact = 0):
685
+
686
+ table = pgtable(tablename, logact)
687
+
688
+ for fld in records:
689
+ vals = records[fld]
690
+ vcnt = len(vals)
691
+ for i in range(vcnt):
692
+ if vals[i] is None:
693
+ vlen = 0
694
+ elif isinstance(vals[i], str):
695
+ vlen = len(vals[i])
696
+ else:
697
+ vlen = 1
698
+ if vlen == 0: records[fld][i] = table[fld]
699
+
700
+ #
701
+ # insert one record into tablename
702
+ # tablename: add record for one table name each call
703
+ # record: hash reference with keys as field names and hash values as field values
704
+ # return PgLOG.SUCCESS or PgLOG.FAILURE
705
+ #
706
+ def pgadd(tablename, record, logact = PGDBI['ERRLOG'], getid = None):
707
+
708
+ global curtran
709
+ if not record: return PgLOG.pglog("Nothing adds to " + tablename, logact)
710
+ if logact&PgLOG.DODFLT: prepare_default(tablename, record, logact)
711
+ if logact&PgLOG.AUTOID and not getid: getid = pgsequence(tablename, logact)
712
+ sqlstr = prepare_insert(tablename, list(record), True, getid)
713
+ values = tuple(record.values())
714
+
715
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "Insert: " + str(values))
716
+
717
+ ret = acnt = pgcnt = 0
718
+ while True:
719
+ pgcur = pgcursor()
720
+ if not pgcur: return PgLOG.FAILURE
721
+ try:
722
+ pgcur.execute(sqlstr, values)
723
+ acnt = 1
724
+ if getid:
725
+ ret = pgcur.fetchone()[0]
726
+ else:
727
+ ret = PgLOG.SUCCESS
728
+ pgcur.close()
729
+ except PgSQL.Error as pgerr:
730
+ if not check_dberror(pgerr, pgcnt, sqlstr, values, logact): return PgLOG.FAILURE
731
+ else:
732
+ break
733
+ pgcnt += 1
734
+
735
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pgadd: 1 record added to " + tablename + ", return " + str(ret))
736
+ if(logact&PgLOG.ENDLCK):
737
+ endtran()
738
+ elif curtran:
739
+ curtran += acnt
740
+ if curtran > PGDBI['MTRANS']: starttran()
741
+
742
+ return ret
743
+
744
+ #
745
+ # insert multiple records into tablename
746
+ # tablename: add records for one table name each call
747
+ # records: dict with field names as keys and each value is a list of field values
748
+ # return PgLOG.SUCCESS or PgLOG.FAILURE
749
+ #
750
+ def pgmadd(tablename, records, logact = PGDBI['ERRLOG'], getid = None):
751
+
752
+ global curtran
753
+ if not records: return PgLOG.pglog("Nothing to insert to table " + tablename, logact)
754
+ if logact&PgLOG.DODFLT: prepare_defaults(tablename, records, logact)
755
+ if logact&PgLOG.AUTOID and not getid: getid = pgsequence(tablename, logact)
756
+ multi = True if getid else False
757
+ sqlstr = prepare_insert(tablename, list(records), multi, getid)
758
+
759
+ v = records.values()
760
+ values = list(zip(*v))
761
+ cntrow = len(values)
762
+ ids = [] if getid else None
763
+
764
+ if PgLOG.PGLOG['DBGLEVEL']:
765
+ for row in values: PgLOG.pgdbg(1000, "Insert: " + str(row))
766
+
767
+ count = pgcnt = 0
768
+ while True:
769
+ pgcur = pgcursor()
770
+ if not pgcur: return PgLOG.FAILURE
771
+
772
+ if getid:
773
+ while count < cntrow:
774
+ record = values[count]
775
+ try:
776
+ pgcur.execute(sqlstr, record)
777
+ ids.append(pgcur.fetchone()[0])
778
+ count += 1
779
+ except PgSQL.Error as pgerr:
780
+ if not check_dberror(pgerr, pgcnt, sqlstr, record, logact): return PgLOG.FAILURE
781
+ break
782
+ else:
783
+ try:
784
+ execute_values(pgcur, sqlstr, values, page_size=PGDBI['PGSIZE'])
785
+ count = cntrow
786
+ except PgSQL.Error as pgerr:
787
+ if not check_dberror(pgerr, pgcnt, sqlstr, values[0], logact): return PgLOG.FAILURE
788
+ if count >= cntrow: break
789
+ pgcnt += 1
790
+
791
+ pgcur.close()
792
+ if(PgLOG.PGLOG['DBGLEVEL']): PgLOG.pgdbg(1000, "pgmadd: {} of {} record(s) added to {}".format(count, cntrow, tablename))
793
+
794
+ if(logact&PgLOG.ENDLCK):
795
+ endtran()
796
+ elif curtran:
797
+ curtran += count
798
+ if curtran > PGDBI['MTRANS']: starttran()
799
+
800
+ return (ids if ids else count)
801
+
802
+ #
803
+ # local function: select prepare for pgget() and pgmget()
804
+ #
805
+ def prepare_select(tablenames, fields = None, condition = None, cndflds = None, logact = 0):
806
+
807
+ sqlstr = ''
808
+ if tablenames:
809
+ if fields:
810
+ sqlstr = "SELECT " + fields
811
+ else:
812
+ sqlstr = "SELECT count(*) cntrec"
813
+
814
+ sqlstr += " FROM " + tablenames
815
+ if condition:
816
+ if re.match(r'^\s*(ORDER|GROUP|HAVING|OFFSET|LIMIT)\s', condition, re.I):
817
+ sqlstr += " " + condition # no where clause, append directly
818
+ else:
819
+ sqlstr += " WHERE " + condition
820
+ elif cndflds:
821
+ sep = 'WHERE'
822
+ for fld in cndflds:
823
+ sqlstr += " {} {}=%s".format(sep, fld)
824
+ sep = 'AND'
825
+ if logact&PgLOG.DOLOCK:
826
+ starttran()
827
+ sqlstr += " FOR UPDATE"
828
+ elif fields:
829
+ sqlstr = "SELECT " + fields
830
+ elif condition:
831
+ sqlstr = condition
832
+
833
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, sqlstr)
834
+
835
+ return sqlstr
836
+
837
+ #
838
+ # tablenames: comma deliminated string of one or more tables and more than one table for joining,
839
+ # fields: comma deliminated string of one or more field names,
840
+ # condition: querry conditions for where clause
841
+ # return a dict reference with keys as field names upon success
842
+ #
843
+ def pgget(tablenames, fields, condition = None, logact = 0):
844
+
845
+ if not logact: logact = PGDBI['ERRLOG']
846
+ if fields and condition and not re.search(r'limit 1$', condition, re.I): condition += " LIMIT 1"
847
+ sqlstr = prepare_select(tablenames, fields, condition, None, logact)
848
+ if fields and not re.search(r'(^|\s)limit 1($|\s)', sqlstr, re.I): sqlstr += " LIMIT 1"
849
+ ucname = True if logact&PgLOG.UCNAME else False
850
+ pgcnt = 0
851
+ record = {}
852
+ while True:
853
+ pgcur = pgcursor()
854
+ if not pgcur: return PgLOG.FAILURE
855
+ try:
856
+ pgcur.execute(sqlstr)
857
+ vals = pgcur.fetchone()
858
+ if vals:
859
+ colcnt = len(pgcur.description)
860
+ for i in range(colcnt):
861
+ col = pgcur.description[i]
862
+ colname = col[0].upper() if ucname else col[0]
863
+ val = vals[i]
864
+ if col[1] == CHCODE and val and val[-1] == ' ': val = val.rstrip()
865
+ record[colname] = val
866
+ pgcur.close()
867
+ except PgSQL.Error as pgerr:
868
+ if not check_dberror(pgerr, pgcnt, sqlstr, None, logact): return PgLOG.FAILURE
869
+ else:
870
+ break
871
+ pgcnt += 1
872
+
873
+ if record and tablenames and not fields:
874
+ if PgLOG.PGLOG['DBGLEVEL']:
875
+ PgLOG.pgdbg(1000, "pgget: {} record(s) found from {}".format(record['cntrec'], tablenames))
876
+ return record['cntrec']
877
+ elif PgLOG.PGLOG['DBGLEVEL']:
878
+ cnt = 1 if record else 0
879
+ PgLOG.pgdbg(1000, "pgget: {} record retrieved from {}".format(cnt, tablenames))
880
+
881
+ return record
882
+
883
+ #
884
+ # tablenames: comma deliminated string of one or more tables and more than one table for joining,
885
+ # fields: comma deliminated string of one or more field names,
886
+ # condition: querry conditions for where clause
887
+ # return a dict reference with keys as field names upon success, values for each field name
888
+ # are in a list. All lists are the same length with missing values set to None
889
+ #
890
+ def pgmget(tablenames, fields, condition = None, logact = PGDBI['ERRLOG']):
891
+
892
+ sqlstr = prepare_select(tablenames, fields, condition, None, logact)
893
+ ucname = True if logact&PgLOG.UCNAME else False
894
+ count = pgcnt = 0
895
+ records = {}
896
+ while True:
897
+ pgcur = pgcursor()
898
+ if not pgcur: return PgLOG.FAILURE
899
+ try:
900
+ pgcur.execute(sqlstr)
901
+ rowvals = pgcur.fetchall()
902
+ if rowvals:
903
+ colcnt = len(pgcur.description)
904
+ count = len(rowvals)
905
+ colvals = list(zip(*rowvals))
906
+ for i in range(colcnt):
907
+ col = pgcur.description[i]
908
+ colname = col[0].upper() if ucname else col[0]
909
+ vals = list(colvals[i])
910
+ if col[1] == CHCODE:
911
+ for j in range(count):
912
+ if vals[j] and vals[j][-1] == ' ': vals[j] = vals[j].rstrip()
913
+ records[colname] = vals
914
+ pgcur.close()
915
+ except PgSQL.Error as pgerr:
916
+ if not check_dberror(pgerr, pgcnt, sqlstr, None, logact): return PgLOG.FAILURE
917
+ else:
918
+ break
919
+ pgcnt += 1
920
+
921
+ if PgLOG.PGLOG['DBGLEVEL']:
922
+ PgLOG.pgdbg(1000, "pgmget: {} record(s) retrieved from {}".format(count, tablenames))
923
+
924
+ return records
925
+
926
+ #
927
+ # tablenames: comma deliminated string of one or more tables
928
+ # fields: comma deliminated string of one or more field names,
929
+ # cnddict: condition dict with field names : values
930
+ # return a dict(field names : values) upon success
931
+ #
932
+ # retrieve one records from tablenames condition dict
933
+ #
934
+ def pghget(tablenames, fields, cnddict, logact = PGDBI['ERRLOG']):
935
+
936
+ if not tablenames: return PgLOG.pglog("Miss Table name to query", logact)
937
+ if not fields: return PgLOG.pglog("Nothing to query " + tablenames, logact)
938
+ if not cnddict: return PgLOG.pglog("Miss condition dict values to query " + tablenames, logact)
939
+ sqlstr = prepare_select(tablenames, fields, None, list(cnddict), logact)
940
+ if fields and not re.search(r'limit 1$', sqlstr, re.I): sqlstr += " LIMIT 1"
941
+ ucname = True if logact&PgLOG.UCNAME else False
942
+
943
+ values = tuple(cnddict.values())
944
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "Query from {} for {}".format(tablenames, values))
945
+
946
+ pgcnt = 0
947
+ record = {}
948
+ while True:
949
+ pgcur = pgcursor()
950
+ if not pgcur: return PgLOG.FAILURE
951
+ try:
952
+ pgcur.execute(sqlstr, values)
953
+ vals = pgcur.fetchone()
954
+ if vals:
955
+ colcnt = len(pgcur.description)
956
+ for i in range(colcnt):
957
+ col = pgcur.description[i]
958
+ colname = col[0].upper() if ucname else col[0]
959
+ val = vals[i]
960
+ if col[1] == CHCODE and val and val[-1] == ' ': val = val.rstrip()
961
+ record[colname] = val
962
+ pgcur.close()
963
+ except PgSQL.Error as pgerr:
964
+ if not check_dberror(pgerr, pgcnt, sqlstr, values, logact): return PgLOG.FAILURE
965
+ else:
966
+ break
967
+ pgcnt += 1
968
+
969
+ if record and tablenames and not fields:
970
+ if PgLOG.PGLOG['DBGLEVEL']:
971
+ PgLOG.pgdbg(1000, "pghget: {} record(s) found from {}".format(record['cntrec'], tablenames))
972
+ return record['cntrec']
973
+ elif PgLOG.PGLOG['DBGLEVEL']:
974
+ cnt = 1 if record else 0
975
+ PgLOG.pgdbg(1000, "pghget: {} record retrieved from {}".format(cnt, tablenames))
976
+
977
+ return record
978
+
979
+ #
980
+ # tablenames: comma deliminated string of one or more tables
981
+ # fields: comma deliminated string of one or more field names,
982
+ # cnddicts: condition dict with field names : value lists
983
+ # return a dict(field names : value lists) upon success
984
+ #
985
+ # retrieve multiple records from tablenames for condition dict
986
+ #
987
+ def pgmhget(tablenames, fields, cnddicts, logact = PGDBI['ERRLOG']):
988
+
989
+ if not tablenames: return PgLOG.pglog("Miss Table name to query", logact)
990
+ if not fields: return PgLOG.pglog("Nothing to query " + tablenames, logact)
991
+ if not cnddicts: return PgLOG.pglog("Miss condition dict values to query " + tablenames, logact)
992
+ sqlstr = prepare_select(tablenames, fields, None, list(cnddicts), logact)
993
+ ucname = True if logact&PgLOG.UCNAME else False
994
+
995
+ v = cnddicts.values()
996
+ values = list(zip(*v))
997
+ cndcnt = len(values)
998
+
999
+ if PgLOG.PGLOG['DBGLEVEL']:
1000
+ for row in values:
1001
+ PgLOG.pgdbg(1000, "Query from {} for {}".format(tablenames, row))
1002
+
1003
+ colcnt = ccnt = count = pgcnt = 0
1004
+ cols = []
1005
+ chrs = []
1006
+ records = {}
1007
+ while True:
1008
+ pgcur = pgcursor()
1009
+ if not pgcur: return PgLOG.FAILURE
1010
+ while ccnt < cndcnt:
1011
+ cndvals = values[ccnt]
1012
+ try:
1013
+ pgcur.execute(sqlstr, cndvals)
1014
+ ccnt += 1
1015
+ rowvals = pgcur.fetchall()
1016
+ if rowvals:
1017
+ if colcnt == 0:
1018
+ for col in pgcur.description:
1019
+ colname = col[0].upper() if ucname else col[0]
1020
+ if col[1] == CHCODE: chrs.append(colname)
1021
+ cols.append(colname)
1022
+ records[colname] = []
1023
+ colcnt = len(cols)
1024
+ rcnt = len(rowvals)
1025
+ count += rcnt
1026
+ colvals = list(zip(*rowvals))
1027
+ for i in range(colcnt):
1028
+ vals = list(colvals[i])
1029
+ colname = cols[i]
1030
+ if chrs and colname in chrs:
1031
+ for j in range(rcnt):
1032
+ if vals[j] and vals[j][-1] == ' ': vals[j] = vals[j].rstrip()
1033
+ records[colname].extend(vals)
1034
+ except PgSQL.Error as pgerr:
1035
+ if not check_dberror(pgerr, pgcnt, sqlstr, cndvals, logact): return PgLOG.FAILURE
1036
+ break
1037
+ if ccnt >= cndcnt: break
1038
+ pgcnt += 1
1039
+ pgcur.close()
1040
+
1041
+ if PgLOG.PGLOG['DBGLEVEL']:
1042
+ PgLOG.pgdbg(1000, "pgmhget: {} record(s) retrieved from {}".format(count, tablenames))
1043
+
1044
+ return records
1045
+
1046
+ #
1047
+ # local fucntion: update prepare for pgupdt, pghupdt and pgmupdt
1048
+ #
1049
+ def prepare_update(tablename, fields, condition = None, cndflds = None):
1050
+
1051
+ strset = []
1052
+ # build set string
1053
+ for fld in fields:
1054
+ strset.append("{}=%s".format(pgname(fld, '.')))
1055
+ strflds = ",".join(strset)
1056
+
1057
+ # build condition string
1058
+ if not condition:
1059
+ cndset = []
1060
+ for fld in cndflds:
1061
+ cndset.append("{}=%s".format(pgname(fld, '.')))
1062
+ condition = " AND ".join(cndset)
1063
+
1064
+ sqlstr = "UPDATE {} SET {} WHERE {}".format(tablename, strflds, condition)
1065
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, sqlstr)
1066
+
1067
+ return sqlstr
1068
+
1069
+ #
1070
+ # update one or multiple rows in tablename
1071
+ # tablename: update for one table name each call
1072
+ # record: dict with field names : values
1073
+ # condition: update conditions for where clause)
1074
+ # return number of rows undated upon success
1075
+ #
1076
+ def pgupdt(tablename, record, condition, logact = PGDBI['ERRLOG']):
1077
+
1078
+ global curtran
1079
+ if not record: PgLOG.pglog("Nothing updates to " + tablename, logact)
1080
+ if not condition or isinstance(condition, int): PgLOG.pglog("Miss condition to update " + tablename, logact)
1081
+ sqlstr = prepare_update(tablename, list(record), condition)
1082
+ if logact&PgLOG.DODFLT: prepare_default(tablename, record, logact)
1083
+
1084
+ values = tuple(record.values())
1085
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "Update {} for {}".format(tablename, values))
1086
+
1087
+ ucnt = pgcnt = 0
1088
+ while True:
1089
+ pgcur = pgcursor()
1090
+ if not pgcur: return PgLOG.FAILURE
1091
+ try:
1092
+ pgcur.execute(sqlstr, values)
1093
+ ucnt = pgcur.rowcount
1094
+ pgcur.close()
1095
+ except PgSQL.Error as pgerr:
1096
+ if not check_dberror(pgerr, pgcnt, sqlstr, values, logact): return PgLOG.FAILURE
1097
+ else:
1098
+ break
1099
+ pgcnt += 1
1100
+
1101
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pgupdt: {} record(s) updated to {}".format(ucnt, tablename))
1102
+ if(logact&PgLOG.ENDLCK):
1103
+ endtran()
1104
+ elif curtran:
1105
+ curtran += ucnt
1106
+ if curtran > PGDBI['MTRANS']: starttran()
1107
+
1108
+ return ucnt
1109
+
1110
+ #
1111
+ # update one or multiple records in tablename
1112
+ # tablename: update for one table name each call
1113
+ # record: update values, dict with field names : values
1114
+ # cnddict: condition dict with field names : values
1115
+ # return number of records updated upon success
1116
+ #
1117
+ def pghupdt(tablename, record, cnddict, logact = PGDBI['ERRLOG']):
1118
+
1119
+ global curtran
1120
+ if not record: PgLOG.pglog("Nothing updates to " + tablename, logact)
1121
+ if not cnddict or isinstance(cnddict, int): PgLOG.pglog("Miss condition to update to " + tablename, logact)
1122
+ if logact&PgLOG.DODFLT: prepare_defaults(tablename, record, logact)
1123
+ sqlstr = prepare_update(tablename, list(record), None, list(cnddict))
1124
+
1125
+ values = tuple(record.values()) + tuple(cnddict.values())
1126
+
1127
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "Update {} for {}".format(tablename, values))
1128
+
1129
+ ucnt = count = pgcnt = 0
1130
+ while True:
1131
+ pgcur = pgcursor()
1132
+ if not pgcur: return PgLOG.FAILURE
1133
+ try:
1134
+ pgcur.execute(sqlstr, values)
1135
+ count += 1
1136
+ ucnt = pgcur.rowcount
1137
+ pgcur.close()
1138
+ except PgSQL.Error as pgerr:
1139
+ if not check_dberror(pgerr, pgcnt, sqlstr, values, logact): return PgLOG.FAILURE
1140
+ else:
1141
+ break
1142
+ pgcnt += 1
1143
+
1144
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pghupdt: {}/{} record(s) updated to {}".format(ucnt, tablename))
1145
+ if(logact&PgLOG.ENDLCK):
1146
+ endtran()
1147
+ elif curtran:
1148
+ curtran += ucnt
1149
+ if curtran > PGDBI['MTRANS']: starttran()
1150
+
1151
+ return ucnt
1152
+
1153
+ #
1154
+ # update multiple records in tablename
1155
+ # tablename: update for one table name each call
1156
+ # records: update values, dict with field names : value lists
1157
+ # cnddicts: condition dict with field names : value lists
1158
+ # return number of records updated upon success
1159
+ #
1160
+ def pgmupdt(tablename, records, cnddicts, logact = PGDBI['ERRLOG']):
1161
+
1162
+ global curtran
1163
+ if not records: PgLOG.pglog("Nothing updates to " + tablename, logact)
1164
+ if not cnddicts or isinstance(cnddicts, int): PgLOG.pglog("Miss condition to update to " + tablename, logact)
1165
+ if logact&PgLOG.DODFLT: prepare_defaults(tablename, records, logact)
1166
+ sqlstr = prepare_update(tablename, list(records), None, list(cnddicts))
1167
+
1168
+ fldvals = tuple(records.values())
1169
+ cntrow = len(fldvals[0])
1170
+ cndvals = tuple(cnddicts.values())
1171
+ cntcnd = len(cndvals[0])
1172
+ if cntcnd != cntrow: return PgLOG.pglog("Field/Condition value counts Miss match {}/{} to update {}".format(cntrow, cntcnd, tablename), logact)
1173
+ v = fldvals + cndvals
1174
+ values = list(zip(*v))
1175
+
1176
+ if PgLOG.PGLOG['DBGLEVEL']:
1177
+ for row in values: PgLOG.pgdbg(1000, "Update {} for {}".format(tablename, row))
1178
+
1179
+ ucnt = pgcnt = 0
1180
+ while True:
1181
+ pgcur = pgcursor()
1182
+ if not pgcur: return PgLOG.FAILURE
1183
+ try:
1184
+ execute_batch(pgcur, sqlstr, values, page_size=PGDBI['PGSIZE'])
1185
+ ucnt = cntrow
1186
+ except PgSQL.Error as pgerr:
1187
+ if not check_dberror(pgerr, pgcnt, sqlstr, values[0], logact): return PgLOG.FAILURE
1188
+ else:
1189
+ break
1190
+ pgcnt += 1
1191
+
1192
+ pgcur.close()
1193
+
1194
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pgmupdt: {} record(s) updated to {}".format(ucnt, tablename))
1195
+ if(logact&PgLOG.ENDLCK):
1196
+ endtran()
1197
+ elif curtran:
1198
+ curtran += ucnt
1199
+ if curtran > PGDBI['MTRANS']: starttran()
1200
+
1201
+ return ucnt
1202
+
1203
+ #
1204
+ # local fucntion: delete prepare for pgdel, pghdel and del
1205
+ #
1206
+ def prepare_delete(tablename, condition = None, cndflds = None):
1207
+
1208
+ # build condition string
1209
+ if not condition:
1210
+ cndset = []
1211
+ for fld in cndflds:
1212
+ cndset.append("{}=%s".format(fld))
1213
+ condition = " AND ".join(cndset)
1214
+
1215
+ sqlstr = "DELETE FROM {} WHERE {}".format(tablename, condition)
1216
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, sqlstr)
1217
+
1218
+ return sqlstr
1219
+
1220
+ #
1221
+ # delete one or mutiple records in tablename according condition
1222
+ # tablename: delete for one table name each call
1223
+ # condition: delete conditions for where clause
1224
+ # return number of records deleted upon success
1225
+ #
1226
+ def pgdel(tablename, condition, logact = PGDBI['ERRLOG']):
1227
+
1228
+ global curtran
1229
+ if not condition or isinstance(condition, int): PgLOG.pglog("Miss condition to delete from " + tablename, logact)
1230
+ sqlstr = prepare_delete(tablename, condition)
1231
+
1232
+ dcnt = pgcnt = 0
1233
+ while True:
1234
+ pgcur = pgcursor()
1235
+ if not pgcur: return PgLOG.FAILURE
1236
+ try:
1237
+ pgcur.execute(sqlstr)
1238
+ dcnt = pgcur.rowcount
1239
+ pgcur.close()
1240
+ except PgSQL.Error as pgerr:
1241
+ if not check_dberror(pgerr, pgcnt, sqlstr, None, logact): return PgLOG.FAILURE
1242
+ else:
1243
+ break
1244
+ pgcnt += 1
1245
+
1246
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pgdel: {} record(s) deleted from {}".format(dcnt, tablename))
1247
+ if logact&PgLOG.ENDLCK:
1248
+ endtran()
1249
+ elif curtran:
1250
+ curtran += dcnt
1251
+ if curtran > PGDBI['MTRANS']: starttran()
1252
+
1253
+ return dcnt
1254
+
1255
+ #
1256
+ # delete one or mutiple records in tablename according condition
1257
+ # tablename: delete for one table name each call
1258
+ # cndict: delete condition dict for names : values
1259
+ # return number of records deleted upon success
1260
+ #
1261
+ def pghdel(tablename, cnddict, logact = PGDBI['ERRLOG']):
1262
+
1263
+ global curtran
1264
+ if not cnddict or isinstance(cnddict, int): PgLOG.pglog("Miss condition dict to delete from " + tablename, logact)
1265
+ sqlstr = prepare_delete(tablename, None, list(cnddict))
1266
+
1267
+ values = tuple(cnddict.values())
1268
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "Delete from {} for {}".format(tablename, values))
1269
+
1270
+ dcnt = pgcnt = 0
1271
+ while True:
1272
+ pgcur = pgcursor()
1273
+ if not pgcur: return PgLOG.FAILURE
1274
+ try:
1275
+ pgcur.execute(sqlstr, values)
1276
+ dcnt = pgcur.rowcount
1277
+ pgcur.close()
1278
+ except PgSQL.Error as pgerr:
1279
+ if not check_dberror(pgerr, pgcnt, sqlstr, values, logact): return PgLOG.FAILURE
1280
+ else:
1281
+ break
1282
+ pgcnt += 1
1283
+
1284
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pghdel: {} record(s) deleted from {}".format(dcnt, tablename))
1285
+ if logact&PgLOG.ENDLCK:
1286
+ endtran()
1287
+ elif curtran:
1288
+ curtran += dcnt
1289
+ if curtran > PGDBI['MTRANS']: starttran()
1290
+
1291
+ return dcnt
1292
+
1293
+ #
1294
+ # delete mutiple records in tablename according condition
1295
+ # tablename: delete for one table name each call
1296
+ # cndicts: delete condition dict for names : value lists
1297
+ # return number of records deleted upon success
1298
+ #
1299
+ def pgmdel(tablename, cnddicts, logact = PGDBI['ERRLOG']):
1300
+
1301
+ global curtran
1302
+ if not cnddicts or isinstance(cnddicts, int): PgLOG.pglog("Miss condition dict to delete from " + tablename, logact)
1303
+ sqlstr = prepare_delete(tablename, None, list(cnddicts))
1304
+
1305
+ v = cnddicts.values()
1306
+ values = list(zip(*v))
1307
+ if PgLOG.PGLOG['DBGLEVEL']:
1308
+ for row in values:
1309
+ PgLOG.pgdbg(1000, "Delete from {} for {}".format(tablename, row))
1310
+
1311
+ dcnt = pgcnt = 0
1312
+ while True:
1313
+ pgcur = pgcursor()
1314
+ if not pgcur: return PgLOG.FAILURE
1315
+ try:
1316
+ execute_batch(pgcur, sqlstr, values, page_size=PGDBI['PGSIZE'])
1317
+ dcnt = len(values)
1318
+ except PgSQL.Error as pgerr:
1319
+ if not check_dberror(pgerr, pgcnt, sqlstr, values[0], logact): return PgLOG.FAILURE
1320
+ else:
1321
+ break
1322
+ pgcnt += 1
1323
+
1324
+ pgcur.close()
1325
+
1326
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pgmdel: {} record(s) deleted from {}".format(dcnt, tablename))
1327
+ if logact&PgLOG.ENDLCK:
1328
+ endtran()
1329
+ elif curtran:
1330
+ curtran += dcnt
1331
+ if curtran > PGDBI['MTRANS']: starttran()
1332
+
1333
+ return dcnt
1334
+
1335
+ #
1336
+ # sqlstr: a complete sql string
1337
+ # return number of record affected upon success
1338
+ #
1339
+ def pgexec(sqlstr, logact = PGDBI['ERRLOG']):
1340
+
1341
+ global curtran
1342
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(100, sqlstr)
1343
+
1344
+ ret = pgcnt = 0
1345
+ while True:
1346
+ pgcur = pgcursor()
1347
+ if not pgcur: return PgLOG.FAILURE
1348
+ try:
1349
+ pgcur.execute(sqlstr)
1350
+ ret = pgcur.rowcount
1351
+ pgcur.close()
1352
+ except PgSQL.Error as pgerr:
1353
+ if not check_dberror(pgerr, pgcnt, sqlstr, None, logact): return PgLOG.FAILURE
1354
+ else:
1355
+ break
1356
+ pgcnt += 1
1357
+
1358
+ if PgLOG.PGLOG['DBGLEVEL']: PgLOG.pgdbg(1000, "pgexec: {} record(s) affected for {}".format(ret, sqlstr))
1359
+ if logact&PgLOG.ENDLCK:
1360
+ endtran()
1361
+ elif curtran:
1362
+ curtran += ret
1363
+ if curtran > PGDBI['MTRANS']: starttran()
1364
+
1365
+ return ret
1366
+
1367
+ #
1368
+ # tablename: one table name to a temporary table
1369
+ # fromtable: table name data gathing from
1370
+ # fields: table name data gathing from
1371
+ # condition: querry conditions for where clause
1372
+ # return number of records created upon success
1373
+ #
1374
+ def pgtemp(tablename, fromtable, fields, condition = None, logact = 0):
1375
+
1376
+ sqlstr = "CREATE TEMPORARY TABLE {} SELECT {} FROM {}".format(tablename, fields, fromtable)
1377
+ if condition: sqlstr += " WHERE " + condition
1378
+
1379
+ return pgexec(sqlstr, logact)
1380
+
1381
+ #
1382
+ # get condition for given table name for accessing information_schema
1383
+ #
1384
+ def table_condition(tablename):
1385
+
1386
+ ms = re.match(r'(.+)\.(.+)', tablename)
1387
+ if ms:
1388
+ scname = ms.group(1)
1389
+ tbname = ms.group(2)
1390
+ else:
1391
+ scname = PGDBI['SCNAME']
1392
+ tbname = tablename
1393
+
1394
+ return "table_schema = '{}' AND table_name = '{}'".format(scname, tbname)
1395
+
1396
+ #
1397
+ # check if a given table name exists or not
1398
+ # tablename: one table name to check
1399
+ #
1400
+ def pgcheck(tablename, logact = 0):
1401
+
1402
+ condition = table_condition(tablename)
1403
+
1404
+ ret = pgget('information_schema.tables', None, condition, logact)
1405
+ return (PgLOG.SUCCESS if ret else PgLOG.FAILURE)
1406
+
1407
+ #
1408
+ # group of functions to check parent records and add an empty one if missed
1409
+ # return user.uid upon success, 0 otherwise
1410
+ #
1411
+ def check_user_uid(userno, date = None):
1412
+
1413
+ if not userno: return 0
1414
+ if type(userno) is str: userno = int(userno)
1415
+
1416
+ if date is None:
1417
+ datecond = "until_date IS NULL"
1418
+ date = 'today'
1419
+ else:
1420
+ datecond = "(start_date IS NULL OR start_date <= '{}') AND (until_date IS NULL OR until_date >= '{}')".format(date, date)
1421
+
1422
+ pgrec = pgget("dssdb.user", "uid", "userno = {} AND {}".format(userno, datecond), PGDBI['ERRLOG'])
1423
+ if pgrec: return pgrec['uid']
1424
+
1425
+ if userno not in NMISSES:
1426
+ PgLOG.pglog("{}: Scientist ID NOT on file for {}".format(userno, date), PgLOG.LGWNEM)
1427
+ NMISSES.append(userno)
1428
+
1429
+ # check again if a user is on file with different date range
1430
+ pgrec = pgget("dssdb.user", "uid", "userno = {}".format(userno), PGDBI['ERRLOG'])
1431
+ if pgrec: return pgrec['uid']
1432
+
1433
+ pgrec = ucar_user_info(userno)
1434
+ if not pgrec: pgrec = {'userno' : userno, 'stat_flag' : 'M'}
1435
+ uid = pgadd("dssdb.user", pgrec, (PGDBI['EXITLG']|PgLOG.AUTOID))
1436
+ if uid: PgLOG.pglog("{}: Scientist ID Added as user.uid = {}".format(userno, uid), PgLOG.LGWNEM)
1437
+
1438
+ return uid
1439
+
1440
+ #
1441
+ # return user.uid upon success, 0 otherwise
1442
+ #
1443
+ def get_user_uid(logname, date = None):
1444
+
1445
+ if not logname: return 0
1446
+ if not date:
1447
+ date = 'today'
1448
+ datecond = "until_date IS NULL"
1449
+ else:
1450
+ datecond = "(start_date IS NULL OR start_date <= '{}') AND (until_date IS NULL OR until_date >= '{}')".format(date, date)
1451
+
1452
+ pgrec = pgget("dssdb.user", "uid", "logname = '{}' AND {}".format(logname, datecond), PGDBI['ERRLOG'])
1453
+ if pgrec: return pgrec['uid']
1454
+
1455
+ if logname not in LMISSES:
1456
+ PgLOG.pglog("{}: UCAR Login Name NOT on file for {}".format(logname, date), PgLOG.LGWNEM)
1457
+ LMISSES.append(logname)
1458
+
1459
+ # check again if a user is on file with different date range
1460
+ pgrec = pgget("dssdb.user", "uid", "logname = '{}'".format(logname), PGDBI['ERRLOG'])
1461
+ if pgrec: return pgrec['uid']
1462
+
1463
+ pgrec = ucar_user_info(0, logname)
1464
+ if not pgrec: pgrec = {'logname' : logname, 'stat_flag' : 'M'}
1465
+ uid = pgadd("dssdb.user", pgrec, (PGDBI['EXITLG']|PgLOG.AUTOID))
1466
+ if uid: PgLOG.pglog("{}: UCAR Login Name Added as user.uid = {}".format(logname, uid), PgLOG.LGWNEM)
1467
+
1468
+ return uid
1469
+
1470
+ #
1471
+ # get ucar user info for given userno (scientist number) or logname (Ucar login)
1472
+ #
1473
+ def ucar_user_info(userno, logname = None):
1474
+
1475
+ MATCH = {
1476
+ 'upid' : "upid",
1477
+ 'uid' : "userno",
1478
+ 'username' : "logname",
1479
+ 'lastName' : "lstname",
1480
+ 'firstName' : "fstname",
1481
+ 'active' : "stat_flag",
1482
+ 'internalOrg' : "division",
1483
+ 'externalOrg' : "org_name",
1484
+ 'country' : "country",
1485
+ 'forwardEmail' : "email",
1486
+ 'email' : "ucaremail",
1487
+ 'phone' : "phoneno"
1488
+ }
1489
+
1490
+ buf = PgLOG.pgsystem("pgperson " + ("-uid {}".format(userno) if userno else "-username {}".format(logname)), PgLOG.LOGWRN, 20)
1491
+ if not buf: return None
1492
+
1493
+ pgrec = {}
1494
+ for line in buf.split('\n'):
1495
+ ms = re.match(r'^(.+)<=>(.*)$', line)
1496
+ if ms:
1497
+ (key, val) = ms.groups()
1498
+ if key in MATCH:
1499
+ if key == 'upid' and 'upid' in pgrec: break # get one record only
1500
+ pgrec[MATCH[key]] = val
1501
+
1502
+ if not pgrec: return None
1503
+
1504
+ if userno:
1505
+ pgrec['userno'] = userno
1506
+ elif pgrec['userno']:
1507
+ pgrec['userno'] = userno = int(pgrec['userno'])
1508
+ if pgrec['upid']: pgrec['upid'] = int(pgrec['upid'])
1509
+ if pgrec['stat_flag']: pgrec['stat_flag'] = 'A' if pgrec['stat_flag'] == '1' else 'C'
1510
+ if pgrec['email'] and re.search(r'(@|\.)ucar\.edu$', pgrec['email'], re.I):
1511
+ pgrec['email'] = pgrec['ucaremail']
1512
+ pgrec['org_name'] = 'NCAR'
1513
+ country = pgrec['country'] if 'country' in pgrec else None
1514
+ pgrec['country'] = set_country_code(pgrec['email'], country)
1515
+ if pgrec['division']:
1516
+ val = "NCAR"
1517
+ else:
1518
+ val = None
1519
+ pgrec['org_type'] = get_org_type(val, pgrec['email'])
1520
+
1521
+ buf = PgLOG.pgsystem("pgusername {}".format(pgrec['logname']), PgLOG.LOGWRN, 20)
1522
+ if not buf: return pgrec
1523
+
1524
+ for line in buf.split('\n'):
1525
+ ms = re.match(r'^(.+)<=>(.*)$', line)
1526
+ if ms:
1527
+ (key, val) = ms.groups()
1528
+ if key == 'startDate':
1529
+ m = re.match(r'^(\d+-\d+-\d+)\s', val)
1530
+ if m:
1531
+ pgrec['start_date'] = m.group(1)
1532
+ else:
1533
+ pgrec['start_date'] = val
1534
+
1535
+ if key == 'endDate':
1536
+ m = re.match(r'^(\d+-\d+-\d+)\s', val)
1537
+ if m:
1538
+ pgrec['until_date'] = m.group(1)
1539
+ else:
1540
+ pgrec['until_date'] = val
1541
+
1542
+ return pgrec
1543
+
1544
+ #
1545
+ # set country code for given coutry name or email address
1546
+ #
1547
+ def set_country_code(email, country = None):
1548
+
1549
+ codes = {
1550
+ 'CHINA' : "P.R.CHINA",
1551
+ 'ENGLAND' : "UNITED.KINGDOM",
1552
+ 'FR' : "FRANCE",
1553
+ 'KOREA' : "SOUTH.KOREA",
1554
+ 'USSR' : "RUSSIA",
1555
+ 'US' : "UNITED.STATES",
1556
+ 'U.S.A.' : "UNITED.STATES"
1557
+ }
1558
+
1559
+ if country:
1560
+ country = country.upper()
1561
+ ms = re.match(r'^(\w+)\s(\w+)$', country)
1562
+ if ms:
1563
+ country = ms.group(1) + '.' + ms.group(2)
1564
+ elif country in codes:
1565
+ country = codes[country]
1566
+ else:
1567
+ country = email_to_country(email)
1568
+
1569
+ return country
1570
+
1571
+ # return wuser.wuid upon success, 0 otherwise
1572
+ def check_wuser_wuid(email, date = None):
1573
+
1574
+ if not email: return 0
1575
+ emcond = "email = '{}'".format(email)
1576
+ if not date:
1577
+ date = 'today'
1578
+ datecond = "until_date IS NULL"
1579
+ else:
1580
+ datecond = "(start_date IS NULL OR start_date <= '{}') AND (until_date IS NULL OR until_date >= '{}')".format(date, date)
1581
+
1582
+ pgrec = pgget("wuser", "wuid", "{} AND {}".format(emcond, datecond), PGDBI['ERRLOG'])
1583
+ if pgrec: return pgrec['wuid']
1584
+
1585
+ # check again if a user is on file with different date range
1586
+ pgrec = pgget("wuser", "wuid", emcond, PgLOG.LOGERR)
1587
+ if pgrec: return pgrec['wuid']
1588
+
1589
+ # now add one in
1590
+ record = {'email' : email}
1591
+ # check again if a ruser is on file
1592
+ pgrec = pgget("ruser", "*", emcond + " AND end_date IS NULL", PGDBI['ERRLOG'])
1593
+ if not pgrec: pgrec = pgget("ruser", "*", emcond, PGDBI['ERRLOG'])
1594
+
1595
+ if pgrec:
1596
+ record['ruid'] = pgrec['id']
1597
+ record['fstname'] = pgrec['fname']
1598
+ record['lstname'] = pgrec['lname']
1599
+ record['country'] = pgrec['country']
1600
+ record['org_type'] = get_org_type(pgrec['org_type'], pgrec['email'])
1601
+ record['start_date'] = str(pgrec['rdate'])
1602
+ if pgrec['end_date']:
1603
+ record['until_date'] = str(pgrec['end_date'])
1604
+ record['stat_flag'] = 'C'
1605
+ else:
1606
+ record['stat_flag'] = 'A'
1607
+
1608
+ if pgrec['title']: record['utitle'] = pgrec['title']
1609
+ if pgrec['mname']: record['midinit'] = pgrec['mname'][0]
1610
+ if pgrec['org']: record['org_name'] = pgrec['org']
1611
+ else:
1612
+ record['stat_flag'] = 'M'
1613
+ record['org_type'] = get_org_type('', email)
1614
+ record['country'] = email_to_country(email)
1615
+
1616
+ wuid = pgadd("wuser", record, PgLOG.LOGERR|PgLOG.AUTOID)
1617
+ if wuid:
1618
+ if pgrec:
1619
+ PgLOG.pglog("{}({}, {}) Added as wuid({})".format(email, pgrec['lname'], pgrec['fname'], wuid), PgLOG.LGWNEM)
1620
+ else:
1621
+ PgLOG.pglog("{} Added as wuid({})".format(email, wuid), PgLOG.LGWNEM)
1622
+ return wuid
1623
+
1624
+ return 0
1625
+
1626
+ # return wuser.wuid upon success, 0 otherwise
1627
+ def check_cdp_wuser(username):
1628
+
1629
+ pgrec = pgget("wuser", "wuid", "cdpname = '{}'".format(username), PGDBI['EXITLG'])
1630
+ if pgrec: return pgrec['wuid']
1631
+
1632
+ idrec = pgget("wuser", "wuid", "email = '{}'".format(pgrec['email']), PGDBI['EXITLG'])
1633
+ wuid = idrec['wuid'] if idrec else 0
1634
+ if wuid > 0:
1635
+ idrec = {}
1636
+ idrec['cdpid'] = pgrec['cdpid']
1637
+ idrec['cdpname'] = pgrec['cdpname']
1638
+ pgupdt("wuser", idrec, "wuid = {}".format(wuid) , PGDBI['EXITLG'])
1639
+ else:
1640
+ pgrec['stat_flag'] = 'A'
1641
+ pgrec['org_type'] = get_org_type(pgrec['org_type'], pgrec['email'])
1642
+ pgrec['country'] = email_to_country(pgrec['email'])
1643
+ wuid = pgadd("wuser", pgrec, PGDBI['EXITLG']|PgLOG.AUTOID)
1644
+ if wuid > 0:
1645
+ PgLOG.pglog("CDP User {} added as wuid = {} in RDADB".format(username, wuid), PgLOG.LGWNEM)
1646
+
1647
+ return wuid
1648
+
1649
+ #
1650
+ # for given email to get long country name
1651
+ #
1652
+ def email_to_country(email):
1653
+
1654
+ ms = re.search(r'\.(\w\w)$', email)
1655
+ if ms:
1656
+ pgrec = pgget("countries", "token", "domain_id = '{}'".format(ms.group(1)), PGDBI['EXITLG'])
1657
+ if pgrec: return pgrec['token']
1658
+ elif re.search(r'\.(gov|edu|mil|org|com|net)$', email):
1659
+ return "UNITED.STATES"
1660
+ else:
1661
+ return "UNKNOWN"
1662
+
1663
+ #
1664
+ # if filelists is published for given dataset, reset it to 'P'
1665
+ #
1666
+ def reset_rdadb_version(dsid):
1667
+
1668
+ pgexec("UPDATE dataset SET version = version + 1 WHERE dsid = '{}'".format(dsid), PGDBI['ERRLOG'])
1669
+
1670
+ #
1671
+ # check the use rdadb flag in table dataset for a given dataset and given values
1672
+ #
1673
+ def use_rdadb(dsid, logact = 0, vals = None):
1674
+
1675
+ ret = '' # default to empty in case dataset not in RDADB
1676
+ if dsid:
1677
+ pgrec = pgget("dataset", "use_rdadb", "dsid = '{}'".format(dsid), PGDBI['EXITLG'])
1678
+ if pgrec:
1679
+ ret = 'N' # default to 'N' if dataset record in RDADB already
1680
+ if pgrec['use_rdadb']:
1681
+ if not vals: vals = "IPYMW" # default to Internal; Publishable; Yes RDADB
1682
+ if vals.find(pgrec['use_rdadb']) > -1:
1683
+ ret = pgrec['use_rdadb']
1684
+ elif logact:
1685
+ PgLOG.pglog("Dataset '{}' is not in RDADB!".format(dsid), logact)
1686
+
1687
+ return ret
1688
+
1689
+ #
1690
+ # fld: field name for querry condition
1691
+ # vals: reference to aaray of values
1692
+ # isstr: 1 for string values requires quotes and support wildcard
1693
+ # noand: 1 for skiping the leading ' AND ' for condition
1694
+ # return a condition string for a given field
1695
+ #
1696
+ def get_field_condition(fld, vals, isstr = 0, noand = 0):
1697
+
1698
+ cnd = wcnd = negative = ''
1699
+ sign = "="
1700
+ logic = " OR "
1701
+ count = len(vals) if vals else 0
1702
+ if count == 0: return ''
1703
+ ncnt = scnt = wcnt = cnt = 0
1704
+ for i in range(count):
1705
+ val = vals[i]
1706
+ if val is None or (i > 0 and val == vals[i-1]): continue
1707
+ if i == 0 and val == PGSIGNS[0]:
1708
+ negative = "NOT "
1709
+ logic = " AND "
1710
+ continue
1711
+ if scnt == 0 and isinstance(val, str):
1712
+ ms = re.match(r'^({})$'.format('|'.join(PGSIGNS[1:])), val)
1713
+ if ms:
1714
+ osign = sign = ms.group(1)
1715
+ scnt += 1
1716
+ if sign == "<>":
1717
+ scnt += 1
1718
+ sign = negative + "BETWEEN"
1719
+ elif negative:
1720
+ sign = "<=" if (sign == ">") else ">="
1721
+ continue
1722
+ if isstr:
1723
+ if not isinstance(val, str): val = str(val)
1724
+ if sign == "=":
1725
+ if not val:
1726
+ ncnt += 1 # found null string
1727
+ elif val.find('%') > -1:
1728
+ sign = negative + "LIKE"
1729
+ elif re.search(r'[\[\(\?\.]', val):
1730
+ sign = negative + "SIMILAR TO"
1731
+ if val.find("'") != 0:
1732
+ val = "'{}'".format(val)
1733
+ elif isinstance(val, str):
1734
+ if val.find('.') > -1:
1735
+ val = float(val)
1736
+ else:
1737
+ val = int(val)
1738
+ if sign == "=":
1739
+ if cnt > 0: cnd += ", "
1740
+ cnd += str(val)
1741
+ cnt += 1
1742
+ else:
1743
+ if sign == "AND":
1744
+ wcnd += " {} {}".format(sign, val)
1745
+ else:
1746
+ if wcnt > 0: wcnd += logic
1747
+ wcnd += "{} {} {}".format(fld, sign, val)
1748
+ wcnt += 1
1749
+ if re.search(r'BETWEEN$', sign):
1750
+ sign = "AND"
1751
+ else:
1752
+ sign = "="
1753
+ scnt = 0
1754
+
1755
+ if scnt > 0:
1756
+ s = 's' if scnt > 1 else ''
1757
+ PgLOG.pglog("Need {} value{} after sign '{}'".format(scnt, s, osign), PgLOG.LGEREX)
1758
+ if wcnt > 1: wcnd = "({})".format(wcnd)
1759
+ if cnt > 0:
1760
+ if cnt > 1:
1761
+ cnd = "{} {}IN ({})".format(fld, negative, cnd)
1762
+ else:
1763
+ cnd = "{} {} {}".format(fld, ("<>" if negative else "="), cnd)
1764
+ if ncnt > 0:
1765
+ ncnd = "{} IS {}NULL".format(fld, negative)
1766
+ cnd = "({}{}{})".format(cnd, logic, ncnd)
1767
+ if wcnt > 0: cnd = "({}{}{})".format(cnd, logic, wcnd)
1768
+ elif wcnt > 0:
1769
+ cnd = wcnd
1770
+ if cnd and not noand: cnd = " AND " + cnd
1771
+
1772
+ return cnd
1773
+
1774
+ #
1775
+ # build up fieldname string for given or default condition
1776
+ #
1777
+ def fieldname_string(fnames, dnames = None, anames = None, wflds = None):
1778
+
1779
+ if not fnames:
1780
+ fnames = dnames # include default fields names
1781
+ elif re.match(r'^all$', fnames, re.I):
1782
+ fnames = anames # include all field names
1783
+
1784
+ if not wflds: return fnames
1785
+
1786
+ for wfld in wflds:
1787
+ if not wfld or fnames.find(wfld) > -1: continue # empty field, or included already
1788
+ if wfld == "Q":
1789
+ pos = fnames.find("R") # request name
1790
+ elif wfld == "Y":
1791
+ pos = fnames.find("X") # parent group name
1792
+ elif wfld == "G":
1793
+ pos = fnames.find("I") # group name
1794
+ else:
1795
+ pos = -1 # prepend other with-field names
1796
+
1797
+ if pos == -1:
1798
+ fnames = wfld + fnames # prepend with-field
1799
+ else:
1800
+ fnames = fnames[0:pos] + wfld + fnames[pos:] # insert with-field
1801
+
1802
+ return fnames
1803
+
1804
+ #
1805
+ # Function get_group_field_path(gindex: group index
1806
+ # dsid: dataset id
1807
+ # field: path field name: webpath or savedpath)
1808
+ # go through group tree upward to find a none-empty path, return it or null
1809
+ #
1810
+ def get_group_field_path(gindex, dsid, field):
1811
+
1812
+ if gindex:
1813
+ pgrec = pgget("dsgroup", "pindex, {}".format(field),
1814
+ "dsid = '{}' AND gindex = {}".format(dsid, gindex), PGDBI['EXITLG'])
1815
+ else:
1816
+ pgrec = pgget("dataset", field,
1817
+ "dsid = '{}'".format(dsid), PGDBI['EXITLG'])
1818
+ if pgrec:
1819
+ if pgrec[field]:
1820
+ return pgrec[field]
1821
+ elif gindex:
1822
+ return get_group_field_path(pgrec['pindex'], dsid, field)
1823
+ else:
1824
+ return None
1825
+
1826
+ #
1827
+ # get the specialist info for a given dataset
1828
+ #
1829
+ def get_specialist(dsid, logact = PGDBI['ERRLOG']):
1830
+
1831
+ if dsid in SPECIALIST: return SPECIALIST['dsid']
1832
+
1833
+ pgrec = pgget("dsowner, dssgrp", "specialist, lstname, fstname",
1834
+ "specialist = logname AND dsid = '{}' AND priority = 1".format(dsid), logact)
1835
+ if pgrec:
1836
+ if pgrec['specialist'] == "datahelp" or pgrec['specialist'] == "dss":
1837
+ pgrec['lstname'] = "Help"
1838
+ pgrec['fstname'] = "Data"
1839
+ else:
1840
+ pgrec['specialist'] = "datahelp"
1841
+ pgrec['lstname'] = "Help"
1842
+ pgrec['fstname'] = "Data"
1843
+
1844
+ SPECIALIST['dsid'] = pgrec # cache specialist info for dsowner of dsid
1845
+ return pgrec
1846
+
1847
+ #
1848
+ # build customized email from get_email()
1849
+ #
1850
+ def build_customized_email(table, field, condition, subject, logact = 0):
1851
+
1852
+ msg = PgLOG.get_email()
1853
+
1854
+ if not msg: return PgLOG.FAILURE
1855
+
1856
+ sender = PgLOG.PGLOG['CURUID'] + "@ucar.edu"
1857
+ receiver = PgLOG.PGLOG['EMLADDR'] if PgLOG.PGLOG['EMLADDR'] else (PgLOG.PGLOG['CURUID'] + "@ucar.edu")
1858
+ if receiver.find(sender) < 0: PgLOG.add_carbon_copy(sender, 1)
1859
+ ebuf = "From: {}\nTo: {}\n".format(sender, receiver)
1860
+ if PgLOG.PGLOG['CCDADDR']: ebuf += "Cc: {}\n".format(PgLOG.PGLOG['CCDADDR'])
1861
+ if not subject: subject = "Message from {}-{}".format(PgLOG.PGLOG['HOSTNAME'], PgLOG.get_command())
1862
+ ebuf += "Subject: {}!\n\n{}\n".format(subject, msg)
1863
+
1864
+ estat = cache_customized_email(table, field, condition, ebuf, logact)
1865
+ if estat and logact:
1866
+ PgLOG.pglog("Email {} cached to '{}.{}' for {}, Subject: {}".format(receiver, table, field, condition, subject), logact)
1867
+
1868
+ return estat
1869
+
1870
+ #
1871
+ # email: full user email address
1872
+ #
1873
+ # get user real name from table ruser for a given email address
1874
+ # opts == 1 : include email
1875
+ # opts == 2 : include org_type
1876
+ # opts == 4 : include country
1877
+ # opts == 8 : include valid_email
1878
+ # opts == 16 : include org
1879
+ #
1880
+ def get_ruser_names(email, opts = 0, date = None):
1881
+
1882
+ fields = "lname lstname, fname fstname"
1883
+
1884
+ if opts&1: fields += ", email"
1885
+ if opts&2: fields += ", org_type"
1886
+ if opts&4: fields += ", country"
1887
+ if opts&8: fields += ", valid_email"
1888
+ if opts&16: fields += ", org"
1889
+
1890
+ if date:
1891
+ datecond = "rdate <= '{}' AND (end_date IS NULL OR end_date >= '{}')".format(date, date)
1892
+ else:
1893
+ datecond = "end_date IS NULL"
1894
+ date = time.strftime("%Y-%m-%d", (time.gmtime() if PgLOG.PGLOG['GMTZ'] else time.localtime()))
1895
+ emcnd = "email = '{}'".format(email)
1896
+ pgrec = pgget("ruser", fields, "{} AND {}".format(emcnd, datecond), PgLOG.LGEREX)
1897
+ if not pgrec: # missing user record add one in
1898
+ PgLOG.pglog("{}: email not in ruser for {}".format(email, date), PgLOG.LOGWRN)
1899
+ # check again if a user is on file with different date range
1900
+ pgrec = pgget("ruser", fields, emcnd, PgLOG.LGEREX)
1901
+ if not pgrec and pgget("dssdb.user", '', emcnd):
1902
+ fields = "lstname, fstname"
1903
+ if opts&1: fields += ", email"
1904
+ if opts&2: fields += ", org_type"
1905
+ if opts&4: fields += ", country"
1906
+ if opts&8: fields += ", email valid_email"
1907
+ if opts&16: fields += ", org_name org"
1908
+ pgrec = pgget("dssdb.user", fields, emcnd, PgLOG.LGEREX)
1909
+
1910
+ if pgrec and pgrec['lstname']:
1911
+ pgrec['name'] = (pgrec['fstname'].capitalize() + ' ') if pgrec['fstname'] else ''
1912
+ pgrec['name'] += pgrec['lstname'].capitalize()
1913
+ else:
1914
+ if not pgrec: pgrec = {}
1915
+ pgrec['name'] = email.split('@')[0]
1916
+ if opts&1: pgrec['email'] = email
1917
+
1918
+ return pgrec
1919
+
1920
+ #
1921
+ # cache a customized email for sending it later
1922
+ #
1923
+ def cache_customized_email(table, field, condition, emlmsg, logact = 0):
1924
+
1925
+ pgrec = {field: emlmsg}
1926
+ if pgupdt(table, pgrec, condition, logact|PgLOG.ERRLOG):
1927
+ if logact: PgLOG.pglog("Email cached to '{}.{}' for {}".format(table, field, condition), logact&(~PgLOG.EXITLG))
1928
+ return PgLOG.SUCCESS
1929
+ else:
1930
+ msg = "cache email to '{}.{}' for {}".format(table, field, condition)
1931
+ PgLOG.pglog("Error msg, try to send directly now", logact|PgLOG.ERRLOG)
1932
+ return PgLOG.send_customized_email(msg, emlmsg, logact)
1933
+
1934
+ #
1935
+ # otype: user organization type
1936
+ # email: user email address)
1937
+ #
1938
+ # return: orgonizaion type like DSS, NCAR, UNIV...
1939
+ #
1940
+ def get_org_type(otype, email):
1941
+
1942
+ if not otype: otype = "OTHER"
1943
+ if email:
1944
+ ms = re.search(r'(@|\.)ucar\.edu$', email)
1945
+ if ms:
1946
+ mc = ms.group(1)
1947
+ if otype == 'UCAR' or otype == 'OTHER': otype = 'NCAR'
1948
+ if otype == 'NCAR' and mc == '@':
1949
+ ms = re.match(r'^(.+)@', email)
1950
+ if ms and pgget("dssgrp", "", "logname = '{}'".format(ms.group(1))): otype = 'DSS'
1951
+ else:
1952
+ ms = re.search(r'\.(mil|org|gov|edu|com|net)(\.\w\w|$)', email)
1953
+ if ms:
1954
+ otype = ms.group(1).upper()
1955
+ if otype == 'EDU': otype = "UNIV"
1956
+
1957
+ return otype
1958
+
1959
+ #
1960
+ # join values and handle the null values
1961
+ #
1962
+ def join_values(vstr, vals):
1963
+
1964
+ if vstr:
1965
+ vstr += "\n"
1966
+ elif vstr is None:
1967
+ vstr = ''
1968
+
1969
+ return "{}Value{}({})".format(vstr, ('s' if len(vals) > 1 else ''), ', '.join(map(str, vals)))
1970
+
1971
+ #
1972
+ # check table hostname to find the system down times. Cache the result for 10 minutes
1973
+ #
1974
+ def get_system_downs(hostname, logact = 0):
1975
+
1976
+ curtime = int(time.time())
1977
+ newhost = 0
1978
+
1979
+ if hostname not in SYSDOWN:
1980
+ SYSDOWN[hostname] = {}
1981
+ newhost = 1
1982
+ if newhost or (curtime - SYSDOWN[hostname]['chktime']) > 600:
1983
+ SYSDOWN[hostname]['chktime'] = curtime
1984
+ SYSDOWN[hostname]['start'] = 0
1985
+ SYSDOWN[hostname]['end'] = 0
1986
+ SYSDOWN[hostname]['active'] = 1
1987
+ SYSDOWN[hostname]['path'] = None
1988
+
1989
+ pgrec = pgget('hostname', 'service, domain, downstart, downend',
1990
+ "hostname = '{}'".format(hostname), logact)
1991
+ if pgrec:
1992
+ if pgrec['service'] == 'N':
1993
+ SYSDOWN[hostname]['start'] = curtime
1994
+ SYSDOWN[hostname]['active'] = 0
1995
+ else:
1996
+ start = int(datetime.timestamp(pgrec['downstart'])) if pgrec['downstart'] else 0
1997
+ end = int(datetime.timestamp(pgrec['downend'])) if pgrec['downend'] else 0
1998
+ if start > 0 and (end == 0 or end > curtime):
1999
+ SYSDOWN[hostname]['start'] = start
2000
+ SYSDOWN[hostname]['end'] = end
2001
+ if pgrec['service'] == 'S' and pgrec['domain'] and re.match(r'^/', pgrec['domain']):
2002
+ SYSDOWN[hostname]['path'] = pgrec['domain']
2003
+
2004
+ SYSDOWN[hostname]['curtime'] = curtime
2005
+
2006
+ return SYSDOWN[hostname]
2007
+
2008
+ #
2009
+ # return seconds for how long the system will continue to be down
2010
+ #
2011
+ def system_down_time(hostname, offset, logact = 0):
2012
+
2013
+ down = get_system_downs(hostname, logact)
2014
+ if down['start'] and down['curtime'] >= (down['start'] - offset):
2015
+ if not down['end']:
2016
+ if PgLOG.PGLOG['PGBATCH'] == PgLOG.PGLOG['PBSNAME']:
2017
+ return PgLOG.PGLOG['PBSTIME']
2018
+ elif down['curtime'] <= down['end']:
2019
+ return (down['end'] - down['curtime'])
2020
+
2021
+ return 0 # the system is not down
2022
+
2023
+ #
2024
+ # return string message if the system is down
2025
+ #
2026
+ def system_down_message(hostname, path, offset, logact = 0):
2027
+
2028
+ down = get_system_downs(hostname, logact)
2029
+ msg = None
2030
+ if down['start'] and down['curtime'] >= (down['start'] - offset):
2031
+ match = match_down_path(path, down['path'])
2032
+ if match:
2033
+ msg = "{}{}:".format(hostname, ('-' + path) if match > 0 else '')
2034
+ if not down['active']:
2035
+ msg += " Not in Service"
2036
+ else:
2037
+ msg += " Planned down, started at " + PgLOG.current_datetime(down['start'])
2038
+ if not down['end']:
2039
+ msg += " And no end time specified"
2040
+ elif down['curtime'] <= down['end']:
2041
+ msg = " And will end by " + PgLOG.current_datetime(down['end'])
2042
+
2043
+ return msg
2044
+
2045
+ #
2046
+ # return 1 if given path match daemon paths, 0 if not; -1 if cannot compare
2047
+ #
2048
+ def match_down_path(path, dpaths):
2049
+
2050
+ if not (path and dpaths): return -1
2051
+
2052
+ paths = re.split(':', dpaths)
2053
+
2054
+ for p in paths:
2055
+ if re.match(r'^{}'.format(p), path): return 1
2056
+
2057
+ return 0
2058
+
2059
+ # validate is login user is in DECS group
2060
+ # check all node if skpdsg is false, otherwise check non-DSG nodes
2061
+ def validate_decs_group(cmdname, logname, skpdsg):
2062
+
2063
+ if skpdsg and PgLOG.PGLOG['DSGHOSTS'] and re.search(r'(^|:){}'.format(PgLOG.PGLOG['HOSTNAME']), PgLOG.PGLOG['DSGHOSTS']): return
2064
+ if not logname: lgname = PgLOG.PGLOG['CURUID']
2065
+
2066
+ if not pgget("dssgrp", '', "logname = '{}'".format(logname), PgLOG.LGEREX):
2067
+ PgLOG.pglog("{}: Must be in DECS Group to run '{}' on {}".format(logname, cmdname, PgLOG.PGLOG['HOSTNAME']), PgLOG.LGEREX)
2068
+
2069
+ #
2070
+ # add an allusage record into yearly table; create a new yearly table if it does not exist
2071
+ # year -- year to identify the yearly table, evaluated if missing
2072
+ # records -- hash to hold one or multiple records.
2073
+ # Dict keys: email -- user email address,
2074
+ # org_type -- organization type
2075
+ # country -- country code
2076
+ # dsid -- dataset ID
2077
+ # date -- date data accessed
2078
+ # time -- time data accessed
2079
+ # quarter -- quarter of the year data accessed
2080
+ # size -- bytes of data accessed
2081
+ # method -- delivery methods: MSS,Web,Ftp,Tape,Cd,Disk,Paper,cArt,Micro
2082
+ # source -- usage source flag: W - wusage, O - ordusage
2083
+ # midx -- refer to mbr2loc.midx if not 0
2084
+ # ip -- user IP address
2085
+ # region -- user region name; for example, Colorado
2086
+ #
2087
+ # isarray -- if true, mutiple records provided via arrays for each hash key
2088
+ # docheck -- if 1, check and add only if record is not on file
2089
+ # docheck -- if 2, check and add if record is not on file, and update if exists
2090
+ # docheck -- if 4, check and add if record is not on file, and update if exists,
2091
+ # and also checking NULL email value too
2092
+ #
2093
+ def add_yearly_allusage(year, records, isarray = 0, docheck = 0):
2094
+
2095
+ acnt = 0
2096
+ if not year:
2097
+ ms = re.match(r'^(\d\d\d\d)', str(records['date'][0] if isarray else records['date']))
2098
+ if ms: year = ms.group(1)
2099
+ tname = "allusage_{}".format(year)
2100
+ if isarray:
2101
+ cnt = len(records['email'])
2102
+ if 'quarter' not in records: records['quarter'] = [0]*cnt
2103
+ for i in range(cnt):
2104
+ if not records['quarter'][i]:
2105
+ ms = re.search(r'-(\d+)-', str(records['date'][i]))
2106
+ if ms: records['quarter'][i] = int((int(ms.group(1))-1)/3)+1
2107
+ if docheck:
2108
+ for i in range(cnt):
2109
+ record = {}
2110
+ for key in records:
2111
+ record[key] = records[key][i]
2112
+ cnd = "email = '{}' AND dsid = '{}' AND method = '{}' AND date = '{}' AND time = '{}'".format(
2113
+ record['email'], record['dsid'], record['method'], record['date'], record['time'])
2114
+ pgrec = pgget(tname, 'aidx', cnd, PgLOG.LOGERR|PgLOG.ADDTBL)
2115
+ if docheck == 4 and not pgrec:
2116
+ cnd = "email IS NULL AND dsid = '{}' AND method = '{}' AND date = '{}' AND time = '{}'".format(
2117
+ record['dsid'], record['method'], record['date'], record['time'])
2118
+ pgrec = pgget(tname, 'aidx', cnd, PgLOG.LOGERR|PgLOG.ADDTBL)
2119
+ if pgrec:
2120
+ if docheck > 1: acnt += pgupdt(tname, record, "aidx = {}".format(pgrec['aidx']), PgLOG.LGEREX)
2121
+ else:
2122
+ acnt += pgadd(tname, record, PgLOG.LGEREX|PgLOG.ADDTBL)
2123
+ else:
2124
+ acnt = pgmadd(tname, records, PgLOG.LGEREX|PgLOG.ADDTBL)
2125
+ else:
2126
+ record = records
2127
+ if not ('quarter' in record and record['quarter']):
2128
+ ms = re.search(r'-(\d+)-', str(record['date']))
2129
+ if ms: record['quarter'] = int((int(ms.group(1))-1)/3)+1
2130
+ if docheck:
2131
+ cnd = "email = '{}' AND dsid = '{}' AND method = '{}' AND date = '{}' AND time = '{}'".format(
2132
+ record['email'], record['dsid'], record['method'], record['date'], record['time'])
2133
+ pgrec = pgget(tname, 'aidx', cnd, PgLOG.LOGERR|PgLOG.ADDTBL)
2134
+ if docheck == 4 and not pgrec:
2135
+ cnd = "email IS NULL AND dsid = '{}' AND method = '{}' AND date = '{}' AND time = '{}'".format(
2136
+ record['dsid'], record['method'], record['date'], record['time'])
2137
+ pgrec = pgget(tname, 'aidx', cnd, PgLOG.LOGERR|PgLOG.ADDTBL)
2138
+ if pgrec:
2139
+ if docheck > 1: acnt = pgupdt(tname, record, "aidx = {}".format(pgrec['aidx']), PgLOG.LGEREX)
2140
+ return acnt
2141
+ acnt = pgadd(tname, record, PgLOG.LGEREX|PgLOG.ADDTBL)
2142
+
2143
+ return acnt
2144
+
2145
+ #
2146
+ # add a wusage record into yearly table; create a new yearly table if it does not exist
2147
+ # year -- year to identify the yearly table, evaluated if missing
2148
+ # records -- hash to hold one or multiple records.
2149
+ # Dict keys: wid - reference to wfile.wid
2150
+ # wuid_read - reference to wuser.wuid, 0 if missing email
2151
+ # dsid - reference to dataset.dsid at the time of read
2152
+ # date_read - date file read
2153
+ # time_read - time file read
2154
+ # quarter - quarter of the year data accessed
2155
+ # size_read - bytes of data read
2156
+ # method - download methods: WEB, CURL, MGET, FTP and MGET
2157
+ # locflag - location flag: Glade or Object
2158
+ # ip - IP address
2159
+ #
2160
+ # isarray -- if true, mutiple records provided via arrays for each hash key
2161
+ #
2162
+ def add_yearly_wusage(year, records, isarray = 0):
2163
+
2164
+ acnt = 0
2165
+ if not year:
2166
+ ms = re.match(r'^(\d\d\d\d)', str(records['date_read'][0] if isarray else records['date_read']))
2167
+ if ms: year = ms.group(1)
2168
+ tname = "wusage_{}".format(year)
2169
+ if isarray:
2170
+ if 'quarter' not in records:
2171
+ cnt = len(records['wid'])
2172
+ records['quarter'] = [0]*cnt
2173
+ for i in range(cnt):
2174
+ ms = re.search(r'-(\d+)-', str(records['date_read'][i]))
2175
+ if ms: records['quarter'][i] = (int((int(ms.group(1))-1)/3)+1)
2176
+ acnt = pgmadd(tname, records, PgLOG.LGEREX|PgLOG.ADDTBL)
2177
+ else:
2178
+ record = records
2179
+ if 'quarter' not in record:
2180
+ ms = re.search(r'-(\d+)-', str(record['date_read']))
2181
+ if ms: record['quarter'] = (int((int(ms.group(1))-1)/3)+1)
2182
+ acnt = pgadd(tname, record, PgLOG.LGEREX|PgLOG.ADDTBL)
2183
+
2184
+ return acnt
2185
+
2186
+ #
2187
+ # double quote a array of single or sign delimited strings
2188
+ #
2189
+ def pgnames(ary, sign = None, joinstr = None):
2190
+
2191
+ pgary = []
2192
+ for a in ary:
2193
+ pgary.append(pgname(a, sign))
2194
+
2195
+ if joinstr == None:
2196
+ return pgary
2197
+ else:
2198
+ return joinstr.join(pgary)
2199
+
2200
+ #
2201
+ # double quote a single or sign delimited string
2202
+ #
2203
+ def pgname(str, sign = None):
2204
+
2205
+ if sign:
2206
+ nstr = ''
2207
+ names = str.split(sign[0])
2208
+ for name in names:
2209
+ if nstr: nstr += sign[0]
2210
+ nstr += pgname(name, sign[1:])
2211
+ else:
2212
+ nstr = str.strip()
2213
+ if nstr and nstr.find('"') < 0:
2214
+ if not re.match(r'^[a-z_][a-z0-9_]*$', nstr) or nstr in PGRES:
2215
+ nstr = '"{}"'.format(nstr)
2216
+
2217
+ return nstr