@@ -18,7 +18,9 @@ package postgresql
1818
1919import (
2020 "context"
21+ "fmt"
2122 "reflect"
23+ "strings"
2224 "time"
2325
2426 "k8s.io/apimachinery/pkg/api/errors"
@@ -257,6 +259,20 @@ func (r *PostgresqlPublicationReconciler) mainReconcile(
257259 if err != nil {
258260 return r .manageError (ctx , reqLogger , instance , originalPatch , err )
259261 }
262+ } else {
263+ // Check if reconcile from PG state is necessary because spec haven't been changed
264+ need , err := r .isReconcileOnPGNecessary (ctx , instance , pg , pgDB , pubRes , nameToSearch )
265+ // Check error
266+ if err != nil {
267+ return r .manageError (ctx , reqLogger , instance , originalPatch , err )
268+ }
269+
270+ // Check if it is needed
271+ if need {
272+ reqLogger .Info ("PG state have been changed but not via operator, update need to be done" )
273+
274+ err = r .manageUpdate (ctx , instance , pg , pgDB , pubRes , nameToSearch )
275+ }
260276 }
261277
262278 // Check if owner are aligned
@@ -316,6 +332,167 @@ func (r *PostgresqlPublicationReconciler) mainReconcile(
316332 return r .manageSuccess (ctx , reqLogger , instance , originalPatch )
317333}
318334
335+ func (* PostgresqlPublicationReconciler ) isReconcileOnPGNecessary (
336+ ctx context.Context ,
337+ instance * v1alpha1.PostgresqlPublication ,
338+ pg postgres.PG ,
339+ pgDB * v1alpha1.PostgresqlDatabase ,
340+ pubRes * postgres.PublicationResult ,
341+ currentPublicationName string ,
342+ ) (bool , error ) {
343+ instanceSpec := instance .Spec
344+
345+ // Check with parameters
346+ // nil spec case
347+ if instanceSpec .WithParameters == nil && (pubRes .PublicationViaRoot || ! pubRes .Delete || ! pubRes .Insert || ! pubRes .Truncate || ! pubRes .Update ) {
348+ return true , nil
349+ }
350+ // Non nil spec case
351+ if instanceSpec .WithParameters != nil {
352+ // publication via root check
353+ if (instanceSpec .WithParameters .PublishViaPartitionRoot == nil && pubRes .PublicationViaRoot ) ||
354+ (instanceSpec .WithParameters .PublishViaPartitionRoot != nil && * instanceSpec .WithParameters .PublishViaPartitionRoot != pubRes .PublicationViaRoot ) {
355+ return true , nil
356+ }
357+
358+ // Now check publish parameters
359+ // Save
360+ publish := strings .ToLower (instanceSpec .WithParameters .Publish )
361+ // Empty spec case
362+ if publish == "" && (! pubRes .Delete || ! pubRes .Insert || ! pubRes .Truncate || ! pubRes .Update ) {
363+ return true , nil
364+ }
365+ // Not empty case
366+ if publish != "" &&
367+ (strings .Contains (publish , "insert" ) != pubRes .Insert ||
368+ strings .Contains (publish , "update" ) != pubRes .Update ||
369+ strings .Contains (publish , "delete" ) != pubRes .Delete ||
370+ strings .Contains (publish , "truncate" ) != pubRes .Truncate ) {
371+ return true , nil
372+ }
373+ }
374+
375+ // Check if it is a all tables
376+ if instanceSpec .AllTables {
377+ // This state cannot be updated so.. Ignoring it
378+ return false , nil
379+ }
380+
381+ // Get publication details
382+ details , err := pg .GetPublicationTablesDetails (ctx , pgDB .Status .Database , currentPublicationName )
383+ if err != nil {
384+ return false , err
385+ }
386+
387+ // Check if we are in the all tables in schema case
388+ if len (instanceSpec .TablesInSchema ) != 0 {
389+ // Compute list of schema coming from publication tables and compare list length.
390+ // The computed list must be <= with the desired list
391+ // Why <= ? Because we can list a schema without any tables in
392+ // So we need to check > to go out quickly
393+ // After that, we need to check that computed list is included in the desired list
394+ // Finally, check that all tables from all schema are found
395+
396+ // Compute list of schema
397+ computedSchemaList := []string {}
398+ currentTableNames := []string {}
399+
400+ for _ , it := range details {
401+ if ! lo .Contains (computedSchemaList , it .SchemaName ) {
402+ computedSchemaList = append (computedSchemaList , it .SchemaName )
403+ }
404+ if ! lo .Contains (currentTableNames , it .TableName ) {
405+ currentTableNames = append (currentTableNames , it .TableName )
406+ }
407+ }
408+
409+ // Check length
410+ if len (computedSchemaList ) > len (instanceSpec .TablesInSchema ) {
411+ return true , nil
412+ }
413+
414+ // Check include/subset
415+ if ! lo .Every (instanceSpec .TablesInSchema , computedSchemaList ) {
416+ return true , nil
417+ }
418+
419+ // Loop over all schema listed
420+ for _ , sch := range instanceSpec .TablesInSchema {
421+ // Get all tables in this schema
422+ tableDetails , err := pg .GetTablesInSchema (ctx , pgDB .Status .Database , sch )
423+ if err != nil {
424+ return false , err
425+ }
426+
427+ // Transform in string slice
428+ allTableNamesInSchema := lo .Map (tableDetails , func (it * postgres.TableOwnership , _ int ) string { return it .TableName })
429+ // Now check differences
430+ r1 , r2 := lo .Difference (allTableNamesInSchema , currentTableNames )
431+ if len (r1 ) != 0 || len (r2 ) != 0 {
432+ return true , nil
433+ }
434+ }
435+ } else {
436+ // Need to check with tables
437+ // Loop over spec table list
438+ for _ , st := range instanceSpec .Tables {
439+ // Check if table isn't in the current list
440+ detail , found := lo .Find (details , func (it * postgres.PublicationTableDetail ) bool {
441+ return st .TableName == it .TableName || st .TableName == fmt .Sprintf ("%s.%s" , it .SchemaName , it .TableName )
442+ })
443+ if ! found {
444+ return true , nil
445+ }
446+
447+ // Check if additional where aren't identical (nil case)
448+ if st .AdditionalWhere != detail .AdditionalWhere {
449+ return true , nil
450+ }
451+ // Check if additional where aren't identical (phase 2)
452+ if st .AdditionalWhere != nil && detail .AdditionalWhere != nil && fmt .Sprintf ("(%s)" , * st .AdditionalWhere ) != * detail .AdditionalWhere {
453+ return true , nil
454+ }
455+
456+ // Now need to check columns
457+
458+ columnNamesToCheck := []string {}
459+
460+ // Check if columns aren't set in spec
461+ if st .Columns == nil {
462+ // If so, get real columns from table and check if list aren't identical
463+ // Split spec table name
464+ spl := strings .Split (st .TableName , "." )
465+ var schemaName , tableName string
466+
467+ // Check split size
468+ if len (spl ) == 1 {
469+ schemaName = defaultPGPublicSchemaName
470+ tableName = spl [0 ]
471+ } else {
472+ schemaName = spl [0 ]
473+ tableName = spl [1 ]
474+ }
475+
476+ columnNamesToCheck , err = pg .GetColumnNamesFromTable (ctx , pgDB .Status .Database , schemaName , tableName )
477+ if err != nil {
478+ return false , err
479+ }
480+ } else {
481+ columnNamesToCheck = * st .Columns
482+ }
483+
484+ // Check difference
485+ r1 , r2 := lo .Difference (columnNamesToCheck , detail .Columns )
486+ if len (r1 ) != 0 || len (r2 ) != 0 {
487+ return true , nil
488+ }
489+ }
490+ }
491+
492+ // Default
493+ return false , nil
494+ }
495+
319496func (* PostgresqlPublicationReconciler ) manageUpdate (
320497 ctx context.Context ,
321498 instance * v1alpha1.PostgresqlPublication ,
@@ -350,11 +527,15 @@ func (*PostgresqlPublicationReconciler) manageUpdate(
350527 if instance .Spec .WithParameters != nil {
351528 // Change with
352529 builder = builder .SetWith (instance .Spec .WithParameters .Publish , instance .Spec .WithParameters .PublishViaPartitionRoot )
530+ } else {
531+ // Potential reconcile case to manage
532+ if pubRes .PublicationViaRoot || ! pubRes .Delete || ! pubRes .Insert || ! pubRes .Truncate || ! pubRes .Update {
533+ // Set default
534+ builder = builder .SetDefaultWith ()
535+ }
353536 }
354537
355538 // Perform update
356- // ? Note: this will do an alter even if it is unnecessary
357- // ? Detecting real diff will be long and painful, perform an alter with what is asked will ensure that nothing can be changed
358539 err := pg .UpdatePublication (ctx , pgDB .Status .Database , currentPublicationName , builder )
359540 // Check error
360541 if err != nil {
0 commit comments