{"record":{"id":"60da94223eb653dd","repo":"pentaho/pentaho-kettle","slug":"multimergejoinmeta-exception-unabletosavestepinfo","errorCode":"MultiMergeJoinMeta.Exception.UnableToSaveStepInfo","errorMessage":"MultiMergeJoinMeta.Exception.UnableToSaveStepInfo","messagePattern":"MultiMergeJoinMeta\\.Exception\\.UnableToSaveStepInfo","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"engine/src/main/java/org/pentaho/di/trans/steps/multimerge/MultiMergeJoinMeta.java","lineNumber":269,"sourceCode":"        rep.saveStepAttribute( id_transformation, id_step, i, \"keys\", keyFields[i] );\n      }\n\n      String[] inputStepsNames  = inputSteps != null ? inputSteps : ArrayUtils.EMPTY_STRING_ARRAY;\n      rep.saveStepAttribute( id_transformation, id_step, \"number_input\", inputStepsNames.length );\n      for ( int i = 0; i < inputStepsNames.length; i++ ) {\n        rep.saveStepAttribute( id_transformation, id_step, \"step\" + i, inputStepsNames[ i ] );\n      }\n//      The following was the old way of persisting this step to the repository. This was inconsistent with\n//      how getXML works, and also fails the load/save tester\n//      List<StreamInterface> infoStreams = getStepIOMeta().getInfoStreams();\n//      rep.saveStepAttribute( id_transformation, id_step, \"number_input\", infoStreams.size() );\n//      for ( int i = 0; i < infoStreams.size(); i++ ) {\n//        rep.saveStepAttribute( id_transformation, id_step, \"step\" + i, infoStreams.get( i ).getStepname() );\n//      }\n      // inputSteps[i]\n      rep.saveStepAttribute( id_transformation, id_step, \"join_type\", getJoinType() );\n    } catch ( Exception e ) {\n      throw new KettleException( BaseMessages.getString( PKG, \"MultiMergeJoinMeta.Exception.UnableToSaveStepInfo\" )\n          + id_step, e );\n    }\n  }\n\n  @Override\n  public void check( List<CheckResultInterface> remarks, TransMeta transMeta, StepMeta stepMeta, RowMetaInterface prev,\n      String[] input, String[] output, RowMetaInterface info, VariableSpace space, Repository repository,\n      IMetaStore metaStore ) {\n    /*\n     * @todo Need to check for the following: 1) Join type must be one of INNER / LEFT OUTER / RIGHT OUTER / FULL OUTER\n     * 2) Number of input streams must be two (for now at least) 3) The field names of input streams must be unique\n     */\n    CheckResult cr =\n        new CheckResult( CheckResultInterface.TYPE_RESULT_WARNING, BaseMessages.getString( PKG,\n            \"MultiMergeJoinMeta.CheckResult.StepNotVerified\" ), stepMeta );\n    remarks.add( cr );\n  }\n","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/engine/src/main/java/org/pentaho/di/trans/steps/multimerge/MultiMergeJoinMeta.java#L251-L287","documentation":"Thrown by MultiMergeJoinMeta.saveRep when saving the step's settings to the repository fails, in particular the rep.saveStepAttribute call that persists \"join_type\". The KettleException message is concatenated with the id_step to identify which step failed to save.","triggerScenarios":"saveRep runs when saving a transformation to a repository; it throws if the repository write fails: dropped database connection, read-only repository/user without write permission, repository schema missing the attribute table/column, transaction rollback, or oversized attribute value.","commonSituations":"Saving during a transient database outage; a user account with read-only repository rights; repository upgraded/downgraded with a schema mismatch; concurrent edits causing locks or deadlocks on r_step_attribute.","solutions":["Check the wrapped cause for the underlying SQL/repository error (connectivity, permissions, schema).","Verify the repository user has write privileges on the step attribute tables and the database is writable.","Confirm repository connectivity and retry the save after any transient database issue is resolved.","Check that the repository schema matches your PDI version; run the repository upgrade scripts if needed.","As a workaround, export the transformation to a .ktr file while the repository write is broken."],"exampleFix":"// before: saving with a read-only repository user\nrep.save(transMeta); // throws UnableToSaveStepInfo + id_step\n\n// after: connect with a user that has INSERT/UPDATE on r_step_attribute\nRepository rep = new KettleDatabaseRepository();\nrep.connect( \"repoHost\", \"8080\", \"kettle\", \"kettle\", writableUser, writablePass );\nrep.save(transMeta);","handlingStrategy":"try-catch","validationCode":"// Check write access to the repository before saving\nif ( !repository.isActive() || !repUserHasWritePrivileges ) {\n  throw new IllegalStateException( \"Repository not writable for user\" );\n}","typeGuard":null,"tryCatchPattern":"try {\n  repository.save( transMeta );\n} catch ( KettleException e ) {\n  if ( e.getMessage().startsWith( \"UnableToSaveStepInfo\" ) ) {\n    // fall back: export to .ktr file, check DB connectivity/permissions, then retry\n  } else { throw e; }\n}","preventionTips":["Save with a repository account that has INSERT/UPDATE rights on step attribute tables.","Verify database connectivity before long editing sessions.","Keep repository schema scripts in sync with the PDI version.","Periodically export transformations to .ktr as a backup."],"tags":["kettle","repository","persistence","metadata","step-meta"],"backgroundTag":"database-write-failed","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}