@@ -360,6 +360,7 @@ pg_rotate_logfile(PG_FUNCTION_ARGS)
360
360
PG_RETURN_BOOL (true);
361
361
}
362
362
363
+ /* Structures used in pg_objs_per_tablespace */
363
364
typedef struct
364
365
{
365
366
char * databaseOid ;
@@ -372,13 +373,25 @@ typedef struct
372
373
int reccount ;
373
374
} tbsp_fctx ;
374
375
375
- int MyFNatoi (const char * numArray , int * value )
376
+ /*
377
+ * Check if a char* array is a number. I don't know if there is
378
+ * something that does the same thing in the codebase, so I wrote
379
+ * my own function.
380
+ */
381
+ static int
382
+ check_is_digit (const char * numArray , int * value )
376
383
{
377
384
int n = 0 ;
378
385
return sscanf (numArray , "%d%n" , value , & n ) > 0 /* integer was converted */
379
386
&& numArray [n ] == '\0' ; /* all input got consumed */
380
387
}
381
388
389
+ /*
390
+ * Function to report which objects are in which tablespace.
391
+ * It follows a similar approach as pg_tablespace_databases, ie,
392
+ * reads the tablespace's directory and list all OIDs in it.
393
+ *
394
+ */
382
395
Datum
383
396
pg_objs_per_tablespace (PG_FUNCTION_ARGS )
384
397
{
@@ -397,9 +410,37 @@ pg_objs_per_tablespace(PG_FUNCTION_ARGS)
397
410
398
411
if (SRF_IS_FIRSTCALL ())
399
412
{
400
-
401
413
MemoryContext oldcontext ;
402
414
TupleDesc tupdesc ;
415
+ Oid tablespaceOid = PG_GETARG_OID (0 );
416
+
417
+ /* If user is not superuser, return a error message and block execution */
418
+ if (!superuser ())
419
+ ereport (ERROR ,
420
+ (errcode (ERRCODE_INSUFFICIENT_PRIVILEGE ),
421
+ (errmsg ("must be superuser to execute this function" ))));
422
+
423
+ /*
424
+ * Global tablespace does not hold databases. If OID passed is from global tablespace
425
+ * return a error and block execution.
426
+ */
427
+ if (tablespaceOid == GLOBALTABLESPACE_OID )
428
+ {
429
+ ereport (ERROR ,
430
+ (errcode (ERRCODE_NO_DATA ),
431
+ (errmsg ("global tablespace never has databases" ))));
432
+ }
433
+ else
434
+ {
435
+ /* if OID passed is from default tablespace point location to base directory.
436
+ * Otherwise point location to tablespace's directory.
437
+ */
438
+ if (tablespaceOid == DEFAULTTABLESPACE_OID )
439
+ location = psprintf ("base" );
440
+ else
441
+ location = psprintf ("pg_tblspc/%u/%s" , tablespaceOid ,
442
+ TABLESPACE_VERSION_DIRECTORY );
443
+ }
403
444
404
445
funcctx = SRF_FIRSTCALL_INIT ();
405
446
oldcontext = MemoryContextSwitchTo (funcctx -> multi_call_memory_ctx );
@@ -412,38 +453,68 @@ pg_objs_per_tablespace(PG_FUNCTION_ARGS)
412
453
413
454
funcctx -> attinmeta = TupleDescGetAttInMetadata (tupdesc );
414
455
415
- location = psprintf ("base" );
456
+ /* allocate directory descriptor. If directory descriptor is null, tablespace does not exists.
457
+ * In this case, return a error and block execution.
458
+ */
416
459
dirdesc = AllocateDir (location );
460
+ if (!dirdesc )
461
+ ereport (ERROR ,
462
+ (errcode (ERRCODE_NO_DATA ),
463
+ (errmsg ("%u is not a valid tablespace Oid" , tablespaceOid ))));
464
+
465
+ /* Reads the tablespace's directory. If it's a tablespace it will contain subdirectories for each
466
+ * database, each one with their own objects.
467
+ */
417
468
while ((direntry = ReadDir (dirdesc , location )) != NULL )
418
469
{
419
470
char * subdir ;
420
-
471
+
472
+ /* ignore parent directories */
421
473
if (direntry -> d_name [0 ] == '.' )
422
474
continue ;
423
475
476
+ /* if directory entry is a directory then it is a database OID. Iterate over it go get the
477
+ * objects in the database.
478
+ */
424
479
if (direntry -> d_type == DT_DIR )
425
480
{
426
- subdir = psprintf ("base/%s" , direntry -> d_name );
481
+ /* point subdir location to tablespace/database directory */
482
+ subdir = psprintf ("%s/%s" , location , direntry -> d_name );
483
+
484
+ /* allocate a descriptor to the tablespace/database location
485
+ * TODO : Test if directory is invalid or does not exists
486
+ */
427
487
subdirdesc = AllocateDir (subdir );
488
+
489
+ /* iterate over tablespace/database directory and get the objects */
428
490
while ((subdirentry = ReadDir (subdirdesc , subdir )) != NULL )
429
491
{
492
+
493
+ /* ignore parent directories */
430
494
if (subdirentry -> d_name [0 ] == '.' )
431
495
continue ;
432
496
433
- if (!MyFNatoi (subdirentry -> d_name , & t ))
497
+ /* check if entry is a number. Do not consider _vm, _fsm and other files */
498
+ if (!check_is_digit (subdirentry -> d_name , & t ))
434
499
continue ;
435
500
501
+ /* If it's the first time, allocate one record into the records array and
502
+ * add a database/object value pair.
503
+ */
436
504
if (maxrecords == 0 )
437
505
{
438
506
records = (tbsp_record * ) malloc (sizeof (tbsp_record ));
439
- records [0 ].databaseOid = get_database_name (atoi (direntry -> d_name ));
507
+ records [0 ].databaseOid = get_database_name (atooid (direntry -> d_name ));
440
508
records [0 ].relationOid = strdup (subdirentry -> d_name );
441
509
maxrecords ++ ;
442
510
}
443
511
else
444
512
{
513
+ /* If the records array is already initialized, realloc one more record and
514
+ * add a database/object value pair into it.
515
+ */
445
516
records = (tbsp_record * ) realloc (records , (maxrecords + 1 ) * sizeof (tbsp_record ));
446
- records [maxrecords ].databaseOid = get_database_name (atoi (direntry -> d_name ));
517
+ records [maxrecords ].databaseOid = get_database_name (atooid (direntry -> d_name ));
447
518
records [maxrecords ].relationOid = strdup (subdirentry -> d_name );
448
519
maxrecords ++ ;
449
520
}
@@ -453,8 +524,10 @@ pg_objs_per_tablespace(PG_FUNCTION_ARGS)
453
524
}
454
525
FreeDir (dirdesc );
455
526
527
+ /* Initialize the context struct used over SRF_PERCALL iterations */
456
528
fctx = palloc (sizeof (tbsp_fctx ));
457
529
530
+ /* assign values to the context struct used to maintain the status over SRF_PERCALL iterations */
458
531
fctx -> records = records ;
459
532
fctx -> reccount = maxrecords ;
460
533
@@ -465,6 +538,7 @@ pg_objs_per_tablespace(PG_FUNCTION_ARGS)
465
538
funcctx = SRF_PERCALL_SETUP ();
466
539
fctx = (tbsp_fctx * ) funcctx -> user_fctx ;
467
540
541
+ /* iterate over the array of records and return a tuple to each database/object value pair */
468
542
if (funcctx -> call_cntr < fctx -> reccount )
469
543
{
470
544
values [0 ] = fctx -> records [funcctx -> call_cntr ].databaseOid ;
0 commit comments