@@ -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"
@@ -39,6 +41,9 @@ import (
3941)
4042
4143const DefaultReplicationSlotPlugin = "pgoutput"
44+ const DefaultPGWithPublishParameter = "insert, update, delete, truncate"
45+
46+ var DefaultPGWithPublishViaRootParameter = false
4247
4348// PostgresqlPublicationReconciler reconciles a PostgresqlPublication object.
4449type PostgresqlPublicationReconciler struct {
@@ -257,6 +262,20 @@ func (r *PostgresqlPublicationReconciler) mainReconcile(
257262 if err != nil {
258263 return r .manageError (ctx , reqLogger , instance , originalPatch , err )
259264 }
265+ } else {
266+ // Check if reconcile from PG state is necessary because spec haven't been changed
267+ need , err := r .isReconcileOnPGNecessary (ctx , instance , pg , pgDB , pubRes , nameToSearch )
268+ // Check error
269+ if err != nil {
270+ return r .manageError (ctx , reqLogger , instance , originalPatch , err )
271+ }
272+
273+ // Check if it is needed
274+ if need {
275+ reqLogger .Info ("PG state have been changed but not via operator, update need to be done" )
276+
277+ err = r .manageUpdate (ctx , instance , pg , pgDB , pubRes , nameToSearch )
278+ }
260279 }
261280
262281 // Check if owner are aligned
@@ -316,6 +335,138 @@ func (r *PostgresqlPublicationReconciler) mainReconcile(
316335 return r .manageSuccess (ctx , reqLogger , instance , originalPatch )
317336}
318337
338+ func (* PostgresqlPublicationReconciler ) isReconcileOnPGNecessary (
339+ ctx context.Context ,
340+ instance * v1alpha1.PostgresqlPublication ,
341+ pg postgres.PG ,
342+ pgDB * v1alpha1.PostgresqlDatabase ,
343+ pubRes * postgres.PublicationResult ,
344+ currentPublicationName string ,
345+ ) (bool , error ) {
346+ instanceSpec := instance .Spec
347+
348+ // Check with parameters
349+ // nil spec case
350+ if instanceSpec .WithParameters == nil && (pubRes .PublicationViaRoot || ! pubRes .Delete || ! pubRes .Insert || ! pubRes .Truncate || ! pubRes .Update ) {
351+ return true , nil
352+ }
353+ // Non nil spec case
354+ if instanceSpec .WithParameters != nil {
355+ // publication via root check
356+ if (instanceSpec .WithParameters .PublishViaPartitionRoot == nil && pubRes .PublicationViaRoot ) ||
357+ (instanceSpec .WithParameters .PublishViaPartitionRoot != nil && * instanceSpec .WithParameters .PublishViaPartitionRoot != pubRes .PublicationViaRoot ) {
358+ return true , nil
359+ }
360+
361+ // Now check publish parameters
362+ // Save
363+ publish := strings .ToLower (instanceSpec .WithParameters .Publish )
364+ // Empty spec case
365+ if publish == "" && (! pubRes .Delete || ! pubRes .Insert || ! pubRes .Truncate || ! pubRes .Update ) {
366+ return true , nil
367+ }
368+ // Not empty case
369+ if publish != "" &&
370+ (strings .Contains (publish , "insert" ) != pubRes .Insert ||
371+ strings .Contains (publish , "update" ) != pubRes .Update ||
372+ strings .Contains (publish , "delete" ) != pubRes .Delete ||
373+ strings .Contains (publish , "truncate" ) != pubRes .Truncate ) {
374+ return true , nil
375+ }
376+ }
377+
378+ // Check if it is a all tables
379+ if instanceSpec .AllTables {
380+ // This state cannot be updated so.. Ignoring it
381+ return false , nil
382+ }
383+
384+ // Get publication details
385+ details , err := pg .GetPublicationTablesDetails (ctx , pgDB .Status .Database , currentPublicationName )
386+ if err != nil {
387+ return false , err
388+ }
389+
390+ // Check if we are in the all tables in schema case
391+ if len (instanceSpec .TablesInSchema ) != 0 {
392+ // Compute list of schema coming from publication tables and compare list length.
393+ // The computed list must be <= with the desired list
394+ // Why <= ? Because we can list a schema without any tables in
395+ // So we need to check > to go out quickly
396+ // After that, we need to check that computed list is included in the desired list
397+ // Compute list of schema
398+ computedSchemaList := lo .Uniq (lo .Map (details , func (it * postgres.PublicationTableDetail , _ int ) string { return it .SchemaName }))
399+
400+ // Check length
401+ if len (computedSchemaList ) > len (instanceSpec .TablesInSchema ) {
402+ return true , nil
403+ }
404+
405+ // Check include/subset
406+ if ! lo .Every (instanceSpec .TablesInSchema , computedSchemaList ) {
407+ return true , nil
408+ }
409+ } else {
410+ // Need to check with tables
411+ // Loop over spec table list
412+ for _ , st := range instanceSpec .Tables {
413+ // Check if table isn't in the current list
414+ detail , found := lo .Find (details , func (it * postgres.PublicationTableDetail ) bool {
415+ return st .TableName == it .TableName || st .TableName == fmt .Sprintf ("%s.%s" , it .SchemaName , it .TableName )
416+ })
417+ if ! found {
418+ return true , nil
419+ }
420+
421+ // Check if additional where aren't identical (nil case)
422+ if st .AdditionalWhere != detail .AdditionalWhere {
423+ return true , nil
424+ }
425+ // Check if additional where aren't identical (phase 2)
426+ if st .AdditionalWhere != nil && detail .AdditionalWhere != nil && fmt .Sprintf ("(%s)" , * st .AdditionalWhere ) != * detail .AdditionalWhere {
427+ return true , nil
428+ }
429+
430+ // Now need to check columns
431+
432+ columnNamesToCheck := []string {}
433+
434+ // Check if columns aren't set in spec
435+ if st .Columns == nil {
436+ // If so, get real columns from table and check if list aren't identical
437+ // Split spec table name
438+ spl := strings .Split (st .TableName , "." )
439+ var schemaName , tableName string
440+
441+ // Check split size
442+ if len (spl ) == 1 {
443+ schemaName = defaultPGPublicSchemaName
444+ tableName = spl [0 ]
445+ } else {
446+ schemaName = spl [0 ]
447+ tableName = spl [1 ]
448+ }
449+
450+ columnNamesToCheck , err = pg .GetColumnNamesFromTable (ctx , pgDB .Status .Database , schemaName , tableName )
451+ if err != nil {
452+ return false , err
453+ }
454+ } else {
455+ columnNamesToCheck = * st .Columns
456+ }
457+
458+ // Check difference
459+ r1 , r2 := lo .Difference (columnNamesToCheck , detail .Columns )
460+ if len (r1 ) != 0 || len (r2 ) != 0 {
461+ return true , nil
462+ }
463+ }
464+ }
465+
466+ // Default
467+ return false , nil
468+ }
469+
319470func (* PostgresqlPublicationReconciler ) manageUpdate (
320471 ctx context.Context ,
321472 instance * v1alpha1.PostgresqlPublication ,
@@ -350,6 +501,11 @@ func (*PostgresqlPublicationReconciler) manageUpdate(
350501 if instance .Spec .WithParameters != nil {
351502 // Change with
352503 builder = builder .SetWith (instance .Spec .WithParameters .Publish , instance .Spec .WithParameters .PublishViaPartitionRoot )
504+ } else {
505+ // Potential reconcile case to manage
506+ if pubRes .PublicationViaRoot || ! pubRes .Delete || ! pubRes .Insert || ! pubRes .Truncate || ! pubRes .Update {
507+ builder = builder .SetWith (DefaultPGWithPublishParameter , & DefaultPGWithPublishViaRootParameter )
508+ }
353509 }
354510
355511 // Perform update
0 commit comments